[Checkins] SVN: zope.component/trunk/ merged the conditional-zope.security branch to the trunk
Fabio Tranchitella
kobold at kobold.it
Sun Nov 8 06:05:28 EST 2009
Log message for revision 105528:
merged the conditional-zope.security branch to the trunk
Changed:
U zope.component/trunk/CHANGES.txt
U zope.component/trunk/setup.py
A zope.component/trunk/src/zope/component/hooks.py
A zope.component/trunk/src/zope/component/hooks.txt
U zope.component/trunk/src/zope/component/interfaces.py
A zope.component/trunk/src/zope/component/security.py
U zope.component/trunk/src/zope/component/tests.py
U zope.component/trunk/src/zope/component/zcml.py
A zope.component/trunk/src/zope/component/zcml_conditional.txt
-=-
Modified: zope.component/trunk/CHANGES.txt
===================================================================
--- zope.component/trunk/CHANGES.txt 2009-11-08 11:03:47 UTC (rev 105527)
+++ zope.component/trunk/CHANGES.txt 2009-11-08 11:05:28 UTC (rev 105528)
@@ -4,8 +4,17 @@
3.8.0 (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.
+- Moved the IPossibleSite and ISite interfaces here from zope.location as they
+ are dealing with zope.component's concept of a site, but not with location.
+
+- Moved the zope.site.hooks functionality to zope.component.hooks as it isn't
+ actually dealing with zope.site's concept of a site.
+
3.7.1 (2009-07-24)
==================
Modified: zope.component/trunk/setup.py
===================================================================
--- zope.component/trunk/setup.py 2009-11-08 11:03:47 UTC (rev 105527)
+++ zope.component/trunk/setup.py 2009-11-08 11:05:28 UTC (rev 105528)
@@ -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'],
),
Added: zope.component/trunk/src/zope/component/hooks.py
===================================================================
--- zope.component/trunk/src/zope/component/hooks.py (rev 0)
+++ zope.component/trunk/src/zope/component/hooks.py 2009-11-08 11:05:28 UTC (rev 105528)
@@ -0,0 +1,127 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation 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.
+#
+##############################################################################
+"""Hooks for getting and setting a site in the thread global namespace.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import threading
+import zope.component
+
+try:
+ import zope.security.proxy
+except ImportError:
+ SECURITY_SUPPORT = False
+else:
+ SECURITY_SUPPORT = True
+
+
+class read_property(object):
+ def __init__(self, func):
+ self.func = func
+
+ def __get__(self, inst, cls):
+ if inst is None:
+ return self
+
+ return self.func(inst)
+
+class SiteInfo(threading.local):
+ site = None
+ sm = zope.component.getGlobalSiteManager()
+
+ def adapter_hook(self):
+ adapter_hook = self.sm.adapters.adapter_hook
+ self.adapter_hook = adapter_hook
+ return adapter_hook
+
+ adapter_hook = read_property(adapter_hook)
+
+siteinfo = SiteInfo()
+
+def setSite(site=None):
+ if site is None:
+ sm = zope.component.getGlobalSiteManager()
+ else:
+
+ # We remove the security proxy because there's no way for
+ # untrusted code to get at it without it being proxied again.
+
+ # We should really look look at this again though, especially
+ # once site managers do less. There's probably no good reason why
+ # they can't be proxied. Well, except maybe for performance.
+
+ if SECURITY_SUPPORT:
+ site = zope.security.proxy.removeSecurityProxy(site)
+ # The getSiteManager method is defined by IPossibleSite.
+ sm = site.getSiteManager()
+
+ siteinfo.site = site
+ siteinfo.sm = sm
+ try:
+ del siteinfo.adapter_hook
+ except AttributeError:
+ pass
+
+def getSite():
+ return siteinfo.site
+
+
+def getSiteManager(context=None):
+ """A special hook for getting the site manager.
+
+ Here we take the currently set site into account to find the appropriate
+ site manager.
+ """
+ if context is None:
+ return siteinfo.sm
+
+ # We remove the security proxy because there's no way for
+ # untrusted code to get at it without it being proxied again.
+
+ # We should really look look at this again though, especially
+ # once site managers do less. There's probably no good reason why
+ # they can't be proxied. Well, except maybe for performance.
+ sm = zope.component.interfaces.IComponentLookup(
+ context, zope.component.getGlobalSiteManager())
+ if SECURITY_SUPPORT:
+ sm = zope.security.proxy.removeSecurityProxy(sm)
+ return sm
+
+
+def adapter_hook(interface, object, name='', default=None):
+ try:
+ return siteinfo.adapter_hook(interface, object, name, default)
+ except zope.component.interfaces.ComponentLookupError:
+ return default
+
+
+def setHooks():
+ zope.component.adapter_hook.sethook(adapter_hook)
+ zope.component.getSiteManager.sethook(getSiteManager)
+
+def resetHooks():
+ # Reset hookable functions to original implementation.
+ zope.component.adapter_hook.reset()
+ zope.component.getSiteManager.reset()
+
+# Clear the site thread global
+clearSite = setSite
+try:
+ from zope.testing.cleanup import addCleanUp
+except ImportError:
+ pass
+else:
+ addCleanUp(resetHooks)
Added: zope.component/trunk/src/zope/component/hooks.txt
===================================================================
--- zope.component/trunk/src/zope/component/hooks.txt (rev 0)
+++ zope.component/trunk/src/zope/component/hooks.txt 2009-11-08 11:05:28 UTC (rev 105528)
@@ -0,0 +1,65 @@
+==============================
+The current component registry
+==============================
+
+There can be any number of component registries in an application. One of them
+is the global component registry, and there is also the concept of a currently
+used component registry. Component registries other than the global one are
+associated with objects called sites. The ``zope.component.hooks`` module
+provides an API to set and access the current site as well as manipulate the
+adapter hook associated with it.
+
+As long as we haven't set a site, none is being considered current:
+
+>>> from zope.component.hooks import getSite
+>>> print getSite()
+None
+
+We can also ask for the current component registry (aka site manager
+historically); it will return the global one if no current site is set:
+
+>>> from zope.component.hooks import getSiteManager
+>>> getSiteManager()
+<BaseGlobalComponents base>
+
+Let's set a site now. A site has to be an object that provides the
+``getSiteManager`` method, which is specified by
+``zope.component.interfaces.IPossibleSite``:
+
+>>> from zope.component.registry import Components
+>>> class Site(object):
+... def __init__(self):
+... self.registry = Components('components')
+... def getSiteManager(self):
+... return self.registry
+
+>>> from zope.component.hooks import setSite
+>>> site1 = Site()
+>>> setSite(site1)
+
+After this, the newly set site is considered the currently active one:
+
+>>> getSite() is site1
+True
+>>> getSiteManager() is site1.registry
+True
+
+If we set another site, that one will be considered current:
+
+>>> site2 = Site()
+>>> site2.registry is not site1.registry
+True
+>>> setSite(site2)
+
+>>> getSite() is site2
+True
+>>> getSiteManager() is site2.registry
+True
+
+Finally we can unset the site and the global component registry is used again:
+
+>>> setSite()
+>>> print getSite()
+None
+>>> getSiteManager()
+<BaseGlobalComponents base>
Modified: zope.component/trunk/src/zope/component/interfaces.py
===================================================================
--- zope.component/trunk/src/zope/component/interfaces.py 2009-11-08 11:03:47 UTC (rev 105527)
+++ zope.component/trunk/src/zope/component/interfaces.py 2009-11-08 11:05:28 UTC (rev 105528)
@@ -923,3 +923,22 @@
class IComponents(IComponentLookup, IComponentRegistry):
"""Component registration and access
"""
+
+
+class IPossibleSite(Interface):
+ """An object that could be a site.
+ """
+
+ def setSiteManager(sitemanager):
+ """Sets the site manager for this object.
+ """
+
+ def getSiteManager():
+ """Returns the site manager contained in this object.
+
+ If there isn't a site manager, raise a component lookup.
+ """
+
+
+class ISite(IPossibleSite):
+ """Marker interface to indicate that we have a site"""
Added: zope.component/trunk/src/zope/component/security.py
===================================================================
--- zope.component/trunk/src/zope/component/security.py (rev 0)
+++ zope.component/trunk/src/zope/component/security.py 2009-11-08 11:05:28 UTC (rev 105528)
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation 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.
+#
+##############################################################################
+"""zope.security support for the configuration handlers
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+from zope.interface import providedBy
+from zope.proxy import ProxyBase, getProxiedObject
+from zope.security.adapter import LocatingTrustedAdapterFactory, \
+ LocatingUntrustedAdapterFactory, TrustedAdapterFactory
+from zope.security.checker import Checker, CheckerPublic, InterfaceChecker
+from zope.security.proxy import Proxy
+
+
+PublicPermission = 'zope.Public'
+
+class PermissionProxy(ProxyBase):
+
+ __slots__ = ('__Security_checker__', )
+
+ def __providedBy__(self):
+ return providedBy(getProxiedObject(self))
+ __providedBy__ = property(__providedBy__)
+
+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
+
+def proxify(ob, checker=None, provides=None, permission=None):
+ """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.
+ """
+
+ if checker is None:
+ if provides is None or permission is None:
+ raise ValueError, 'Required arguments: checker or both provides and permissions'
+ if permission == PublicPermission:
+ permission = CheckerPublic
+ checker = InterfaceChecker(provides, permission)
+ ob = PermissionProxy(ob)
+ ob.__Security_checker__ = checker
+ return ob
+
+def protectedFactory(original_factory, provides, permission):
+ if permission == PublicPermission:
+ permission = CheckerPublic
+ checker = InterfaceChecker(provides, permission)
+ # 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 securityAdapterFactory(factory, permission, locate, trusted):
+ if locate or (permission is not None and permission != PublicPermission):
+ if trusted:
+ return LocatingTrustedAdapterFactory(factory)
+ else:
+ return LocatingUntrustedAdapterFactory(factory)
+ elif trusted:
+ return TrustedAdapterFactory(factory)
+ else:
+ return factory
Modified: zope.component/trunk/src/zope/component/tests.py
===================================================================
--- zope.component/trunk/src/zope/component/tests.py 2009-11-08 11:03:47 UTC (rev 105527)
+++ zope.component/trunk/src/zope/component/tests.py 2009-11-08 11:05:28 UTC (rev 105528)
@@ -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,12 @@
r'exceptions.\1Error:'),
])
+ zcml_conditional = doctest.DocFileSuite('zcml_conditional.txt', checker=checker)
+ zcml_conditional.layer = ConditionalSecurityLayer()
+
+ hooks_conditional = doctest.DocFileSuite('hooks.txt', checker=checker)
+ hooks_conditional.layer = ConditionalSecurityLayer()
+
return unittest.TestSuite((
doctest.DocTestSuite(setUp=setUp, tearDown=tearDown),
unittest.makeSuite(HookableTests),
@@ -1670,10 +1700,14 @@
doctest.DocFileSuite('registry.txt', checker=checker,
setUp=setUpRegistryTests,
tearDown=tearDownRegistryTests),
+ doctest.DocFileSuite('hooks.txt',checker=checker,
+ setUp=setUp, tearDown=tearDown),
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,
+ hooks_conditional,
unittest.makeSuite(StandaloneTests),
unittest.makeSuite(ResourceViewTests),
))
Modified: zope.component/trunk/src/zope/component/zcml.py
===================================================================
--- zope.component/trunk/src/zope/component/zcml.py 2009-11-08 11:03:47 UTC (rev 105527)
+++ zope.component/trunk/src/zope/component/zcml.py 2009-11-08 11:05:28 UTC (rev 105528)
@@ -19,22 +19,30 @@
import warnings
import zope.component
+import zope.configuration.fields
import zope.interface
import zope.schema
-import zope.configuration.fields
-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.configuration.exceptions import ConfigurationError
from zope.i18nmessageid import MessageFactory
+
+try:
+ from zope.component.security import _checker, proxify, protectedFactory, \
+ securityAdapterFactory
+ from zope.security.zcml import Permission
+except ImportError:
+ SECURITY_SUPPORT = False
+ from zope.schema import TextLine as Permission
+else:
+ SECURITY_SUPPORT = True
+
_ = MessageFactory('zope')
-PublicPermission = 'zope.Public'
+def check_security_support():
+ if not SECURITY_SUPPORT:
+ raise ConfigurationError("security proxied components are not "
+ "supported because zope.security is not available")
def handler(methodName, *args, **kwargs):
method = getattr(zope.component.getGlobalSiteManager(), methodName)
@@ -51,7 +59,7 @@
required=False,
)
- permission = zope.security.zcml.Permission(
+ permission = Permission(
title=_("Permission"),
description=_("Permission required to use this component."),
required=False,
@@ -96,7 +104,7 @@
),
)
- permission = zope.security.zcml.Permission(
+ permission = Permission(
title=_("Permission"),
description=_("This adapter is only available, if the principal"
" has this permission."),
@@ -147,20 +155,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,20 +189,13 @@
factory = _rolledUpFactory(factories)
if permission is not None:
- if permission == PublicPermission:
- permission = CheckerPublic
- checker = InterfaceChecker(provides, permission)
- factory = _protectedFactory(factory, checker)
+ check_security_support()
+ factory = protectedFactory(factory, provides, permission)
# invoke custom adapter factories
- if locate or (permission is not None and permission is not CheckerPublic):
- if trusted:
- factory = LocatingTrustedAdapterFactory(factory)
- else:
- factory = LocatingUntrustedAdapterFactory(factory)
- else:
- if trusted:
- factory = TrustedAdapterFactory(factory)
+ if locate or permission is not None or trusted:
+ check_security_support()
+ factory = securityAdapterFactory(factory, permission, locate, trusted)
_context.action(
discriminator = ('adapter', for_, provides, name),
@@ -263,7 +250,7 @@
),
)
- permission = zope.security.zcml.Permission(
+ permission = Permission(
title=_("Permission"),
description=_("This subscriber is only available, if the"
" principal has this permission."),
@@ -319,22 +306,15 @@
"determine what the factory (or handler) adapts.")
if permission is not None:
- if permission == PublicPermission:
- permission = CheckerPublic
- checker = InterfaceChecker(provides, permission)
- factory = _protectedFactory(factory, checker)
+ check_security_support()
+ factory = protectedFactory(factory, provides, permission)
for_ = tuple(for_)
# invoke custom adapter factories
- if locate or (permission is not None and permission is not CheckerPublic):
- if trusted:
- factory = LocatingTrustedAdapterFactory(factory)
- else:
- factory = LocatingUntrustedAdapterFactory(factory)
- else:
- if trusted:
- factory = TrustedAdapterFactory(factory)
+ if locate or permission is not None or trusted:
+ check_security_support()
+ factory = securityAdapterFactory(factory, permission, locate, trusted)
if handler is not None:
_context.action(
@@ -383,24 +363,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,12 +379,9 @@
raise TypeError("Missing 'provides' attribute")
if permission is not None:
- if permission == PublicPermission:
- permission = CheckerPublic
- checker = InterfaceChecker(provides, permission)
+ check_security_support()
+ component = proxify(component, provides=provides, permission=permission)
- component = proxify(component, checker)
-
_context.action(
discriminator = ('utility', provides, name),
callable = handler,
@@ -475,7 +434,7 @@
),
)
- permission = zope.security.zcml.Permission(
+ permission = Permission(
title=_("Permission"),
description=_("The permission needed to use the view."),
required=False,
@@ -572,7 +531,8 @@
if not factory:
raise ConfigurationError("No view factory specified.")
- if permission:
+ if permission is not None:
+ check_security_support()
checker = _checker(_context, permission,
allowed_interface, allowed_attributes)
@@ -684,7 +644,9 @@
"allowed_attributes"
)
- if permission:
+ if permission is not None:
+ check_security_support()
+
checker = _checker(_context, permission,
allowed_interface, allowed_attributes)
@@ -716,22 +678,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/trunk/src/zope/component/zcml_conditional.txt
===================================================================
--- zope.component/trunk/src/zope/component/zcml_conditional.txt (rev 0)
+++ zope.component/trunk/src/zope/component/zcml_conditional.txt 2009-11-08 11:05:28 UTC (rev 105528)
@@ -0,0 +1,612 @@
+ZCML directives without zope.security support
+=============================================
+
+This tests run without zope.security available:
+
+ >>> from zope.component.zcml import check_security_support
+ >>> check_security_support()
+ Traceback (most recent call last):
+ ...
+ ConfigurationError: security proxied components are not supported because zope.security is not available
+
+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