[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