| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- ==========================
- Using the Adapter Registry
- ==========================
- This is a small demonstration of the :mod:`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:
- .. doctest::
- >>> 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:
- .. doctest::
- >>> 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:
- .. doctest::
- >>> class ISize(zope.interface.Interface):
- ...
- ... def getSize():
- ... 'Return the size of an object.'
- ...
- Now we need to implement the file interface. 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):
- .. doctest::
- >>> 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``:
- .. doctest::
- >>> 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:
- .. doctest::
- >>> 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 is 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:
- .. doctest::
- >>> registry.register([IFile], ISize, '', FileSize)
- You can now use the the registry to lookup the adapter:
- .. doctest::
- >>> registry.lookup1(IFile, ISize, '')
- <class '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()``:
- .. doctest::
- >>> 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:
- .. doctest::
- >>> 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:
- .. doctest::
- >>> from zope.interface.interface import adapter_hooks
- >>> adapter_hooks.append(hook)
- Once the hook is registered, you can use the desired syntax:
- .. doctest::
- >>> size = ISize(file)
- >>> size.getSize()
- 7
- Now we have to clean up after ourselves, so that others after us have a clean
- ``adapter_hooks`` list:
- .. doctest::
- >>> 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, referential
- and complete treatment of the package. Warning: People have reported that
- ``adapter.txt`` makes their brain feel soft!
- .. _twisted: https://twistedmatrix.com/
|