diff options
Diffstat (limited to 'src/zope/interface/human.txt')
| -rw-r--r-- | src/zope/interface/human.txt | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/src/zope/interface/human.txt b/src/zope/interface/human.txt new file mode 100644 index 0000000..749b87d --- /dev/null +++ b/src/zope/interface/human.txt @@ -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 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:: + + >>> 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! |
