[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