[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