Step 1: Interfaces

In this step, we will cover a fundamental concept of Zope 3: interfaces. They play a key role in Zope 3 and their understanding is essential for Zope 3 and its Component Architecture.

Introduction to interfaces

Interfaces describe the functionality of a component in a way that is both meaningful to the developer and the Component Architecture. A lot of the times, interfaces define the API of a component, meaning methods and attributes, like in this excerpt from Acquisition.interfaces:

from zope.interface import Interface

class IAcquirer(Interface):
    """Acquire attributes from containers."""

    def __of__(context):
        """Get the object in a context."""

On other occasions they are just markers on a component that carry a more implicit meaning and don't carry any (new) API specification, like in this excerpt from OFS.interfaces:

class IItemWithName(IItem):
    """Item with name"""

In this case we call them marker interfaces.

If you find a component providing a certain interface you can count on the API of that interface. If not, the component is broken. Of course, we cannot force every implementation to comply with the interface (after all, this still is Python), but there's an implicit contract guaranteed to you by the Component Architecture. In fact, it is often quite useful to see interfaces as contracts between components. And like in real life, you have to count on the other party complying with the contract...

Since interfaces describe the functionality of components, they are the keys by which components are looked up in Zope 3. Zope 2 looks up things by names, e.g. meta_type or CMF's portal_type and getToolByName. As you can imagine, looking things up by interface makes it much more meaningful. You're not just getting back an object with some name, you get a component that promises certain functionality which is formalized in the API of the interface you looked it up with.

For example, compare

>>> catalog = getToolByName('portal_catalog')

with

>>> catalog = getUtility(ICatalog)

Now, which line do you think says more about the catalog object?

Implement vs. provide

The Component Architecture introduces two distinct concepts: implementing an interface vs. providing an interface. Objects provide interfaces, as much as they provide functionality. Object factories such as classes, on the other hand, implement interfaces:

>>> from zope.interface import implements
>>> class Foobar(object):
...     implements(IFoobar)
>>> IFoobar.implementedBy(Foobar)
True

So, when a class implements an interface, instances of this class will provide it:

>>> foo = Foobar()
>>> IFoobar.providedBy(foo)
True

Of course, objects can provide more interfaces than their classes on a per-instance basis:

>>> from zope.interface import alsoProvides
>>> alsoProvides(foobar, IExtendedFoobar)
>>> IExtendedFoobar.providedBy(foobar)
True
>>> foobar2 = Foobar()
>>> IExtendedFoobar.providedBy(foobar2)
False

Migration

Zope 2 already has interfaces, but they are not compatible with the Zope 3 Component Architecture. Because of that, Zope 3 interfaces are much more meaningful.

The good news is that you can use Zope 3 interfaces in Zope 2 today, even with existing components. In certain places, mostly the CMF, Zope 2 interfaces are still used in which case you can easily bridge Zope 3 interfaces back to Zope 2 ones:

>>> from Interface.bridge import fromZ3Interface
>>> IAZope2Interface = fromZ3Interface(IAZope3Interface)

With this functionality in place, it is recommended to use Zope 3 interfaces where possible and bridge them back to Zope 2 interfaces where needed, not the other way around (because Zope 2 interfaces are a dead end).

Schemas

As a Plone developer, the concept of schemas is already familiar to you from Archetypes. In Zope 3, schemas are just mere interfaces. Why? Because schemas are not primarily thought of as browser forms definitions but as data models. So, schemas are interfaces that don't describe what an object does but what what kind of data an object holds, by using attribute definitions. A lot of the times, this is much more Pythonic because Python doesn't need accessors or mutators.

Modelling content components with attribute definitions instead of a method API is also much more in line with a fundamental idea of the Component Architecture: separation of concerns, meaning dull content objects and smart controllers (views and adapters) around them.

Of course, you may realize that Zope 3's understanding of schemas is a bit different from the one of Archetypes. It is especially not constrained to browser forms, thus much more flexible and generally perceived as more Pythonic. It is really too bad that there are two different schema implementations in the Zope world. It even didn't have to be that way, the Zope 3 schema engine was already around when Archetypes was invented.

Step 5 of this tutorial will cover schemas, however deliberately not in the context of browser forms.

A simple interface

Now, finally, after all that theory we can dive into some code. We said in the introduction that we wanted to extend Plone with Atom feed capabilities. Therefore, let's create a new product in our instance's Products directory called FiveFeeds. Don't forget the __init__.py (it can just be an empty file).

Interfaces by convention go to a module or package called interfaces, so let's create a interfaces.py (download source) file inside the FiveFeeds package with the following contents:

from zope.interface import Interface

class IPortalObject(Interface):
    pass

As you can undoubtedly see, we've created the simplest interface one can think of. No doubt this is a marker interface. We will use it to mark objects living in our portal site. Step 2, which talks about ZCML, will show us how to do that.

Summary