[Checkins] SVN: zope.component/branches/conditional-zope.security/ Removed the dependencies on zope.proxy and zope.security from the zcml extra.

Fabio Tranchitella kobold at kobold.it
Sun Oct 11 19:16:29 EDT 2009


Log message for revision 105020:
  Removed the dependencies on zope.proxy and zope.security from the zcml extra.
  
  zope.component does not hard depend on them anymore; the support for security
  proxied components ZCML registrations is enabled only if zope.security and
  zope.proxy are available.
  
  

Changed:
  U   zope.component/branches/conditional-zope.security/CHANGES.txt
  U   zope.component/branches/conditional-zope.security/setup.py
  U   zope.component/branches/conditional-zope.security/src/zope/component/tests.py
  U   zope.component/branches/conditional-zope.security/src/zope/component/zcml.py
  A   zope.component/branches/conditional-zope.security/src/zope/component/zcml_conditional.txt

-=-
Modified: zope.component/branches/conditional-zope.security/CHANGES.txt
===================================================================
--- zope.component/branches/conditional-zope.security/CHANGES.txt	2009-10-11 22:21:10 UTC (rev 105019)
+++ zope.component/branches/conditional-zope.security/CHANGES.txt	2009-10-11 23:16:28 UTC (rev 105020)
@@ -1,10 +1,13 @@
 CHANGES
 *******
 
-3.8.0 (unreleased)
+3.8.0dev (unreleased)
 ==================
 
-- ...
+- Removed the dependencies on zope.proxy and zope.security from the zcml extra:
+  zope.component does not hard depend on them anymore; the support for security
+  proxied components ZCML registrations is enabled only if zope.security and
+  zope.proxy are available.
 
 3.7.1 (2009-07-24)
 ==================

Modified: zope.component/branches/conditional-zope.security/setup.py
===================================================================
--- zope.component/branches/conditional-zope.security/setup.py	2009-10-11 22:21:10 UTC (rev 105019)
+++ zope.component/branches/conditional-zope.security/setup.py	2009-10-11 23:16:28 UTC (rev 105020)
@@ -63,7 +63,13 @@
     package_dir = {'': 'src'},
 
     namespace_packages=['zope',],
-    tests_require = ['zope.testing'],
+    tests_require = [
+        'zope.testing'
+        'zope.hookable',
+        'zope.location',
+        'zope.proxy',
+        'zope.security',
+        ],
     install_requires=['setuptools',
                       'zope.interface',
                       'zope.event',
@@ -74,14 +80,14 @@
         hook = ['zope.hookable'],
         persistentregistry = ['ZODB3'],
         zcml = ['zope.configuration',
-                'zope.security',
-                'zope.proxy',
                 'zope.i18nmessageid',
                 ],
         test = ['ZODB3',
                 'zope.testing',
                 'zope.hookable',
                 'zope.location',
+                'zope.proxy',
+                'zope.security',
                 ],
         docs = ['z3c.recipe.sphinxdoc'],
         ),

