Skip to content

Commit

Permalink
Added application from Grok. Changed the tests and imports.
Browse files Browse the repository at this point in the history
Removed the use of WrongType.
  • Loading branch information
trollfot committed Apr 28, 2012
1 parent f71ee55 commit 3763d4a
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 19 deletions.
2 changes: 1 addition & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Changes
1.6 (unreleased)
----------------

- Nothing changed yet.
- Moved the directive `site` from Grok to this package.


1.5 (2011-01-03)
Expand Down
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def read(*rnames):
'zope.configuration',
'zope.location',
'zope.testing',
'grokcore.content',
]

setup(
Expand All @@ -42,6 +43,7 @@ def read(*rnames):
zip_safe=False,
install_requires=['setuptools',
'ZODB3',
'zope.event',
'grokcore.component >= 2.1',
'martian >= 0.13',
'zope.annotation',
Expand Down
2 changes: 1 addition & 1 deletion src/grokcore/site/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from grokcore.component import *
from grokcore.site.directive import site, local_utility
from grokcore.site.components import Site, LocalUtility
from grokcore.site.components import Site, LocalUtility, Application
from grokcore.site.util import getApplication

import grokcore.site.testing
Expand Down
24 changes: 18 additions & 6 deletions src/grokcore/site/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
#
##############################################################################

from grokcore.component.interfaces import IContext

from persistent import Persistent

from zope.interface import implements
from grokcore.component.interfaces import IContext
from grokcore.site.interfaces import IApplication
from zope.annotation.interfaces import IAttributeAnnotatable
from zope.site.site import SiteManagerContainer
from zope.container.contained import Contained
from zope.interface import implements
from zope.site.site import SiteManagerContainer


class BaseSite(object):
Expand All @@ -39,8 +38,22 @@ class Site(BaseSite, SiteManagerContainer):
Architecture entities like :class:`grokcore.site.LocalUtility` and
:class:`grok.Indexes` objects; see those classes for more
information.
"""


class Application(Site):
"""Mixin for creating Grok application objects.
When a :class:`grokcore.content.Container` (or a
:class:`grokcore.content.Model`, though most developers
use containers) also inherits from :class:`grokcore.site.Application`,
it not only gains the component registration abilities of a
:class:`grokcore.site.site`, but will also be listed in the
Grok admin control panel as one of the applications
that the admin can install directly at the root of their Zope
database.
"""
implements(IApplication)


class LocalUtility(Contained, Persistent):
Expand All @@ -62,6 +75,5 @@ class LocalUtility(Contained, Persistent):
is one that the `grok.LocalUtility` already implements, in which
case Grok cannot tell them apart, and `grok.provides()` must be
used explicitly anyway).
"""
implements(IContext, IAttributeAnnotatable)
1 change: 1 addition & 0 deletions src/grokcore/site/ftests/application/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# this is a package
52 changes: 52 additions & 0 deletions src/grokcore/site/ftests/application/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
An application is a mixin for grok application objects.
You can get the current application by using the
grok.getApplication() function. Typically this will return the same
object as grok.getSite(), but it is possible to have sub-Site objects
which will be returned for grok.getSite(), where-as grok.getApplication
will walk up the tree until it reaches the top-level site object.
Let's create an application, then get it using grok.getApplication():
>>> import grokcore.site
>>> import zope.site.hooks
>>> root = getRootFolder()
>>> app = grokcore.site.util.create_application(Cave, root, 'mycave')
>>> root['cave'] = app
>>> zope.site.hooks.setSite(app)
>>> grokcore.site.getApplication()
<grokcore.site.ftests.application.application.Cave object at ...>
Or get it using getSite():
>>> from zope.component.hooks import getSite
>>> getSite()
<grokcore.site.ftests.application.application.Cave object at ...>
Now we can create a container with a sub-site. When we call grok.getSite()
we'll get the box:
>>> root['cave']['box'] = WoodBox()
>>> zope.site.hooks.setSite(root['cave']['box'])
>>> getSite()
<grokcore.site.ftests.application.application.WoodBox object at ...>
But when we call grokcore.site.util.getApplication() we get the cave:
>>> grokcore.site.getApplication()
<grokcore.site.ftests.application.application.Cave object at ...>
"""
import grokcore.content
import grokcore.site


class Cave(grokcore.content.Container, grokcore.site.Application):
"""A shelter for homeless cavemen.
"""


class WoodBox(grokcore.content.Container, grokcore.site.Site):
"""A prehistoric container for holding ZCA registries.
"""
2 changes: 1 addition & 1 deletion src/grokcore/site/ftests/test_grok_functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def suiteFromPackage(name):

def test_suite():
suite = unittest.TestSuite()
for name in ['utility', 'site']:
for name in ['utility', 'site', 'application']:
suite.addTest(suiteFromPackage(name))
return suite

Expand Down
33 changes: 27 additions & 6 deletions src/grokcore/site/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,35 @@
#
##############################################################################

from zope.interface import Interface, Attribute
from zope.interface import Interface, Attribute, implements
from zope.component.interfaces import IObjectEvent
from grokcore.component.interfaces import IGrokcoreComponentAPI


class IApplication(Interface):
"""Interface to mark the local site used as application root.
"""


class IApplicationInitializedEvent(IObjectEvent):
"""A Grok Application has been created with success and is now ready
to be used.
This event can be used to trigger the creation of contents or other tasks
that require the application to be fully operational : utilities installed
and indexes created in the catalog."""


class ApplicationInitializedEvent(object):
"""A Grok Application has been created and is now ready to be used.
"""
implements(IApplicationInitializedEvent)

def __init__(self, app):
assert IApplication.providedBy(app)
self.object = app


class IUtilityInstaller(Interface):
"""This install an utility in a site. Let you have different
'installation' method if you want (one for Zope2 / Zope3).
Expand All @@ -27,14 +52,10 @@ def __call__(site, utility, provides, name=u'',
"""


class IApplication(Interface):
"""Interface to mark the local site used as application root.
"""


class IBaseClasses(Interface):
Site = Attribute("Mixin class for sites.")
LocalUtility = Attribute("Base class for local utilities.")
Application = Attribute("Base class for applications.")


class IDirectives(Interface):
Expand Down
21 changes: 19 additions & 2 deletions src/grokcore/site/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import grokcore.site.components
import grokcore.site.interfaces


class SiteGrokker(martian.ClassGrokker):
"""Grokker for subclasses of `grokcore.site.Site`."""
martian.component(grokcore.site.components.BaseSite)
Expand Down Expand Up @@ -110,5 +111,21 @@ def setupUtility(site, utility, provides, name=u'',
if setup is not None:
setup(utility)

site_manager.registerUtility(utility, provided=provides,
name=name)
site_manager.registerUtility(utility, provided=provides, name=name)


class ApplicationGrokker(martian.ClassGrokker):
"""Grokker for Grok application classes."""
martian.component(grokcore.site.components.Application)
martian.priority(500)

def grok(self, name, factory, module_info, config, **kw):
# XXX fail loudly if the same application name is used twice.
provides = grokcore.site.interfaces.IApplication
name = '%s.%s' % (module_info.dotted_name, name)
config.action(
discriminator=('utility', provides, name),
callable=component.provideUtility,
args=(factory, provides, name),
)
return True
1 change: 1 addition & 0 deletions src/grokcore/site/tests/application/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# this is a package
37 changes: 37 additions & 0 deletions src/grokcore/site/tests/application/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
After grokking a module that defines an application, the application factory is
available as a utility::
>>> from grokcore.site import testing
>>> testing.grok(__name__)
>>> import zope.component
>>> import grokcore.site.interfaces
>>> calendar_app = zope.component.getUtility(
... grokcore.site.interfaces.IApplication,
... name='grokcore.site.tests.application.application.Calendar')
>>> calendar_app
<class 'grokcore.site.tests.application.application.Calendar'>
Applications are both containers and sites::
>>> issubclass(calendar_app, grokcore.site.Site)
True
Applications can be instanciated without any arguments::
>>> calendar = calendar_app()
>>> calendar
<grokcore.site.tests.application.application.Calendar object at 0x...>
"""

import grokcore.site


class Calendar(grokcore.site.Application):
"""A calendar application that knows about ancient
calendar systems from the stone age.
"""
50 changes: 50 additions & 0 deletions src/grokcore/site/tests/test_grok.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import re
import unittest
from pkg_resources import resource_listdir
from zope.testing import doctest, cleanup, renormalizing
import zope.component.eventtesting

def setUpZope(test):
zope.component.eventtesting.setUp(test)

def cleanUpZope(test):
cleanup.cleanUp()

checker = renormalizing.RENormalizing([
# str(Exception) has changed from Python 2.4 to 2.5 (due to
# Exception now being a new-style class). This changes the way
# exceptions appear in traceback printouts.
(re.compile(r"ConfigurationExecutionError: <class '([\w.]+)'>:"),
r'ConfigurationExecutionError: \1:'),
])

def suiteFromPackage(name):
files = resource_listdir(__name__, name)
suite = unittest.TestSuite()
for filename in files:
if not filename.endswith('.py'):
continue
if filename.endswith('_fixture.py'):
continue
if filename == '__init__.py':
continue

dottedname = 'grokcore.site.tests.%s.%s' % (name, filename[:-3])
test = doctest.DocTestSuite(dottedname,
setUp=setUpZope,
tearDown=cleanUpZope,
checker=checker,
optionflags=doctest.ELLIPSIS+
doctest.NORMALIZE_WHITESPACE)

suite.addTest(test)
return suite

def test_suite():
suite = unittest.TestSuite()
for name in ['utility', 'application']:
suite.addTest(suiteFromPackage(name))
return suite

if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
33 changes: 31 additions & 2 deletions src/grokcore/site/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
#
##############################################################################

from grokcore.site.interfaces import IApplication
import grokcore.site
from grokcore.site.interfaces import IApplication, ApplicationInitializedEvent
from zope.component.hooks import getSite
from zope.event import notify
from zope.lifecycleevent import ObjectCreatedEvent


def getApplication():
"""Return the nearest enclosing :class:`grok.Application`.
"""Return the nearest enclosing :class:`grokcore.site.Application`.
Raises :exc:`ValueError` if no application can be found.
"""
Expand All @@ -33,3 +36,29 @@ def getApplication():
obj = obj.__parent__
raise ValueError("No application found.")


def create_application(factory, container, name):
"""Creates an application and triggers the events from
the application lifecycle.
"""
# Check the factory.
assert IApplication.implementedBy(factory)

# Check the availability of the name in the container.
if name in container:
raise KeyError(name)

# Instanciate the application
application = factory()

# Trigger the creation event.
notify(ObjectCreatedEvent(application))

# Persist the application.
# This may raise a KeyError.
container[name] = application

# Trigger the initialization event.
notify(ApplicationInitializedEvent(application))

return application

0 comments on commit 3763d4a

Please sign in to comment.