[Checkins] SVN: grokcore.component/trunk/ svn merge -r 119387:120018 svn+ssh://svn.zope.org/repos/main/grokcore.component/branches/sylvain-subscribers .
Jan-Wijbrand Kolman
janwijbrand at gmail.com
Mon Jan 31 05:59:54 EST 2011
Log message for revision 120019:
svn merge -r 119387:120018 svn+ssh://svn.zope.org/repos/main/grokcore.component/branches/sylvain-subscribers .
Changed:
U grokcore.component/trunk/README.txt
U grokcore.component/trunk/buildout.cfg
U grokcore.component/trunk/src/grokcore/component/__init__.py
U grokcore.component/trunk/src/grokcore/component/components.py
U grokcore.component/trunk/src/grokcore/component/decorators.py
U grokcore.component/trunk/src/grokcore/component/interfaces.py
U grokcore.component/trunk/src/grokcore/component/meta.py
A grokcore.component/trunk/src/grokcore/component/subscription.py
U grokcore.component/trunk/src/grokcore/component/tests/event/subscriber.py
A grokcore.component/trunk/src/grokcore/component/tests/subscriber/
U grokcore.component/trunk/src/grokcore/component/tests/test_grok.py
U grokcore.component/trunk/src/grokcore/component/util.py
-=-
Modified: grokcore.component/trunk/README.txt
===================================================================
--- grokcore.component/trunk/README.txt 2011-01-31 10:53:07 UTC (rev 120018)
+++ grokcore.component/trunk/README.txt 2011-01-31 10:59:53 UTC (rev 120019)
@@ -173,34 +173,34 @@
class ISchema(Interface):
"""This schema will be used to power a z3c.form form"""
-
+
field = zope.schema.TextLine(title=u"Sample field")
-
+
...
label_override = z3c.form.widget.StaticWidgetAttribute(
u"Override label", field=ISchema['field'])
-
+
grokcore.component.global_adapter(label_override, name=u"label")
-
+
In the example above, the provided and adapted interfaces are deduced from the
object returned by the ``StaticWidgetAttribute`` factory. The full syntax
for global_adapter is::
global_adapter(factory, (IAdapted1, IAdapted2,), IProvided, name=u"name")
-
+
The factory must be a callable (the adapter factory). Adapted interfaces are
given as a tuple. You may use a single interface instead of a one-element
tuple for single adapters. The provided interface is given as shown. The name
defaults to u"" (an unnamed adapter).
-Subscriber
-----------
+Handling events
+---------------
-Here we see a subscriber much like it occurs within Zope itself. It
-subscribes to the modified event for all annotatable objects (in other
-words, objects that can have metadata associated with them). When
-invoked, it updates the Dublin Core 'Modified' property accordingly::
+Here we see an event handler much like it occurs within Zope itself. It
+subscribes to the modified event for all annotatable objects (in other words,
+objects that can have metadata associated with them). When invoked, it updates
+the Dublin Core 'Modified' property accordingly::
import datetime
import grokcore.component
@@ -329,12 +329,12 @@
``name`` argument must be a keyword argument and is optional. If given,
a named adapter is registered.
-``@implementer(iface1, iface2, ...)```
+``@implementer(iface1, iface2, ...)``
declares that the function implements a certain interface (or a
number of interfaces). This is useful when a function serves as an object
factory, e.g. as an adapter.
-``@provider(iface1, iface2, ...)```
+``@provider(iface1, iface2, ...)``
declares that the function object provides a certain interface (or a
number of interfaces). This is akin to calling directlyProvides() on
the function object.
Modified: grokcore.component/trunk/buildout.cfg
===================================================================
--- grokcore.component/trunk/buildout.cfg 2011-01-31 10:53:07 UTC (rev 120018)
+++ grokcore.component/trunk/buildout.cfg 2011-01-31 10:59:53 UTC (rev 120019)
@@ -17,4 +17,4 @@
recipe = zc.recipe.testrunner
eggs = grokcore.component
grokcore.component[test]
-defaults = ['--tests-pattern', '^f?tests$', '-v']
+defaults = ['--tests-pattern', '^f?tests$', '-v', '--auto-color']
Modified: grokcore.component/trunk/src/grokcore/component/__init__.py
===================================================================
--- grokcore.component/trunk/src/grokcore/component/__init__.py 2011-01-31 10:53:07 UTC (rev 120018)
+++ grokcore.component/trunk/src/grokcore/component/__init__.py 2011-01-31 10:59:53 UTC (rev 120019)
@@ -22,7 +22,8 @@
from martian import ClassGrokker, InstanceGrokker, GlobalGrokker
from grokcore.component.components import (
- Adapter, GlobalUtility, MultiAdapter, Context)
+ Adapter, GlobalUtility, MultiAdapter, Context, Subscription,
+ MultiSubscription)
from grokcore.component.directive import (
context, description, direct, name, order, path, provides, title,
@@ -31,6 +32,10 @@
from grokcore.component.decorators import (
subscribe, adapter, implementer, provider)
+from grokcore.component.subscription import (
+ querySubscriptions, queryMultiSubscriptions,
+ queryOrderedSubscriptions, queryOrderedMultiSubscriptions)
+
# Import this module so that it's available as soon as you import the
# 'grokcore.component' package. Useful for tests and interpreter examples.
import grokcore.component.testing
Modified: grokcore.component/trunk/src/grokcore/component/components.py
===================================================================
--- grokcore.component/trunk/src/grokcore/component/components.py 2011-01-31 10:53:07 UTC (rev 120018)
+++ grokcore.component/trunk/src/grokcore/component/components.py 2011-01-31 10:59:53 UTC (rev 120019)
@@ -17,6 +17,7 @@
from grokcore.component.interfaces import IContext
+
class Adapter(object):
"""Base class to define an adapter.
@@ -25,11 +26,13 @@
.. attribute:: context
The adapted object.
-
+
"""
+
def __init__(self, context):
self.context = context
+
class GlobalUtility(object):
"""Base class to define a globally registered utility.
@@ -37,10 +40,25 @@
"""
pass
+
class MultiAdapter(object):
"""Base class to define a Multi Adapter.
"""
pass
+
+class Subscription(object):
+ """Base class for a subscription adapter.
+ """
+
+ def __init__(self, context):
+ self.context = context
+
+
+class MultiSubscription(object):
+ """Base class for a subscription multi-adapter.
+ """
+
+
class Context(object):
implements(IContext)
Modified: grokcore.component/trunk/src/grokcore/component/decorators.py
===================================================================
--- grokcore.component/trunk/src/grokcore/component/decorators.py 2011-01-31 10:53:07 UTC (rev 120018)
+++ grokcore.component/trunk/src/grokcore/component/decorators.py 2011-01-31 10:59:53 UTC (rev 120019)
@@ -37,14 +37,17 @@
raise GrokImportError("@grok.subscribe requires at least one "
"argument.")
+ # Add the function and subscribed interfaces to the
+ # grok.subscribers module annotation.
subscribers = frame.f_locals.get('__grok_subscribers__', None)
if subscribers is None:
frame.f_locals['__grok_subscribers__'] = subscribers = []
subscribers.append((function, self.subscribed))
- # Also add __grok_adapts__ attribute to the function so that
- # you can manually register the subscriber with, say,
- # provideHandler.
+ # Also store the subscribed interfaces on the
+ # attribute__component_adapts__ for provideHandler to register
+ # the subscriber (in case you don't grok your package and
+ # register it manually)
return zope.component.adapter(*self.subscribed)(function)
class adapter(zope.component.adapter):
@@ -59,18 +62,18 @@
if type(interfaces[0]) is types.FunctionType:
raise GrokImportError(
"@grok.adapter requires at least one argument.")
-
+
self.name = u""
-
+
if kw:
if 'name' in kw:
self.name = kw.pop('name')
if kw:
raise GrokImportError(
"@grok.adapter got unexpected keyword arguments: %s" % ','.join(kw.keys()))
-
+
zope.component.adapter.__init__(self, *interfaces)
-
+
def __call__(self, ob):
ob = zope.component.adapter.__call__(self, ob)
if self.name:
@@ -87,6 +90,7 @@
if adapters is None:
frame.f_locals['__grok_adapters__'] = adapters = []
adapters.append(ob)
+
return zope.interface.implementer.__call__(self, ob)
class provider:
Modified: grokcore.component/trunk/src/grokcore/component/interfaces.py
===================================================================
--- grokcore.component/trunk/src/grokcore/component/interfaces.py 2011-01-31 10:53:07 UTC (rev 120018)
+++ grokcore.component/trunk/src/grokcore/component/interfaces.py 2011-01-31 10:59:53 UTC (rev 120019)
@@ -40,10 +40,13 @@
GlobalGrokker = Attribute("Base class to define a module grokker.")
Context = Attribute("Base class for automatically associated contexts.")
-
+
Adapter = Attribute("Base class for adapters.")
MultiAdapter = Attribute("Base class for multi-adapters.")
GlobalUtility = Attribute("Base class for global utilities.")
+ Subscription = Attribute("Base class for subscription adapters.")
+ MultiSubscription = Attribute(
+ "Base class for subscription mult-adapters.")
class IDirectives(Interface):
@@ -57,13 +60,13 @@
def implements(*interfaces):
"""Declare that a class implements the given interfaces."""
-
+
def implementsOnly(*interfaces):
"""Declare that a class implements only the given interfaces.
-
+
Interfaces implemented by base classes are explicitly not inherited.
"""
-
+
def classProvides(*interfaces):
"""Declare that a class (as opposed to instances of the class)
directly provides the given interfaces.
@@ -165,7 +168,7 @@
"""Describes that a function that's used as an adapter
implements an interface or a number of interfaces.
"""
-
+
def provider(*interfaces):
"""Describes that a function directly provides an interface or a
number of interfaces.
@@ -195,3 +198,10 @@
class IGrokcoreComponentAPI(IBaseClasses, IDirectives, IDecorators,
IGrokErrors, IMartianAPI):
"""grokcore.component's public API."""
+
+ querySubscriptions = Attribute("Function to query subscriptions.")
+ queryOrderedSubscriptions = Attribute(
+ "Function to query subscription in order.")
+ queryMultiSubscriptions = Attribute("Function to query subscriptions.")
+ queryOrderedMultiSubscriptions = Attribute(
+ "Function to query subscriptions in order.")
Modified: grokcore.component/trunk/src/grokcore/component/meta.py
===================================================================
--- grokcore.component/trunk/src/grokcore/component/meta.py 2011-01-31 10:53:07 UTC (rev 120018)
+++ grokcore.component/trunk/src/grokcore/component/meta.py 2011-01-31 10:59:53 UTC (rev 120019)
@@ -13,12 +13,15 @@
##############################################################################
"""Grokkers for the various components."""
+import operator
+
import martian
import martian.util
import grokcore.component
import zope.component.interface
from zope import component, interface
from martian.error import GrokError
+from zope.interface import implementedBy
def _provides(component, module=None, **data):
martian.util.check_implements_one(component)
@@ -30,6 +33,7 @@
return list(interface.providedBy(component))[0]
return _provides(component)
+
class AdapterGrokker(martian.ClassGrokker):
martian.component(grokcore.component.Adapter)
martian.directive(grokcore.component.context)
@@ -44,17 +48,18 @@
)
return True
+
class MultiAdapterGrokker(martian.ClassGrokker):
martian.component(grokcore.component.MultiAdapter)
martian.directive(grokcore.component.provides)
martian.directive(grokcore.component.name)
def execute(self, factory, config, provides, name, **kw):
- if component.adaptedBy(factory) is None:
+ for_ = component.adaptedBy(factory)
+ if for_ is None:
raise GrokError("%r must specify which contexts it adapts "
"(use the 'adapts' directive to specify)."
% factory, factory)
- for_ = component.adaptedBy(factory)
config.action(
discriminator=('adapter', for_, provides, name),
@@ -63,6 +68,42 @@
)
return True
+
+class SubscriptionGrokker(martian.ClassGrokker):
+ martian.component(grokcore.component.Subscription)
+ martian.directive(grokcore.component.context)
+ martian.directive(grokcore.component.provides)
+ martian.directive(grokcore.component.name)
+
+ def execute(self, factory, config, context, provides, name, **kw):
+ config.action(
+ discriminator=None,
+ callable=component.provideSubscriptionAdapter,
+ args=(factory, (context,), provides),
+ )
+ return True
+
+
+class MultiSubscriptionGrokker(martian.ClassGrokker):
+ martian.component(grokcore.component.MultiSubscription)
+ martian.directive(grokcore.component.provides)
+ martian.directive(grokcore.component.name)
+
+ def execute(self, factory, config, provides, name, **kw):
+ adapts = component.adaptedBy(factory)
+ if adapts is None:
+ raise GrokError("%r must specify which contexts it adapts "
+ "(use the 'adapts' directive to specify)."
+ % factory, factory)
+
+ config.action(
+ discriminator=None,
+ callable=component.provideSubscriptionAdapter,
+ args=(factory, adapts, provides),
+ )
+ return True
+
+
class GlobalUtilityGrokker(martian.ClassGrokker):
martian.component(grokcore.component.GlobalUtility)
@@ -86,11 +127,21 @@
)
return True
-class AdapterDecoratorGrokker(martian.GlobalGrokker):
+class ImplementerDecoratorGrokker(martian.GlobalGrokker):
+
def grok(self, name, module, module_info, config, **kw):
adapters = module_info.getAnnotation('grok.adapters', [])
+ subscribers = set(map(operator.itemgetter(0),
+ module_info.getAnnotation('grok.subscribers', [])))
+
for function in adapters:
+ if function in subscribers:
+ # We don't register functions that are decorated with
+ # grok.implementer() *and* the grok.subscribe()
+ # decorator. These are registered as so called
+ # subcribers and not as regular adapters.
+ continue
interfaces = getattr(function, '__component_adapts__', None)
if interfaces is None:
context = grokcore.component.context.bind().get(module)
@@ -135,6 +186,7 @@
return True
+
class GlobalAdapterDirectiveGrokker(martian.GlobalGrokker):
def grok(self, name, module, module_info, config, **kw):
@@ -158,22 +210,36 @@
return True
-class SubscriberGrokker(martian.GlobalGrokker):
+class SubscriberDirectiveGrokker(martian.GlobalGrokker):
+
def grok(self, name, module, module_info, config, **kw):
subscribers = module_info.getAnnotation('grok.subscribers', [])
for factory, subscribed in subscribers:
- config.action(
- discriminator=None,
- callable=component.provideHandler,
- args=(factory, subscribed),
- )
+ provides = None
+ implemented = list(implementedBy(factory))
+ if len(implemented) == 1:
+ provides = implemented[0]
+ # provideHandler is essentially the same as
+ # provideSubscriptionAdapter, where provided=None. However,
+ # handlers and subscription adapters are tracked in
+ # separately so we cannot exchange one registration call
+ # for the the other.
+ if provides is None:
+ config.action(
+ discriminator=None,
+ callable=component.provideHandler,
+ args=(factory, subscribed))
+ else:
+ config.action(
+ discriminator=None,
+ callable=component.provideSubscriptionAdapter,
+ args=(factory, subscribed, provides))
for iface in subscribed:
config.action(
discriminator=None,
callable=zope.component.interface.provideInterface,
- args=('', iface)
- )
+ args=('', iface))
return True
Copied: grokcore.component/trunk/src/grokcore/component/subscription.py (from rev 120018, grokcore.component/branches/sylvain-subscribers/src/grokcore/component/subscription.py)
===================================================================
--- grokcore.component/trunk/src/grokcore/component/subscription.py (rev 0)
+++ grokcore.component/trunk/src/grokcore/component/subscription.py 2011-01-31 10:59:53 UTC (rev 120019)
@@ -0,0 +1,41 @@
+##############################################################################
+#
+# Copyright (c) 2006-2007 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Grok subscriptions functions.
+"""
+from zope import component
+from grokcore.component import util
+
+def queryOrderedMultiSubscriptions(components, interface):
+ return util.sort_components(component.subscribers(components, interface))
+
+def queryOrderedSubscriptions(component, interface):
+ return queryOrderedMultiSubscriptions((component, ), interface)
+
+def queryMultiSubscriptions(components, interface):
+ """Query for subscriptions on the `components` providing `interface`.
+
+ :parameter components: tuple of components to lookup the subscription for.
+ :parameter interface: interface that the subscriptions should provide.
+ :return: a list of subscriptions.
+ """
+ return component.subscribers(components, interface)
+
+def querySubscriptions(component, interface):
+ """Query for subscriptions on `component` providing `interface`.
+
+ :parameter component: a component to lookup the subscriptions for.
+ :parameter interface: interface that the subscriptions should provide.
+ :return: a list of subscription.
+ """
+ return queryMultiSubscriptions((component,), interface)
Modified: grokcore.component/trunk/src/grokcore/component/tests/event/subscriber.py
===================================================================
--- grokcore.component/trunk/src/grokcore/component/tests/event/subscriber.py 2011-01-31 10:53:07 UTC (rev 120018)
+++ grokcore.component/trunk/src/grokcore/component/tests/event/subscriber.py 2011-01-31 10:59:53 UTC (rev 120019)
@@ -8,9 +8,9 @@
['Manfred']
>>> mammoths2
['Manfred']
-
-The decorated event handling function can also be called directly:
-
+
+The decorated event handling function can also be called directly:
+
>>> mammothAdded(Mammoth('Max'),None)
>>> mammoths
['Manfred', 'Max']
Modified: grokcore.component/trunk/src/grokcore/component/tests/test_grok.py
===================================================================
--- grokcore.component/trunk/src/grokcore/component/tests/test_grok.py 2011-01-31 10:53:07 UTC (rev 120018)
+++ grokcore.component/trunk/src/grokcore/component/tests/test_grok.py 2011-01-31 10:59:53 UTC (rev 120019)
@@ -39,7 +39,7 @@
checker=checker,
optionflags=doctest.ELLIPSIS+
doctest.NORMALIZE_WHITESPACE)
- except ImportError, e: # or should this accept anything?
+ except ImportError: # or should this accept anything?
traceback.print_exc()
raise
suite.addTest(test)
@@ -48,7 +48,7 @@
def test_suite():
suite = unittest.TestSuite()
for name in ['adapter', 'directive', 'grokker', 'utility', 'view',
- 'event', 'inherit', 'order']:
+ 'event', 'inherit', 'order', 'subscriber']:
suite.addTest(suiteFromPackage(name))
api = doctest.DocFileSuite('api.txt')
Modified: grokcore.component/trunk/src/grokcore/component/util.py
===================================================================
--- grokcore.component/trunk/src/grokcore/component/util.py 2011-01-31 10:53:07 UTC (rev 120018)
+++ grokcore.component/trunk/src/grokcore/component/util.py 2011-01-31 10:59:53 UTC (rev 120019)
@@ -15,7 +15,6 @@
"""
from grokcore.component import directive
-
def _sort_key(component):
# If components have a grok.order directive, sort by that.
explicit_order, implicit_order = directive.order.bind().get(component)
@@ -26,4 +25,7 @@
def sort_components(components):
+ """Sort a list of components using the information provided by
+ `grok.order`.
+ """
return sorted(components, key=_sort_key)
More information about the checkins
mailing list