Personal tools
You are here: Home Documentation Tutorials Grok Poller Tutorial Adapters

Adapters

This section tries to be a short introduction to adapters as they are used in Zope component architecture; thus Grok, and as an example, we will use the ``NameChooser`` mentioned in the previous section, and implement our own version of it.
This tutorial shows how to implement a simple polling application using Grok.
Page 10 of 14.

Adapters are a way to add functionality to existing objects, without actually changing the objects, this is called "adapting an object." The following is the complete interface definition of INameChooser from zope.app.container:

class INameChooser(Interface):

    def checkName(name, object):
        """Check whether an object name is valid.

        Raises a user error if the name is not valid.
        """

    def chooseName(name, object):
        """Choose a unique valid name for the object.

        The given name and object may be taken into account when
        choosing the name.

        chooseName is expected to always choose a valid name (that would pass
        the checkName test) and never raise an error.

        """

We want to add those two methods to our container, so it can validate and choose a name for the polls, but we don't want to clutter the implementation with all the different interfaces we need or might need in the future, so we "outsource" the implementation to an adapter. To test adapting our container to INameChooser we can run the following code in the debug console:

>>> from zope.app.container.interfaces import INameChooser
>>> poller = root['poller']
>>> namechooser = INameChooser(poller)
>>> namechooser
<zope.app.container.contained.NameChooser object at 0x140cad0>

as you can see, namechooser is now an instance of an object which implements INameChooser interface. This means that the actual object is now different, but works in the context of our container. For this to work, an adapter must be registered for our container interface, in this case it was already done for us by default in Grok.

The adaptation is not as magical as it seems. The following is a simple mock name chooser adapter, which allows us to customize name choosing for our container:

from zope.app.container.interfaces import INameChooser

class MockNameChooser(object):
    def __init__(self, context):
        self.context = context

    def checkName(self, name, object):
        # Only allow unique names
        return not name in self.context

    def chooseName(self, name, object):
        # Don't modify the name
        return name

So if we now wish to adapt Poller to our MockNameChooser, we just run the following code:

namechooser = MockNameChooser(poller)

it just assigns poller as self.context in our namechooser instance, so that the adapter can implement INameChooser interface in our objects context. This is the basic adaptation pattern that doesn't need frameworks around it. But in the debug console above, we didn't use the adapter class name when we adapted the poller to a name chooser, we actually used the interface for adaptation, and this is where the Zope component framework comes in.

If we adapt the poller to INameChooser interface, it still returns Zope's own NameChooser instance, not the adapter we just implemented. This is because the component framework doesn't know that MockNameChooser exists. To actually register the adapter, we must use a couple of Grok directives, and we can also use grok.Adapter as a subclass to shorten the code a bit:

class MockNameChooser(grok.Adapter):
    grok.context(Poller)
    grok.provides(INameChooser)

    def checkName(self, name, object):
        # Only allow unique names
        return not name in self.context

    def chooseName(self, name, object):
        # Don't modify the name
        return name

grok.Adapter is just a simple class, that implements the __init__ method according to Zope adapter convention; that the class accepts a single parameter which is the context of the adapter, aptly named self.context:

class Adapter(object):
    def __init__(self, context):
        self.context = context

grok.context works in the same way as with views [1], it tells the component framework that the context for this adapter is Poller. grok.provides is a new directive for us, which informs the component framework that this adapter provides INameChooser framework (for Poller context.)

[1]The views are actually adapters too; for more information about it, read the Grok documentation.

Now that the adapter is registered, whenever someone tries to adapt the poller to the name chooser interface, it gets our implementation. This is a simple, but effective pattern to create extendable frameworks and applications in Python.

For more information about adapters, read "Grok Developer's Notes" section on adapters [2] and watch Brandon Craig Rhodes' "Introduction to Zope Component Architecture" presentation [3] from NOLA Symposium '08.

[2]<http://grok.zope.org/doc/current/grok_overview.html#adapters>
[3]<http://plone.tv/media/1442083381/view>
 

Additional import needed

Posted by anil at Jan 24, 2011 01:19 AM
For those just getting started, in order to overcome the import error for
>>> from zope.app.container.interfaces import INameChooser

one needs to add an additional installation requirement in setup.py

     # Add extra requirements here
     'zope.app.container',

and then run
bin/buildout