[Zope3-checkins] SVN: Zope3/trunk/src/zope/interface/ Added a less comprehensive, but much simpler, example on how to use the

Stephan Richter srichter at cosmos.phy.tufts.edu
Wed Sep 1 10:06:28 EDT 2004


Log message for revision 27381:
  Added a less comprehensive, but much simpler, example on how to use the 
  adapter registry and adapter hooks.
  


Changed:
  A   Zope3/trunk/src/zope/interface/human.txt
  U   Zope3/trunk/src/zope/interface/tests/test_adapter.py


-=-
Added: Zope3/trunk/src/zope/interface/human.txt
===================================================================
--- Zope3/trunk/src/zope/interface/human.txt	2004-09-01 14:04:18 UTC (rev 27380)
+++ Zope3/trunk/src/zope/interface/human.txt	2004-09-01 14:06:28 UTC (rev 27381)
@@ -0,0 +1,152 @@
+==========================
+Using the Adapter Registry
+==========================
+
+This is a small demonstration of the zope.interface package including its
+adapter registry. It is intended to provide a concrete but narrow example on
+how to use interfaces and adapters outside of Zope 3.
+
+First we have to import the interface package.
+
+  >>> import zope.interface
+
+We now develop an interface for our object, which is a simple file in this
+case. For now we simply support one attribute, the body, which contains the
+actual file contents.
+
+  >>> class IFile(zope.interface.Interface):
+  ...
+  ...     body = zope.interface.Attribute('Contents of the file.')
+  ...
+
+For statistical reasons we often want to know the size of a file. However, it
+would be clumsy to implement the size directly in the file object, since the
+size really represents meta-data. Thus we create another interface that
+provides the size of something.
+
+  >>> class ISize(zope.interface.Interface):
+  ...
+  ...     def getSize():
+  ...         'Return the size of an object.'
+  ...
+
+Now we need to implement the file. It is essential that the object states
+that it implements the `IFile` interface. We also provide a default body
+value (just to make things simpler for this example).
+
+  >>> class File(object):
+  ...
+  ...      zope.interface.implements(IFile)
+  ...      body = 'foo bar'
+  ...
+
+Next we implement an adapter that can provide the `ISize` interface given any
+object providing `IFile`. By convention we use `__used_for__` to specify the
+interface that we expect the adapted object to provide, in our case
+`IFile`. However, this attribute is not used for anything. If you have
+multiple interfaces for which an adapter is used, just specify the interfaces
+via a tuple.
+
+Again by convention, the constructor of an adapter takes one argument, the
+context. The context in this case is an instance of `File` (providing `IFile`)
+that is used to extract the size from. Also by convention the context is
+stored in an attribute named `context` on the adapter. The twisted community
+refers to the context as the `original` object. However, you may feel free to
+use a specific argument name, such as `file`.
+
+  >>> class FileSize(object):
+  ...
+  ...      zope.interface.implements(ISize)
+  ...      __used_for__ = IFile
+  ...
+  ...      def __init__(self, context):
+  ...          self.context = context
+  ...
+  ...      def getSize(self):
+  ...          return len(self.context.body)
+  ...
+
+Now that we have written our adapter, we have to register it with an adapter
+registry, so that it can be looked up when needed. There is no such thing as a
+global registry; thus we have to instantiate one for our example manually.
+
+  >>> from zope.interface.adapter import AdapterRegistry
+  >>> registry = AdapterRegistry()
+
+
+The registry keeps a map of what adapters implement based on another
+interface, the object already provides. Therefore, we next have to register an
+adapter that adapts from `IFile` to `ISize`. The first argument to
+the registry's `register()` method is a list of original interfaces.In our
+cause we have only one original interface, `IFile`. A list makes sense, since
+the interface package has the concept of multi-adapters, which are adapters
+that require multiple objects to adapt to a new interface. In these
+situations, your adapter constructor will require an argument for each
+specified interface.
+
+The second argument is the interface the adapter provides, in our case
+`ISize`. The third argument in the name of the adapter. Since we do not care
+about names, we simply leave it as an empty string. Names are commonly useful,
+if you have adapters for the same set of interfaces, but they are useful in
+different situations. The last argument is simply the adapter class.
+
+  >>> registry.register([IFile], ISize, '', FileSize)
+
+You can now use the the registry to lookup the adapter.
+
+  >>> registry.lookup1(IFile, ISize, '')
+  <class '__main__.FileSize'>
+
+Let's get a little bit more practical. Let's create a `File` instance and
+create the adapter using a registry lookup. Then we see whether the adapter
+returns the correct size by calling `getSize()`.
+
+  >>> file = File()
+  >>> size = registry.lookup1(IFile, ISize, '')(file)
+  >>> size.getSize()
+  7
+
+However, this is not very practical, since I have to manually pass in the
+arguments to the lookup method. There is some syntactic candy that will allow
+us to get an adapter instance by simply calling `ISize(file)`. To make use of
+this functionality, we need to add our registry to the adapter_hooks list,
+which is a member of the adapters module. This list stores a collection of
+callables that are automatically invoked when IFoo(obj) is called; their
+purpose is to locate adapters that implement an interface for a certain
+context instance.
+
+You are required to implement your own adapter hook; this example covers one
+of the simplest hooks that use the registry, but you could implement one that
+used an adapter cache or persistent adapters, for instance. The helper hook is
+required to expect as first argument the desired output interface (for us
+`ISize`) and as the second argument the context of the adapter (here
+`file`). The function returns an adapter, i.e. a `FileSize` instance.
+
+  >>> def hook(provided, object):
+  ...     adapter = registry.lookup1(zope.interface.providedBy(object),
+  ...                                provided, '')
+  ...     return adapter(object)
+  ...
+
+We now just add the hook to an `adapter_hooks` list.
+
+  >>> from zope.interface.interface import adapter_hooks
+  >>> adapter_hooks.append(hook)
+
+Once the hook is registered, you can use the desired syntax.
+
+  >>> size = ISize(file)
+  >>> size.getSize()
+  7
+
+Now we have to cleanup after ourselves, so that others after us have a clean
+`adapter_hooks` list.
+
+  >>> adapter_hooks.remove(hook)
+
+That's it. I have intentionally left out a discussion of named adapters and
+multi-adapters, since this text is intended as a practical and simple
+introduction to Zope 3 interfaces and adapters. You might want to read the
+`adapter.txt` in the `zope.interface` package for a more formal, referencial
+and complete treatment of the package. Warning: People have reported that
+`adapter.txt` makes their brain feel soft!

Modified: Zope3/trunk/src/zope/interface/tests/test_adapter.py
===================================================================
--- Zope3/trunk/src/zope/interface/tests/test_adapter.py	2004-09-01 14:04:18 UTC (rev 27380)
+++ Zope3/trunk/src/zope/interface/tests/test_adapter.py	2004-09-01 14:06:28 UTC (rev 27381)
@@ -136,7 +136,7 @@
 def test_suite():
     from zope.testing.doctestunit import DocFileSuite
     return unittest.TestSuite((
-        DocFileSuite('../adapter.txt', 'foodforthought.txt',
+        DocFileSuite('../adapter.txt', '../human.txt', 'foodforthought.txt',
                      globs={'__name__': '__main__'}),
         doctest.DocTestSuite(),
         ))



More information about the Zope3-Checkins mailing list