[Interface-dev] Interface and Adapter Demo

Stephan Richter stephan.richter at tufts.edu
Mon Jun 28 10:06:45 EDT 2004


Hi Christian,

I have finished the example we started last night and commented it. Since I 
thought that other people might find this non-Zope-specific example helpful, 
I CCed the Zope 3 Dev and Interface Dev mailing lists. 

And here it is:


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 very important 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:
  ...
  ...      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.

Again by convention, the constructor of an adapter takes one argument, the
context. The context 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 'context' on the adapter. The twisted community refers to the
context as the 'original' object. 

  >>> class FileSize:
  ...
  ...      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 the adapter
registry, so that it can be looked up when needed. 

  >>> from zope.interface.adapter import AdapterRegistry
  >>> registry = AdapterRegistry()

Register an adapter that adapts from 'IFile' to 'ISize'. The first argument to
the registry's 'register()' is a list of original interfaces. In our cause we
have only one original 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. 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 at 0x402ea3bc>

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 have to register our registry with some hook mechanism.

To do that, we have to write a helper hook function whose first argument is
the desired output interface (for us 'ISize') and the second argument is 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

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.

Have fun.

Regards,
Stephan
-- 
Stephan Richter
CBU Physics & Chemistry (B.S.) / Tufts Physics (Ph.D. student)
Web2k - Web Software Design, Development and Training


More information about the Interface-dev mailing list