Modified: zope.component/branches/conditional-zope.security/src/zope/component/tests.py
===================================================================
--- zope.component/branches/conditional-zope.security/src/zope/component/tests.py	2009-10-11 22:21:10 UTC (rev 105019)
+++ zope.component/branches/conditional-zope.security/src/zope/component/tests.py	2009-10-11 23:16:28 UTC (rev 105020)
@@ -15,16 +15,18 @@
 
 $Id$
 """
+import persistent
 import re
+import sys
 import unittest
 import transaction
-import persistent
 from cStringIO import StringIO
 
 from zope import interface, component
 from zope.interface.verify import verifyObject
 from zope.interface.interfaces import IInterface
 from zope.testing import doctest, renormalizing
+from zope.testing.testrunner.layer import UnitTests
 
 from zope.component.interfaces import ComponentLookupError
 from zope.component.interfaces import IComponentArchitecture
@@ -1635,6 +1637,28 @@
         self.assertRaises(ValueError, xmlconfig, config, testing=1)
 
 
+class ConditionalSecurityLayer(UnitTests):
+
+    __name__ = 'ConditionalSecurity'
+    __bases__ = ()
+
+    def setUp(self):
+        setUp()
+        self.modules = {}
+        for m in ('zope.security', 'zope.proxy'):
+            self.modules[m] = sys.modules[m]
+            sys.modules[m] = None
+        import zope.component.zcml
+        reload(zope.component.zcml)
+
+    def tearDown(self):
+        tearDown()
+        for m in ('zope.security', 'zope.proxy'):
+            sys.modules[m] = self.modules[m]
+        import zope.component.zcml
+        reload(zope.component.zcml)
+
+
 def setUpRegistryTests(tests):
     setUp()
 
@@ -1655,6 +1679,9 @@
                     r'exceptions.\1Error:'),
         ])
 
+    zcml_conditional = doctest.DocFileSuite('zcml_conditional.txt', checker=checker)
+    zcml_conditional.layer = ConditionalSecurityLayer()
+
     return unittest.TestSuite((
         doctest.DocTestSuite(setUp=setUp, tearDown=tearDown),
         unittest.makeSuite(HookableTests),
@@ -1672,8 +1699,9 @@
                              tearDown=tearDownRegistryTests),
         doctest.DocFileSuite('event.txt',
                              setUp=setUp, tearDown=tearDown),
-        doctest.DocFileSuite('zcml.txt',checker=checker,
+        doctest.DocFileSuite('zcml.txt', checker=checker,
                              setUp=setUp, tearDown=tearDown),
+        zcml_conditional,
         unittest.makeSuite(StandaloneTests),
         unittest.makeSuite(ResourceViewTests),
         ))

Modified: zope.component/branches/conditional-zope.security/src/zope/component/zcml.py
===================================================================
--- zope.component/branches/conditional-zope.security/src/zope/component/zcml.py	2009-10-11 22:21:10 UTC (rev 105019)
+++ zope.component/branches/conditional-zope.security/src/zope/component/zcml.py	2009-10-11 23:16:28 UTC (rev 105020)
@@ -19,19 +19,77 @@
 
 import warnings
 import zope.component
+import zope.configuration.fields
 import zope.interface
 import zope.schema
-import zope.configuration.fields
+
+from zope.component.interface import provideInterface
 from zope.configuration.exceptions import ConfigurationError
-import zope.security.zcml
-from zope.component.interface import provideInterface
-from zope.proxy import ProxyBase, getProxiedObject
-from zope.security.proxy import Proxy
-from zope.security.checker import InterfaceChecker, CheckerPublic, Checker
-from zope.security.adapter import LocatingTrustedAdapterFactory
-from zope.security.adapter import LocatingUntrustedAdapterFactory
-from zope.security.adapter import TrustedAdapterFactory
 from zope.i18nmessageid import MessageFactory
+
+try:
+    from zope.proxy import ProxyBase, getProxiedObject
+    from zope.security.adapter import LocatingTrustedAdapterFactory, \
+        LocatingUntrustedAdapterFactory, TrustedAdapterFactory
+    from zope.security.checker import InterfaceChecker, CheckerPublic, Checker
+    from zope.security.proxy import Proxy
+    from zope.security.zcml import Permission
+except ImportError:
+    SECURITY_SUPPORT = False
+    from zope.schema import TextLine as Permission
+else:
+    SECURITY_SUPPORT = True
+    class PermissionProxy(ProxyBase):
+
+        __slots__ = ('__Security_checker__', )
+
+        def __providedBy__(self):
+            return zope.interface.providedBy(getProxiedObject(self))
+        __providedBy__ = property(__providedBy__)
+
+    def proxify(ob, checker):
+        """Try to get the object proxied with the `checker`, but not too soon
+
+        We really don't want to proxy the object unless we need to.
+        """
+
+        ob = PermissionProxy(ob)
+        ob.__Security_checker__ = checker
+        return ob
+
+    def _protectedFactory(original_factory, checker):
+        # This has to be named 'factory', aparently, so as not to confuse
+        # apidoc :(
+        def factory(*args):
+            ob = original_factory(*args)
+            try:
+                ob.__Security_checker__ = checker
+            except AttributeError:
+                ob = Proxy(ob, checker)
+
+            return ob
+        factory.factory = original_factory
+        return factory
+
+    def _checker(_context, permission, allowed_interface, allowed_attributes):
+        if (not allowed_attributes) and (not allowed_interface):
+            allowed_attributes = ["__call__"]
+
+        if permission == PublicPermission:
+            permission = CheckerPublic
+
+        require={}
+        if allowed_attributes:
+            for name in allowed_attributes:
+                require[name] = permission
+        if allowed_interface:
+            for i in allowed_interface:
+                for name in i.names(all=True):
+                    require[name] = permission
+
+        checker = Checker(require)
+        return checker
+
 _ = MessageFactory('zope')
 
 PublicPermission = 'zope.Public'
@@ -51,7 +109,7 @@
         required=False,
         )
 
-    permission = zope.security.zcml.Permission(
+    permission = Permission(
         title=_("Permission"),
         description=_("Permission required to use this component."),
         required=False,
@@ -96,7 +154,7 @@
           ),
         )
 
-    permission = zope.security.zcml.Permission(
+    permission = Permission(
         title=_("Permission"),
         description=_("This adapter is only available, if the principal"
                       " has this permission."),
@@ -147,20 +205,6 @@
     factory.factory = factories[0]
     return factory
 
-def _protectedFactory(original_factory, checker):
-    # This has to be named 'factory', aparently, so as not to confuse
-    # apidoc :(
-    def factory(*args):
-        ob = original_factory(*args)
-        try:
-            ob.__Security_checker__ = checker
-        except AttributeError:
-            ob = Proxy(ob, checker)
-
-        return ob
-    factory.factory = original_factory
-    return factory
-
 def adapter(_context, factory, provides=None, for_=None, permission=None,
             name='', trusted=False, locate=False):
 
@@ -195,6 +239,9 @@
         factory = _rolledUpFactory(factories)
 
     if permission is not None:
+        if not SECURITY_SUPPORT:
+            raise ConfigurationError("security proxied components are not "
+                "supported because zope.security is not available")
         if permission == PublicPermission:
             permission = CheckerPublic
         checker = InterfaceChecker(provides, permission)
@@ -202,12 +249,18 @@
 
     # invoke custom adapter factories
     if locate or (permission is not None and permission is not CheckerPublic):
+        if not SECURITY_SUPPORT:
+            raise ConfigurationError("security proxied components are not "
+                "supported because zope.security is not available")
         if trusted:
             factory = LocatingTrustedAdapterFactory(factory)
         else:
             factory = LocatingUntrustedAdapterFactory(factory)
     else:
         if trusted:
+            if not SECURITY_SUPPORT:
+                raise ConfigurationError("security proxied components are not "
+                    "supported because zope.security is not available")
             factory = TrustedAdapterFactory(factory)
 
     _context.action(
@@ -263,7 +316,7 @@
           ),
         )
 
-    permission = zope.security.zcml.Permission(
+    permission = Permission(
         title=_("Permission"),
         description=_("This subscriber is only available, if the"
                       " principal has this permission."),
@@ -319,6 +372,9 @@
                             "determine what the factory (or handler) adapts.")
 
     if permission is not None:
+        if not SECURITY_SUPPORT:
+            raise ConfigurationError("security proxied components are not "
+                "supported because zope.security is not available")
         if permission == PublicPermission:
             permission = CheckerPublic
         checker = InterfaceChecker(provides, permission)
@@ -328,12 +384,18 @@
 
     # invoke custom adapter factories
     if locate or (permission is not None and permission is not CheckerPublic):
+        if not SECURITY_SUPPORT:
+            raise ConfigurationError("security proxied components are not "
+                "supported because zope.security is not available")
         if trusted:
             factory = LocatingTrustedAdapterFactory(factory)
         else:
             factory = LocatingUntrustedAdapterFactory(factory)
     else:
         if trusted:
+            if not SECURITY_SUPPORT:
+                raise ConfigurationError("security proxied components are not "
+                    "supported because zope.security is not available")
             factory = TrustedAdapterFactory(factory)
 
     if handler is not None:
@@ -383,24 +445,6 @@
         required=False,
         )
 
-class PermissionProxy(ProxyBase):
-
-    __slots__ = ('__Security_checker__', )
-
-    def __providedBy__(self):
-        return zope.interface.providedBy(getProxiedObject(self))
-    __providedBy__ = property(__providedBy__)
-
-def proxify(ob, checker):
-    """Try to get the object proxied with the `checker`, but not too soon
-
-    We really don't want to proxy the object unless we need to.
-    """
-
-    ob = PermissionProxy(ob)
-    ob.__Security_checker__ = checker
-    return ob
-
 def utility(_context, provides=None, component=None, factory=None,
             permission=None, name=''):
     if factory and component:
@@ -417,10 +461,12 @@
             raise TypeError("Missing 'provides' attribute")
 
     if permission is not None:
+        if not SECURITY_SUPPORT:
+            raise ConfigurationError("security proxied components are not "
+                "supported because zope.security is not available")
         if permission == PublicPermission:
             permission = CheckerPublic
         checker = InterfaceChecker(provides, permission)
-
         component = proxify(component, checker)
 
     _context.action(
@@ -475,7 +521,7 @@
           ),
         )
 
-    permission = zope.security.zcml.Permission(
+    permission = Permission(
         title=_("Permission"),
         description=_("The permission needed to use the view."),
         required=False,
@@ -572,7 +618,10 @@
     if not factory:
         raise ConfigurationError("No view factory specified.")
 
-    if permission:
+    if permission is not None:
+        if not SECURITY_SUPPORT:
+            raise ConfigurationError("security proxied components are not "
+                "supported because zope.security is not available")
 
         checker = _checker(_context, permission,
                            allowed_interface, allowed_attributes)
@@ -684,7 +733,11 @@
             "allowed_attributes"
             )
 
-    if permission:
+    if permission is not None:
+        if not SECURITY_SUPPORT:
+            raise ConfigurationError("security proxied components are not "
+                "supported because zope.security is not available")
+
         checker = _checker(_context, permission,
                            allowed_interface, allowed_attributes)
 
@@ -716,22 +769,3 @@
         callable = provideInterface,
         args = (provides.__module__ + '.' + provides.__name__, type)
                )
-
-def _checker(_context, permission, allowed_interface, allowed_attributes):
-    if (not allowed_attributes) and (not allowed_interface):
-        allowed_attributes = ["__call__"]
-
-    if permission == PublicPermission:
-        permission = CheckerPublic
-
-    require={}
-    if allowed_attributes:
-        for name in allowed_attributes:
-            require[name] = permission
-    if allowed_interface:
-        for i in allowed_interface:
-            for name in i.names(all=True):
-                require[name] = permission
-
-    checker = Checker(require)
-    return checker

Added: zope.component/branches/conditional-zope.security/src/zope/component/zcml_conditional.txt
===================================================================
--- zope.component/branches/conditional-zope.security/src/zope/component/zcml_conditional.txt	                        (rev 0)
+++ zope.component/branches/conditional-zope.security/src/zope/component/zcml_conditional.txt	2009-10-11 23:16:28 UTC (rev 105020)
@@ -0,0 +1,604 @@
+ZCML directives without zope.security support
+=============================================
+
+Components may be registered using the registration API exposed by
+``zope.component`` (provideAdapter, provideUtility, etc.).  They may
+also be registered using configuration files.  The common way to do
+that is by using ZCML (Zope Configuration Markup Language), an XML
+spelling of component registration.
+
+In ZCML, each XML element is a *directive*.  There are different
+top-level directives that let us register components.  We will
+introduce them one by one here.
+
+This helper will let us easily execute ZCML snippets:
+
+  >>> from cStringIO import StringIO
+  >>> from zope.configuration.xmlconfig import xmlconfig
+  >>> def runSnippet(snippet):
+  ...     template = """\
+  ...     <configure xmlns='http://namespaces.zope.org/zope'
+  ...                i18n_domain="zope">
+  ...     %s
+  ...     </configure>"""
+  ...     xmlconfig(StringIO(template % snippet))
+
+adapter
+-------
+
+Adapters play a key role in the Component Architecture.  In ZCML, they
+are registered with the <adapter /> directive.
+
+  >>> from zope.component.testfiles.adapter import A1, A2, A3, Handler
+  >>> from zope.component.testfiles.adapter import I1, I2, I3, IS
+  >>> from zope.component.testfiles.components import IContent, Content, Comp, comp
+
+Before we register the first test adapter, we can verify that adapter
+lookup doesn't work yet:
+
+  >>> from zope.component.tests import clearZCML
+  >>> clearZCML()
+  >>> from zope.component.testfiles.components import IApp
+  >>> IApp(Content(), None) is None
+  True
+
+Then we register the adapter and see that the lookup works:
+
+  >>> runSnippet('''
+  ...   <adapter
+  ...       factory="zope.component.testfiles.components.Comp"
+  ...       provides="zope.component.testfiles.components.IApp"
+  ...       for="zope.component.testfiles.components.IContent"
+  ...       />''')
+
+  >>> IApp(Content()).__class__
+  <class 'zope.component.testfiles.components.Comp'>
+
+It is also possible to give adapters names.  Then the combination of
+required interface, provided interface and name makes the adapter
+lookup unique.  The name is supplied using the ``name`` argument to
+the <adapter /> directive:
+
+  >>> from zope.component.tests import clearZCML
+  >>> clearZCML()
+  >>> import zope.component
+  >>> zope.component.queryAdapter(Content(), IApp, 'test') is None
+  True
+
+  >>> runSnippet('''
+  ...   <adapter
+  ...       factory="zope.component.testfiles.components.Comp"
+  ...       provides="zope.component.testfiles.components.IApp"
+  ...       for="zope.component.testfiles.components.IContent"
+  ...       name="test"
+  ...       />''')
+
+  >>> zope.component.getAdapter(Content(), IApp, 'test').__class__
+  <class 'zope.component.testfiles.components.Comp'>
+
+Adapter factories
+~~~~~~~~~~~~~~~~~
+
+It is possible to supply more than one adapter factory.  In this case,
+during adapter lookup each factory will be called and the return value
+will be given to the next factory.  The return value of the last
+factory is returned as the result of the adapter lookup.  For examle:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <adapter
+  ...       factory="zope.component.testfiles.adapter.A1
+  ...                zope.component.testfiles.adapter.A2
+  ...                zope.component.testfiles.adapter.A3"
+  ...       provides="zope.component.testfiles.components.IApp"
+  ...       for="zope.component.testfiles.components.IContent"
+  ...       />''')
+
+The resulting adapter is an A3, around an A2, around an A1, around the
+adapted object:
+
+  >>> content = Content()
+  >>> a3 = IApp(content)
+  >>> a3.__class__ is A3
+  True
+
+  >>> a2 = a3.context[0]
+  >>> a2.__class__ is A2
+  True
+
+  >>> a1 = a2.context[0]
+  >>> a1.__class__ is A1
+  True
+
+  >>> a1.context[0] is content
+  True
+
+Of course, if no factory is provided at all, we will get an error:
+
+  >>> runSnippet('''
+  ...   <adapter
+  ...       factory=""
+  ...       provides="zope.component.testfiles.components.IApp"
+  ...       for="zope.component.testfiles.components.IContent"
+  ...       />''')
+  Traceback (most recent call last):
+    ...
+  ZopeXMLConfigurationError: File "<string>", line 4.2-8.8
+      ValueError: No factory specified
+
+Declaring ``for`` and ``provides`` in Python
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The <adapter /> directive can figure out from the in-line Python
+declaration (using ``zope.component.adapts()`` or
+``zope.component.adapter()`` as well as ``zope.interface.implements``)
+what the adapter should be registered for and what it provides::
+
+  >>> clearZCML()
+  >>> IApp(Content(), None) is None
+  True
+
+  >>> runSnippet('''
+  ...   <adapter factory="zope.component.testfiles.components.Comp" />''')
+
+  >>> IApp(Content()).__class__
+  <class 'zope.component.testfiles.components.Comp'>
+
+Of course, if the adapter has no ``implements()`` declaration, ZCML
+can't figure out what it provides:
+
+  >>> runSnippet('''
+  ...   <adapter
+  ...       factory="zope.component.testfiles.adapter.A4"
+  ...       for="zope.component.testfiles.components.IContent"
+  ...       />''')
+  Traceback (most recent call last):
+    ...
+  ZopeXMLConfigurationError: File "<string>", line 4.2-7.8
+      TypeError: Missing 'provides' attribute
+
+On the other hand, if the factory implements more than one interface,
+ZCML can't figure out what it should provide either:
+
+  >>> runSnippet('''
+  ...   <adapter
+  ...       factory="zope.component.testfiles.adapter.A5"
+  ...       for="zope.component.testfiles.components.IContent"
+  ...       />''')
+  Traceback (most recent call last):
+    ...
+  ZopeXMLConfigurationError: File "<string>", line 4.2-7.8
+      TypeError: Missing 'provides' attribute
+
+A not so common edge case is registering adapters directly for
+classes, not for interfaces.  For example:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <adapter
+  ...       for="zope.component.testfiles.components.Content"
+  ...       provides="zope.component.testfiles.adapter.I1"
+  ...       factory="zope.component.testfiles.adapter.A1"
+  ...       />''')
+
+  >>> content = Content()
+  >>> a1 = zope.component.getAdapter(content, I1, '')
+  >>> isinstance(a1, A1)
+  True
+
+This time, any object providing ``IContent`` won't work if it's not an
+instance of the ``Content`` class:
+
+  >>> import zope.interface
+  >>> class MyContent:
+  ...     zope.interface.implements(IContent)
+  >>> zope.component.getAdapter(MyContent(), I1, '')  # doctest: +ELLIPSIS
+  Traceback (most recent call last):
+    ...
+  ComponentLookupError: ...
+
+Multi-adapters
+~~~~~~~~~~~~~~
+
+Conventional adapters adapt one object to provide another interface.
+Multi-adapters adapt several objects at once:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <adapter
+  ...       for="zope.component.testfiles.components.IContent
+  ...            zope.component.testfiles.adapter.I1
+  ...            zope.component.testfiles.adapter.I2"
+  ...       provides="zope.component.testfiles.adapter.I3"
+  ...       factory="zope.component.testfiles.adapter.A3"
+  ...       />''')
+
+  >>> content = Content()
+  >>> a1 = A1()
+  >>> a2 = A2()
+  >>> a3 = zope.component.queryMultiAdapter((content, a1, a2), I3)
+  >>> a3.__class__ is A3
+  True
+  >>> a3.context == (content, a1, a2)
+  True
+
+You can even adapt an empty list of objects (we call this a
+null-adapter):
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <adapter
+  ...       for=""
+  ...       provides="zope.component.testfiles.adapter.I3"
+  ...       factory="zope.component.testfiles.adapter.A3"
+  ...       />''')
+
+  >>> a3 = zope.component.queryMultiAdapter((), I3)
+  >>> a3.__class__ is A3
+  True
+  >>> a3.context == ()
+  True
+
+Even with multi-adapters, ZCML can figure out the ``for`` and
+``provides`` parameters from the Python declarations:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <adapter factory="zope.component.testfiles.adapter.A3" />''')
+
+  >>> a3 = zope.component.queryMultiAdapter((content, a1, a2), I3)
+  >>> a3.__class__ is A3
+  True
+  >>> a3.context == (content, a1, a2)
+  True
+
+Chained factories are not supported for multi-adapters, though:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <adapter
+  ...       for="zope.component.testfiles.components.IContent
+  ...            zope.component.testfiles.adapter.I1
+  ...            zope.component.testfiles.adapter.I2"
+  ...       provides="zope.component.testfiles.components.IApp"
+  ...       factory="zope.component.testfiles.adapter.A1
+  ...                zope.component.testfiles.adapter.A2"
+  ...       />''')
+  Traceback (most recent call last):
+    ...
+  ZopeXMLConfigurationError: File "<string>", line 4.2-11.8
+      ValueError: Can't use multiple factories and multiple for
+
+And neither for null-adapters:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <adapter
+  ...       for=""
+  ...       provides="zope.component.testfiles.components.IApp"
+  ...       factory="zope.component.testfiles.adapter.A1
+  ...                zope.component.testfiles.adapter.A2"
+  ...       />''')
+  Traceback (most recent call last):
+    ...
+  ZopeXMLConfigurationError: File "<string>", line 4.2-9.8
+      ValueError: Can't use multiple factories and multiple for
+
+subscriber
+----------
+
+With the <subscriber /> directive you can register subscription
+adapters or event subscribers with the adapter registry.  Consider
+this very typical example of a <subscriber /> directive:
+ 
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <subscriber
+  ...       provides="zope.component.testfiles.adapter.IS"
+  ...       factory="zope.component.testfiles.adapter.A3"
+  ...       for="zope.component.testfiles.components.IContent
+  ...            zope.component.testfiles.adapter.I1"
+  ...       />''')
+
+  >>> content = Content()
+  >>> a1 = A1()
+
+  >>> subscribers = zope.component.subscribers((content, a1), IS)
+  >>> a3 = subscribers[0]
+  >>> a3.__class__ is A3
+  True
+  >>> a3.context == (content, a1)
+  True
+
+Note how ZCML provides some additional information when registering
+components, such as the ZCML filename and line numbers:
+
+  >>> gsm = zope.component.getGlobalSiteManager()
+  >>> doc = [reg.info for reg in gsm.registeredSubscriptionAdapters()
+  ...        if reg.provided is IS][0]
+  >>> print doc
+  File "<string>", line 4.2-9.8
+    Could not read source.
+
+The "fun" behind subscription adapters/subscribers is that when
+several ones are declared for the same for/provides, they are all
+found.  With regular adapters, the most specific one (and in doubt the
+one registered last) wins.  Consider these two subscribers:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <subscriber
+  ...       provides="zope.component.testfiles.adapter.IS"
+  ...       factory="zope.component.testfiles.adapter.A3"
+  ...       for="zope.component.testfiles.components.IContent
+  ...            zope.component.testfiles.adapter.I1"
+  ...       />
+  ...   <subscriber
+  ...       provides="zope.component.testfiles.adapter.IS"
+  ...       factory="zope.component.testfiles.adapter.A2"
+  ...       for="zope.component.testfiles.components.IContent
+  ...            zope.component.testfiles.adapter.I1"
+  ...       />''')
+
+  >>> subscribers = zope.component.subscribers((content, a1), IS)
+  >>> len(subscribers)
+  2
+  >>> sorted([a.__class__.__name__ for a in subscribers])
+  ['A2', 'A3']
+
+Declaring ``for`` and ``provides`` in Python
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Like the <adapter /> directive, the <subscriber /> directive can
+figure out from the in-line Python declaration (using
+``zope.component.adapts()`` or ``zope.component.adapter()``) what the
+subscriber should be registered for:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <subscriber
+  ...       provides="zope.component.testfiles.adapter.IS"
+  ...       factory="zope.component.testfiles.adapter.A3"
+  ...       />''')
+
+  >>> content = Content()
+  >>> a2 = A2()
+  >>> subscribers = zope.component.subscribers((content, a1, a2), IS)
+
+  >>> a3 = subscribers[0]
+  >>> a3.__class__ is A3
+  True
+  >>> a3.context == (content, a1, a2)
+  True
+
+In the same way the directive can figure out what a subscriber
+provides:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <subscriber handler="zope.component.testfiles.adapter.A3" />''')
+
+  >>> sm = zope.component.getSiteManager()
+  >>> a3 = sm.adapters.subscriptions((IContent, I1, I2), None)[0]
+  >>> a3 is A3
+  True
+
+A not so common edge case is declaring subscribers directly for
+classes, not for interfaces.  For example:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <subscriber
+  ...       for="zope.component.testfiles.components.Content"
+  ...       provides="zope.component.testfiles.adapter.I1"
+  ...       factory="zope.component.testfiles.adapter.A1"
+  ...       />''')
+
+  >>> subs = list(zope.component.subscribers((Content(),), I1))
+  >>> isinstance(subs[0], A1)
+  True
+
+This time, any object providing ``IContent`` won't work if it's not an
+instance of the ``Content`` class:
+
+  >>> list(zope.component.subscribers((MyContent(),), I1))
+  []
+
+Event subscriber (handlers)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes, subscribers don't need to be adapters that actually provide
+anything.  It's enough that a callable is called for a certain event.
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <subscriber
+  ...       for="zope.component.testfiles.components.IContent
+  ...            zope.component.testfiles.adapter.I1"
+  ...       handler="zope.component.testfiles.adapter.Handler"
+  ...       />''')
+
+In this case, simply getting the subscribers is enough to invoke them:
+
+  >>> list(zope.component.subscribers((content, a1), None))
+  []
+  >>> content.args == ((a1,),)
+  True
+
+
+utility
+-------
+
+Apart from adapters (and subscription adapters), the Component
+Architecture knows a second kind of component: utilities.  They are
+registered using the <utility /> directive.
+
+Before we register the first test utility, we can verify that utility
+lookup doesn't work yet:
+
+  >>> clearZCML()
+  >>> zope.component.queryUtility(IApp) is None
+  True
+
+Then we register the utility:
+
+  >>> runSnippet('''
+  ...   <utility
+  ...       component="zope.component.testfiles.components.comp"
+  ...       provides="zope.component.testfiles.components.IApp"
+  ...       />''')
+  >>> zope.component.getUtility(IApp) is comp
+  True
+
+Like adapters, utilities can also have names.  There can be more than
+one utility registered for a certain interface, as long as they each
+have a different name.
+
+First, we make sure that there's no utility yet:
+
+  >>> clearZCML()
+  >>> zope.component.queryUtility(IApp, 'test') is None
+  True
+
+Then we register it:
+
+  >>> runSnippet('''
+  ...   <utility
+  ...       component="zope.component.testfiles.components.comp"
+  ...       provides="zope.component.testfiles.components.IApp"
+  ...       name="test"
+  ...       />''')
+  >>> zope.component.getUtility(IApp, 'test') is comp
+  True
+
+Utilities can also be registered from a factory.  In this case, the
+ZCML handler calls the factory (without any arguments) and registers
+the returned value as a utility.  Typically, you'd pass a class for
+the factory:
+
+  >>> clearZCML()
+  >>> zope.component.queryUtility(IApp) is None
+  True
+
+  >>> runSnippet('''
+  ...   <utility
+  ...       factory="zope.component.testfiles.components.Comp"
+  ...       provides="zope.component.testfiles.components.IApp"
+  ...       />''')
+  >>> zope.component.getUtility(IApp).__class__ is Comp
+  True
+
+Declaring ``provides`` in Python
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Like other directives, <utility /> can also figure out which interface
+a utility provides from the Python declaration:
+
+  >>> clearZCML()
+  >>> zope.component.queryUtility(IApp) is None
+  True
+
+  >>> runSnippet('''
+  ...   <utility component="zope.component.testfiles.components.comp" />''')
+  >>> zope.component.getUtility(IApp) is comp
+  True
+
+It won't work if the component that is to be registered doesn't
+provide anything:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <utility component="zope.component.testfiles.adapter.a4" />''')
+  Traceback (most recent call last):
+    ...
+  ZopeXMLConfigurationError: File "<string>", line 4.2-4.61
+      TypeError: Missing 'provides' attribute
+
+Or if more than one interface is provided (then the ZCML directive
+handler doesn't know under which the utility should be registered):
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <utility component="zope.component.testfiles.adapter.a5" />''')
+  Traceback (most recent call last):
+    ...
+  ZopeXMLConfigurationError: File "<string>", line 4.2-4.61
+      TypeError: Missing 'provides' attribute
+
+We can repeat the same drill for utility factories:
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <utility factory="zope.component.testfiles.components.Comp" />''')
+  >>> zope.component.getUtility(IApp).__class__ is Comp
+  True
+
+  >>> runSnippet('''
+  ...   <utility factory="zope.component.testfiles.adapter.A4" />''')
+  Traceback (most recent call last):
+    ...
+  ZopeXMLConfigurationError: File "<string>", line 4.2-4.59
+      TypeError: Missing 'provides' attribute
+
+  >>> clearZCML()
+  >>> runSnippet('''
+  ...   <utility factory="zope.component.testfiles.adapter.A5" />''')
+  Traceback (most recent call last):
+    ...
+  ZopeXMLConfigurationError: File "<string>", line 4.2-4.59
+      TypeError: Missing 'provides' attribute
+
+interface
+---------
+
+The <interface /> directive lets us register an interface.  Interfaces
+are registered as named utilities.  We therefore needn't go though all
+the lookup details again, it is sufficient to see whether the
+directive handler emits the right actions.
+
+First we provide a stub configuration context:
+
+  >>> import re, pprint
+  >>> atre = re.compile(' at [0-9a-fA-Fx]+')
+  >>> class Context(object):
+  ...    actions = ()
+  ...    def action(self, discriminator, callable, args):
+  ...        self.actions += ((discriminator, callable, args), )
+  ...    def __repr__(self):
+  ...        stream = StringIO()
+  ...        pprinter = pprint.PrettyPrinter(stream=stream, width=60)
+  ...        pprinter.pprint(self.actions)
+  ...        r = stream.getvalue()
+  ...        return (''.join(atre.split(r))).strip()
+  >>> context = Context()
+
+Then we provide a test interface that we'd like to register:
+
+  >>> from zope.interface import Interface
+  >>> class I(Interface):
+  ...     pass
+
+It doesn't yet provide ``ITestType``:
+
+  >>> from zope.component.tests import ITestType
+  >>> ITestType.providedBy(I)
+  False
+
+However, after calling the directive handler...
+
+  >>> from zope.component.zcml import interface
+  >>> interface(context, I, ITestType)
+  >>> context
+  ((None,
+    <function provideInterface>,
+    ('',
+     <InterfaceClass __builtin__.I>,
+     <InterfaceClass zope.component.tests.ITestType>)),)
+
+...it does provide ``ITestType``:
+
+  >>> from zope.interface.interfaces import IInterface
+  >>> ITestType.extends(IInterface)
+  True
+  >>> IInterface.providedBy(I)
+  True



More information about the checkins mailing list