[Zope3-checkins] SVN: Zope3/trunk/src/zope/component/ - Added an
API for declaring interfaces adapted by classes
Jim Fulton
jim at zope.com
Thu Dec 16 16:48:59 EST 2004
Log message for revision 28641:
- Added an API for declaring interfaces adapted by classes
- Added a simplified Python API for registering global utilities
and adapters.
Changed:
A Zope3/trunk/src/zope/component/README.txt
U Zope3/trunk/src/zope/component/__init__.py
U Zope3/trunk/src/zope/component/interfaces.py
U Zope3/trunk/src/zope/component/tests/placelesssetup.py
U Zope3/trunk/src/zope/component/tests/test_api.py
-=-
Added: Zope3/trunk/src/zope/component/README.txt
===================================================================
--- Zope3/trunk/src/zope/component/README.txt 2004-12-16 21:48:54 UTC (rev 28640)
+++ Zope3/trunk/src/zope/component/README.txt 2004-12-16 21:48:58 UTC (rev 28641)
@@ -0,0 +1,188 @@
+Zope Component Architecture
+===========================
+
+This package, together with `zope.interface`, provides facilities for
+defining, registering and looking up components. There are two basic
+kinds of components, adapters and utilities.
+
+Utilities
+---------
+
+Utilities are just components that provide an interface and that are
+looked up by an interface and a name. Let's look at a trivial utility
+definition:
+
+ >>> import zope.interface
+
+ >>> class IGreeter(zope.interface.Interface):
+ ... def greet():
+ ... "say hello"
+
+ >>> class Greeter:
+ ... zope.interface.implements(IGreeter)
+ ...
+ ... def __init__(self, other="world"):
+ ... self.other = other
+ ...
+ ... def greet(self):
+ ... print "Hello", self.other
+
+We can register an instance this class using `provideUtility` [1]_:
+
+ >>> import zope.component
+ >>> greet = Greeter('bob')
+ >>> zope.component.provideUtility(greet, IGreeter, 'robert')
+
+In this example we registered the utility as providing the `IGreeter`
+interface with a name of 'bob'. We can look the interface up with
+either `queryUtility` or `getUtility`:
+
+ >>> zope.component.queryUtility(IGreeter, 'robert').greet()
+ Hello bob
+
+ >>> zope.component.getUtility(IGreeter, 'robert').greet()
+ Hello bob
+
+`queryUtility` and `getUtility` differ in how failed lookups are
+handled:
+
+ >>> zope.component.queryUtility(IGreeter, 'ted')
+ >>> zope.component.queryUtility(IGreeter, 'ted', 42)
+ 42
+ >>> zope.component.getUtility(IGreeter, 'ted')
+ ... # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ ComponentLookupError: (<InterfaceClass ...IGreeter>, 'ted')
+
+If a component provides only one interface, as in the example above,
+then we can ommit the provided interface from the call to
+`provideUtility`:
+
+ >>> ted = Greeter('ted')
+ >>> zope.component.provideUtility(ted, name='ted')
+ >>> zope.component.queryUtility(IGreeter, 'ted').greet()
+ Hello ted
+
+The name defaults to an empty string:
+
+ >>> world = Greeter()
+ >>> zope.component.provideUtility(world)
+ >>> zope.component.queryUtility(IGreeter).greet()
+ Hello world
+
+Adapters
+--------
+
+Adapters are components that are computed from other components to
+adapt them to some interface. Because they are computed from other
+objects, they are provided as factories, usually classes. Here, we'll
+create a greeter for persons, so we can provide personalized greetings
+for different people:
+
+ >>> class IPerson(zope.interface.Interface):
+ ... name = zope.interface.Attribute("Name")
+
+ >>> class PersonGreeter:
+ ...
+ ... zope.component.adapts(IPerson)
+ ... zope.interface.implements(IGreeter)
+ ...
+ ... def __init__(self, person):
+ ... self.person = person
+ ...
+ ... def greet(self):
+ ... print "Hello", self.person.name
+
+The class defines a constructor that takes an argument for every
+object adapted.
+
+We use `zope.component.adapts` to declare what we adapt. If we
+declare the interfaces adapted and if we provide only one interface,
+as in the example above, then we can provide the adapter very simply [1]_:
+
+ >>> zope.component.provideAdapter(PersonGreeter)
+
+For adapters that adapt a single interface to a single interface
+without a name, we can get the adapter by simply calling the
+interface:
+
+ >>> class Person:
+ ... zope.interface.implements(IPerson)
+ ...
+ ... def __init__(self, name):
+ ... self.name = name
+
+ >>> IGreeter(Person("Sally")).greet()
+ Hello Sally
+
+We can also provide arguments to be very specific about what
+how to register the adapter.
+
+ >>> class BobPersonGreeter(PersonGreeter):
+ ... name = 'Bob'
+ ... def greet(self):
+ ... print "Hello", self.person.name, "my name is", self.name
+
+ >>> zope.component.provideAdapter(
+ ... BobPersonGreeter, [IPerson], IGreeter, 'bob')
+
+The arguments can also be provided as keyword arguments:
+
+ >>> class TedPersonGreeter(BobPersonGreeter):
+ ... name = "Ted"
+
+ >>> zope.component.provideAdapter(
+ ... factory=TedPersonGreeter, adapts=[IPerson],
+ ... provides=IGreeter, name='ted')
+
+For named adapters, use `queryAdapter`, or `getAdapter`:
+
+ >>> zope.component.queryAdapter(Person("Sally"), IGreeter, 'bob').greet()
+ Hello Sally my name is Bob
+
+ >>> zope.component.getAdapter(Person("Sally"), IGreeter, 'ted').greet()
+ Hello Sally my name is Ted
+
+If an adapter can't be found, `queryAdapter` returns a default value
+and `getAdapter` raises an error:
+
+ >>> zope.component.queryAdapter(Person("Sally"), IGreeter, 'frank')
+ >>> zope.component.queryAdapter(Person("Sally"), IGreeter, 'frank', 42)
+ 42
+ >>> zope.component.getAdapter(Person("Sally"), IGreeter, 'frank')
+ ... # doctest: +ELLIPSIS
+ Traceback (most recent call last):
+ ...
+ ComponentLookupError: (...Person...>, <...IGreeter>)
+
+Adapters can adapt multiple objects:
+
+ >>> class TwoPersonGreeter:
+ ...
+ ... zope.component.adapts(IPerson, IPerson)
+ ... zope.interface.implements(IGreeter)
+ ...
+ ... def __init__(self, person, greeter):
+ ... self.person = person
+ ... self.greeter = greeter
+ ...
+ ... def greet(self):
+ ... print "Hello", self.person.name
+ ... print "my name is", self.greeter.name
+
+ >>> zope.component.provideAdapter(TwoPersonGreeter)
+
+To look up a multi-adapter, use either `queryMultiAdapter` or
+`getMultiAdapter`:
+
+ >>> zope.component.queryMultiAdapter((Person("Sally"), Person("Bob")),
+ ... IGreeter).greet()
+ Hello Sally
+ my name is Bob
+
+
+.. [1] CAUTION: This API should only be used from test or
+ application-setup code. This api shouldn't be used by regular
+ library modules, as component registration is a configuration
+ activity.
Modified: Zope3/trunk/src/zope/component/__init__.py
===================================================================
--- Zope3/trunk/src/zope/component/__init__.py 2004-12-16 21:48:54 UTC (rev 28640)
+++ Zope3/trunk/src/zope/component/__init__.py 2004-12-16 21:48:58 UTC (rev 28641)
@@ -17,9 +17,11 @@
"""
import sys
import warnings
+import zope.interface
from zope.interface import moduleProvides, Interface, providedBy
from zope.interface.interfaces import IInterface
from zope.component.interfaces import IComponentArchitecture, IFactory
+from zope.component.interfaces import IComponentRegistrationConvenience
from zope.component.interfaces import IServiceService
from zope.component.interfaces import IDefaultViewName
from zope.component.exceptions import ComponentLookupError
@@ -33,7 +35,7 @@
def hookable(ob):
return ob
-moduleProvides(IComponentArchitecture)
+moduleProvides(IComponentArchitecture, IComponentRegistrationConvenience)
__all__ = tuple(IComponentArchitecture)
def warningLevel():
@@ -280,3 +282,60 @@
def queryResource(name, request, default=None, providing=Interface,
context=None):
return queryAdapter(request, providing, name, default, context)
+
+
+# The following apis provide registration support for Python code:
+
+class _adapts_descr(object):
+
+ def __init__(self, interfaces):
+ self.interfaces = interfaces
+
+ def __get__(self, inst, cls):
+ if inst is None:
+ return self.interfaces
+ raise AttributeError, '__component_adapts__'
+
+def adapts(*interfaces):
+ frame = sys._getframe(1)
+ locals = frame.f_locals
+
+ # Try to make sure we were called from a class def. In 2.2.0 we can't
+ # check for __module__ since it doesn't seem to be added to the locals
+ # until later on.
+ if (locals is frame.f_globals) or (
+ ('__module__' not in locals) and sys.version_info[:3] > (2, 2, 0)):
+ raise TypeError(name+" can be used only from a class definition.")
+
+ if '__component_adapts__' in locals:
+ raise TypeError("adapts can be used only once in a class definition.")
+
+ locals['__component_adapts__'] = _adapts_descr(interfaces)
+
+def provideUtility(component, provides=None, name=u''):
+ if provides is None:
+ provides = list(providedBy(component))
+ if len(provides) == 1:
+ provides = provides[0]
+ else:
+ raise TypeError("Missing 'provides' argument")
+
+ getGlobalService('Utilities').provideUtility(provides, component, name)
+
+def provideAdapter(factory, adapts=None, provides=None, name=''):
+ if provides is None:
+ provides = list(zope.interface.implementedBy(factory))
+ if len(provides) == 1:
+ provides = provides[0]
+ else:
+ raise TypeError("Missing 'provides' argument")
+
+ if adapts is None:
+ try:
+ adapts = factory.__component_adapts__
+ except AttributeError:
+ raise TypeError("Missing 'adapts' argument")
+
+ getGlobalService('Adapters').register(adapts, provides, name, factory)
+
+
Modified: Zope3/trunk/src/zope/component/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/component/interfaces.py 2004-12-16 21:48:54 UTC (rev 28640)
+++ Zope3/trunk/src/zope/component/interfaces.py 2004-12-16 21:48:58 UTC (rev 28641)
@@ -378,7 +378,60 @@
If the component can't be found, the default is returned.
"""
+ def adapts(*interfaces):
+ """Declare that a class adapts the given interfaces.
+ This function can only be used in a class definition.
+
+ (TODO, allow classes to be passed as well as interfaces.)
+ """
+
+class IComponentRegistrationConvenience(Interface):
+ """API for registering components.
+
+ CAUTION: This API should only be used from test or
+ application-setup code. This api shouldn't be used by regular
+ library modules, as component registration is a configuration
+ activity.
+ """
+
+ def provideUtility(component, provides=None, name=u''):
+ """Register a utility globally
+
+ A utility is registered to provide an interface with a
+ name. If a component provides only one interface, then the
+ provides argument can be omitted and the provided interface
+ will be used. (In this case, provides argument can still be
+ provided to provide a less specific interface.)
+
+ CAUTION: This API should only be used from test or
+ application-setup code. This api shouldn't be used by regular
+ library modules, as component registration is a configuration
+ activity.
+
+ """
+
+ def provideAdapter(factory, adapts=None, provides=None, name=u''):
+ """Register an adapter globally
+
+ An adapter is registered to provide an interface with a name
+ for some number of object types. If a factory implements only
+ one interface, then the provides argument can be omitted and
+ the provided interface will be used. (In this case, a provides
+ argument can still be provided to provide a less specific
+ interface.)
+
+ If the factory has an adapts declaration, then the adapts
+ argument can be omitted and the declaration will be used. (An
+ adapts argument can be provided to override the declaration.)
+
+ CAUTION: This API should only be used from test or
+ application-setup code. This api shouldn't be used by regular
+ library modules, as component registration is a configuration
+ activity.
+
+ """
+
class IRegistry(Interface):
"""Object that supports component registry
"""
Modified: Zope3/trunk/src/zope/component/tests/placelesssetup.py
===================================================================
--- Zope3/trunk/src/zope/component/tests/placelesssetup.py 2004-12-16 21:48:54 UTC (rev 28640)
+++ Zope3/trunk/src/zope/component/tests/placelesssetup.py 2004-12-16 21:48:58 UTC (rev 28641)
@@ -19,26 +19,32 @@
from zope.component import getGlobalServices
from zope.component.servicenames import Adapters, Utilities
+def setUp(test=None):
+ CleanUp().setUp()
+ sm = getGlobalServices()
+ defineService = sm.defineService
+ provideService = sm.provideService
+
+ # utility service
+ from zope.component.interfaces import IUtilityService
+ defineService(Utilities, IUtilityService)
+ from zope.component.utility import GlobalUtilityService
+ provideService(Utilities, GlobalUtilityService())
+
+ # adapter service
+ from zope.component.interfaces import IAdapterService
+ defineService(Adapters, IAdapterService)
+ from zope.component.adapter import GlobalAdapterService
+ provideService(Adapters, GlobalAdapterService())
+
+def tearDown(test=None):
+ CleanUp().cleanUp()
+
# A mix-in class inheriting from CleanUp that also connects the CA services
class PlacelessSetup(CleanUp):
def setUp(self):
- CleanUp.setUp(self)
- sm = getGlobalServices()
- defineService = sm.defineService
- provideService = sm.provideService
-
- # utility service
- from zope.component.interfaces import IUtilityService
- defineService(Utilities, IUtilityService)
- from zope.component.utility import GlobalUtilityService
- provideService(Utilities, GlobalUtilityService())
-
- # adapter service
- from zope.component.interfaces import IAdapterService
- defineService(Adapters, IAdapterService)
- from zope.component.adapter import GlobalAdapterService
- provideService(Adapters, GlobalAdapterService())
-
+ setUp()
+
def tearDown(self):
- CleanUp.tearDown(self)
+ tearDown()
Modified: Zope3/trunk/src/zope/component/tests/test_api.py
===================================================================
--- Zope3/trunk/src/zope/component/tests/test_api.py 2004-12-16 21:48:54 UTC (rev 28640)
+++ Zope3/trunk/src/zope/component/tests/test_api.py 2004-12-16 21:48:58 UTC (rev 28641)
@@ -28,7 +28,7 @@
from zope.component.service import serviceManager
from zope.component.exceptions import ComponentLookupError
from zope.component.servicenames import Adapters
-from zope.component.tests.placelesssetup import PlacelessSetup
+from zope.component.tests import placelesssetup
from zope.component.tests.request import Request
from zope.component.interfaces import IComponentArchitecture, IServiceService
from zope.component.interfaces import IDefaultViewName
@@ -94,7 +94,7 @@
return self.serviceservice
-class Test(PlacelessSetup, unittest.TestCase):
+class Test(placelesssetup.PlacelessSetup, unittest.TestCase):
def testInterfaces(self):
import zope.component
@@ -539,7 +539,26 @@
getDefaultViewName,
ob, Request(I1))
+def testNo__component_adapts__leakage():
+ """
+ We want to make sure that an adapts call in a class definition
+ doesn't affect instances.
+ >>> import zope.component
+ >>> class C:
+ ... zope.component.adapts()
+
+ >>> C.__component_adapts__
+ ()
+ >>> C().__component_adapts__
+ Traceback (most recent call last):
+ ...
+ AttributeError: __component_adapts__
+
+ """
+
+
+
class TestNoSetup(unittest.TestCase):
def testNotBrokenWhenNoService(self):
@@ -549,9 +568,15 @@
pass
def test_suite():
+ from zope.testing import doctest
return unittest.TestSuite((
unittest.makeSuite(Test),
unittest.makeSuite(TestNoSetup),
+ doctest.DocTestSuite(),
+ doctest.DocFileSuite('../README.txt',
+ setUp=placelesssetup.setUp,
+ tearDown=placelesssetup.tearDown,
+ ),
))
if __name__ == "__main__":
More information about the Zope3-Checkins
mailing list