Personal tools
You are here: Home Documentation Tutorials b-org: Creating content types the Plone 2.5 way A whirlwind tour of Zope 3
Support

Get Help

Join our chat rooms or support forums if you have more specific questions.

Plone Training
Learn how to design, build, and deploy a website in Plone through one of the numerous Plone training sessions around the world.
Find Plone training…
 
Document Actions

A whirlwind tour of Zope 3

Zope 3 is still fairly new. After reading this tutorial, it should hopefully start to feel a bit more familiar. In this section, we will give a brief overview of what is different in Zope 3 and how it fits into Plone.

Martin Aspeli

Plone 2.5 brings us closer to the promised land of Zope 3. Zope 3 brings us a new way of working. This tutorial will show how to marry the old and the new, to make Plone products that are more extensible, better tested and easier to maintain.
Page 2 of 15.

The name Zope 3 is a lie. True - it is brought to you by many of the same clever people who built Zope 2, one of the most advanced open source app servers of its day. True, it is still Python, it still publishes things over the web, and there are still Zope Page Templates. However, Zope 3 is about small, re-usable components orchestrated into a flexible framework. It is this flexibility that allows us to use Zope 3 technologies in Zope 2 applications like Plone.

A piece of wizardry called Five (Zope 2 + Zope 3 = Five, geddit?) makes a number of Zope 3 components directly available in Zope 2, and since Zope 2.8, almost all of Zope 3 has shipped with Zope 2 as a python library. Plone 2.5's primary purpose was to lay the foundations for taking advantage of Zope 3 technologies in Plone.

Zope 3 may seem a bit alien at first, because it uses strange concepts such as adapters and utilities. Luckily, these are not so difficult to understand, and once you do, you will find that they help you focus your development on smaller and more manageable components. You will also find that these basic concepts underpin most of the innovative parts of Zope 3.

Interfaces

Everything in Zope 3 starts with interfaces. Unlike Java or C#, say, Python does not have a native type for an interface, so an interface in Zope 3 is basically a class that contains only empty methods and attributes, and inherits from Interface. Here is a basic example:

from zope.interface import Interface, Attribute

class IShoe(Interface):
"""A shoe
"""

color = Attribute("Color of the shoe")
size = Attribute("Shoe size")

class IShoeWearing(Interface):
"""An object that may wear shoes
"""

def wear(left, right):
"""Wear the given pair of shoes
"""

Interfaces are primarily documentation - everything has docstrings. Also note that the wear() method lacks a body (there is not even a pass statement - the docstring is enough to keep the syntax valid), and does not take a self parameter. That is because you will never instantiate or use an interface directly, only use it to specify the behaviour of an object.

An object can be associated with an interface in a few different ways. The most common way is via its class. We say that the class implements an interface, and objects of that class provide that interface:

from zope.interface import implements

class Shoe(object):
"""A regular shoe
"""

implements(IShoe)

color = u''
size = 0

The implements(IShoe) line means that objects of this class will provide IShoe. Further, we fulfill the interface by setting the two attributes (we could have implemented them as properties or used a an __init__() method as well). The IShoeWearing interface will be implemented in the section on adapters below.

We use interfaces to model components. Interfaces are normally the first stage of design, in that you should define clear interfaces and write actual classes to fulfill those interfaces. This formalism makes for great documentation - interfaces are conventionally found in an interfaces module, and this is typically the first place you look after browsing a package's documentation. It also underpins the adapter and utility system - otherwise known as the Component Architecture - as described below.

Note that you can use common OOP techniques in designing interfaces. If one interface describes a component that has an "is-a" or "has-a" relationship to another component, you can let interfaces subclass or reference each other. An object will provide the interfaces of its class, and all its base-classes, and all base-interfaces of those interfaces. Don't worry about untangling that - it works the way you would expect.

You can also apply interfaces directly to an object. Of course, if that interface has methods and attributes, they must be provided by the object, and unless you resort to crazy dynamic programming, the object will get those from its class, which means that you may as well have applied the interface to the class. However, some interfaces don't have methods or attributes, but are used as markers to distinguish a particular feature of an object. Such marker interfaces may be used as follows:

class IDamaged(Interface):
"""A shoe that is damaged
"""
>>> from zope.interface import directlyProvides
>>> boot = Shoe()
>>> IDamaged.providedBy(boot)
False
>>> directlyProvides(boot, IDamaged)
>>> IDamaged.providedBy(boot)
True

Marker interfaces are very useful for things that change at run-time in response to some event (e.g. some user action), and thus cannot be determined in advance. In a moment, you see that what you will learn about adapters and adapter factories below also applies to marker interfaces - it is possible to alter which adapter factory is invoked by applying a different marker interface.

It's also possible to apply interfaces directly to classes (that is the class itself provides the interface, as opposed to the more usual case where the class implements the interface so that objects of that class provides it - this is useful because it allows you to group those classes together and describe the type of class they are) and to modules (where you want to describe the public methods and variables of a module). These constructs are less common, so don't worry about them for now. Look at the documentation and interfaces (!) in the zope.interface package for more.


Adapters

The most important thing that Zope 3 promises is separation of concerns. In Zope 2, almost everything has a base class that pulls in a number of mix-in classes, such as SimpleItem (surely, the most ironically named class in Zope 2) and its plethora of base classes that include RoleManager, Acquisition.Implicit and many others. This means that a class written for Zope 2 is nearly impossible to re-use outside of Zope.

Furthermore, in Zope 2 we are tightly wedded to the context (aka here) because it is so convenient to use in page templates, workflow scripts etc. For example, people often write an Archetypes class that contains a schema (storage logic), methods for providing various operations (business logic) and methods for preparing things to display in a page template (view logic). Often, people do this simply because they can't think of a better place to put things, but it does mean that re-using any part of the functionality becomes impossible without importing the whole class - and its base classes, which include Archetypes' BaseObject, CMF's DynamicType, and Zope's SimpleItem - to name a few!

Think about the example above. The Shoe class is well-contained and only concerned with one thing - storing the attributes of shoes. It can be used as an abstraction of shoe anywhere, and is very lightweight. Now let's consider that we may want to wear shoes as well. We can create a pair of shoes easily enough:

>>> left = Shoe()
>>> right = Shoe()
>>> left.size = right.size = 10
>>> left.color = right.color = u"brown"

Now we want someone to wear these shoes. Let's say we have a person:

class IPerson(Interface):
"""A person
"""

name = Attribute("The person's name")
apparel = Attribute("A list of things this person is wearing")

class Person(object):

implements(IPerson)

name = u''
apparel = ()

In a Zope 2 world, we may have required Person to mix in some ShoeWearingMixin class that specified exactly how shoes should be worn. That makes for fat interfaces that are difficult to understand. In a Zope 3 world, we would more likely use an adapter.

An adapter is a glue component that can adapt an object providing one interface (or a particular combination of interfaces, in the case of a multi-adapter) to another interface. We already have a specification for something that wears shoes, in the form of IShoeWearing. Here is a snippet of code that may use this interface:

>>> wearing = ...
>>> wearing.wear(left, right)

The question is what to do with the '...' - how do we obtain an object that provides IShoeWearing? Code like this is normally operating on some context, which in this case may be a Person. If that Person implemented IShoeWearing (or at least the wear() method), it would work, but then we are making undue demands on Person. What we need is a way to adapt this IPerson to something that is IShoeWearing. To do that, we need to write an adapter:

from zope.interface import implements
from zope.component import adapts

class PersonWearingShoes(object):
"""Adapter allowing a person to wear shoes
"""
implements(IShoeWearing)
adapts(IPerson)

def __init__(self, context):
self.context = context

def wear(self, left, right):
self.context.apparel += (left, right)

Here, we implement the IShoeWearing interface. Note how the wear() method now has a self parameter, since this is a real object. Also note the __init__() method, which takes a parameter conventionally called context. This is the thing that is being adapted, in this case an object providing IPerson. We store this as an instance variable and then reference it later. Note that adapters are almost always transient objects that are created on the fly (we will see how in a second).

We could now do something like this:

>>> wearing = PersonWearingShoes(person)
>>> wearing.wear(left, right)

However, this still requires that we know exactly which adapter to invoke for the particular object (person in this case), effectively creating a tight coupling between the adapter, the thing being adapted, and the code using the adapter.

Luckily, the Zope 3 Component Architecture knows how to find the right adapter if you only tell it about the available adapters. We do that using ZCML, the Zope Configuration Markup Language. This is an XML dialect that is used to configure many aspects of Zope 3 code, such as permissions and component registration. You can do what ZCML does in Python code as well, but typically it's more convenient to use ZCML because it allows you to separate your logic from your configuration.

ZCML directives are stored in file called configure.zcml, which itself may include other files. A configure.zcml file in your product directory (Products/myproduct/configure.zcml) will be picked up automatically by Five. Here is a snippet that will register the above adapter:

<adapter factory=".shoes.PersonWearingShoes" />

You will sometimes see a fuller form of this directive, like:

<adapter
factory=".shoes.PersonWearingShoes"
for=".interfaces.IPerson"
provides=".interfaces.IShoeWearing"
/>

Here, we are specifying full dotted names to interfaces in the for or provides attributes. These are equivalent to the adapts() and implements() calls we used when defining the adapter. Note that adapts() did not work prior to Zope 2.9 (so the ZCML for attribute is mandatory), and that if your adapter class for some reason implements more than one interface (e.g. because it's inheriting another adapter that has its own implements() call), you may need to specify provides to let Zope 3 know which interface you're really adapting to.

Notice here that the dotted names begin with dot. This means "relative to the current package". You can write "..foo.bar" to reference the parent package as well. You could specify an absolute path instead, e.g. Products.Archetypes.interfaces.IBaseObject or zope.app.annotation.interfaces.IAttributeAnnotatable. Typically, you use the full dotted name for things in other packages and the relative name for things in your own package.

The factory attribute normally references a class. In Python, a class is just a callable (taking the parameters specified in its __init__() method) that returns an instance of itself. You can reference another callable as well if you need to, such as a function that takes the same parameters (only context in this case - obviously there is no self for functions), finds or constructs and object (which must provide IShoeWearing) and then returns it. This is rarely used, but can be very powerful (for example, it could find an object providing the given interface in the adapted object's annotations - but don't worry if you don't understand that for now).

With this wiring in place, we can now find an adapter for an IPerson to IShoeWearing. The Component Architecture will ensure that we find the correct adapter:

>>> wearing = IShoeWearing(person)
>>> wearing.wear(left, right)
>>> person.apparel == (left, right,)
True

We are "calling" the interface, which is a convenience syntax for an adapter lookup. If an adapter cold not be found, you will get a ComponentLookupError. There are plenty of functions in zope.component to discover adapters and other components - see zope.component.interfaces for the full story.

It is important to realise that the adapter lookup is essentially a search. The Component Architecture will look at the interfaces provided by person and look for a suitable adapter to IShoeWearing. As mentioned before, it's possible for an object to provide many interfaces, e.g. inherited from its base classes, implemented explicitly by the object (by declaring implements(IFoo, IBar)), via ZCML or because an object directly provides an interface. It is therefore possible that there are multiple adapters that could be applicable. In this case, Zope 3 will use the interface resolution order (IRO) to find the most specific adapter. The IRO is much like you would expect of polymorphism in traditional OOP:

  • an interface directly provided by the object is more specific than one provided by its class
  • an object provided by an object's class is more specific than that provided by a base class
  • if an object has multiple base classes, interfaces are inherited in the same order as methods are inherited
  • if a class implements multiple interfaces, the first one specified is more specific than the second one, and so on

Remember marker interfaces? One use of marker interfaces is to imply a particular adapter. Think about the case where you may have  specific adapter to IShoeWearing for some marker interface IAmputee. If you mark a person as an IAmputee due to some unforunate accident, the IShoeWearing adapter may raise a warning rather than modify the apparel list.

All of this may seem a little roundabout and unfamiliar, but you'll get to grips with it soon enough. Let's re-cap how we arrived at this:

  1. We modelled our application domain with some interfaces - IPerson, IShoe
  2. We modelled an aspect of a person (or other object) for wearing shoes - IShoeWearing
  3. We wrote some simple classes that implemented the domain interfaces IPerson and IShoe
  4. We wrote and registered a simple adapter that could adapt an IPerson to IShoeWearing

Then we showed how this could be used by some hypothetical client code. The upshot is that the client code only needed to know about IPerson and IShoeWearing, not how the aspect of a person that involves wearing shoes is implemented. The Component Architecture will ensure that the appropriate adapter is found, regardless of whether the person is a vanilla IPerson, a sub-class with a more specific sub-interface, or an instance with a marker interface applied.


Multi-adapters, named adapters and views

In the example above, we used an adapter with a single context. That is the most common form of adapter, but sometimes there is more than one object that forms the context of an adapter. As a rule of thumb, if you find yourself passing a particular parameter into every method of an adapter, it should probably be a multi-adapter.

The most common example of a multi-adapter that you will come across is that of a view, which incidentally is also how Zope 3 solves the "where do I put my view logic" code. We will cover views in detail later, but for now think of them as a python class that is automatically instantiated and bound to a page template when it's rendered. In the template, the variable view refers to the view instance and can be used in TAL expressions to gain things to render or loop on.

When dealing with a view, there are two things that make up its context - the context content object (conventionally called context) and the current request (conventionally called request). Thus, a view class is a multi-adapter from the tuple (context, request) to IBrowserView. As it happens, there are ZCML directives called browser:page and browser:view that make it easier to register a view and bind a page template to it, handle security etc. However, abstractly a view looks like this:

class PersonView(object):
implements(IBrowserView)
adapts(IPerson, IHttpRequest)

def __init__(self, context, request):
self.context = context
self.request = request

def name(self):
return self.context.name

def requested_shoes(self):
return self.request.get('requested_shoes', [])

Notice how this adapts both IPerson and IHttpRequest, and thus takes two parameters in its __init__() method. As you will learn later, views typically inherit the BrowserView base class for convenience, but the principle is the same.

To obtain a multi-adapter, you can't use the "calling an interface" syntax that you use for a regular adapter. Instead, you must use the getMultiAdapter() method:

>>> from zope.component import getMultiAdapter
...
>>> personView = getMultiAdapter((person, request,), IBrowserView)

You could use queryMultiAdapter() instead if you wanted it to return None instead of raise a ComponentLookupError when it fails to find the adapter.

The above code has a problem, however (apart from being an incomplete example) - what if you have more than one view on the same object, say for two different tabs? To resolve this ambiguity, views are actually named multi-adapters. The names correspond to the names used as part a URL, and are registered using the name attribute in ZCML. This is used in browser:page and browser:view directives, but can also be used in the standard adapter directive:

<adapter factory=".sampleviews.PersonView" name="index.html" />

To get this particular view, we can write:

>>> personView = getMultiAdapter((person, request,), name=u'index.html')

conventionally, we leave off the required interface when we used named adapters, although you can supply it if necessary.

Multi-adapters are useful for other things as well. If you have an adapter and find that every method takes at a common parameter, it's a good candidate for a multi-adapter. Also observe that in the case above, we could register a different adapter for a different type of request as well as for a different type of object. Again, the Component Arhictecture will find the most specific one looking at both interfaces.

Named adapters do not have to be multi-adapters, of course. They are typically used in cases where something (e.g. the user) is making a selection from a set of possible choices (such as choosing the particular view among many possible views).

Utilities

In the CMF, we have tools, which are essentially singletons. They contain various methods and attributes and may be found using the ubiquitous getToolByName() function. The main problem with tools is that they live in content space, as objects in the ZODB, and require a lot of Zope 2 specific things.

Let's say we had a shoe locating service (very useful when you can't find your shoes):

class IShoeLocator(Interface):
"""A service for finding your shoes
"""

def findShoes(owner):
"""Find all shoes for the given owner.
"""

class DefaultShoeLocator(object):
implements(IShoeLocator)

def findShoes(self, owner):
return ...

The Component Architecture contains a very flexible utility registry, which lets you look up things by interface and possibly by name. Unlike adapters, utilities do not have context, and they are instantiated only once, when Zope starts up. Global utilities are not persistent (but local utilities are - see below).

As with adapters, we register utilities with ZCML:

<utility factory=".locator.DefaultShoeLocator" />

Alternatively, you could skip the implements() call on the factory and set it in ZCML. This may also be necessary in order to disambiguate if you have more than one interface being provided by the utility component:

<utility 
factory=".locator.DefaultShoeLocator"
provides=".interfaces.IShoeLocator
/>

Now you can find the utility using getUtility():

>>> from zope.component import getUtility
>>> locator = getUtility(IShoeLocator)
>>> locator.findShoes(u"optilude")
...

The utility registry turns out to be a very useful generic registry, because like the adapter registry, it can manage named utilities. Let's say that you had a few different shoes you wanted to keep around:

>>> left = Shoe()
>>> right = Shoe()
...

>>> from zope.component import provideUtility
>>> provideUtility(left, name=u'left-shoe')
>>> provideUtility(right, name=u'right-shoe')

We can now find these utilities again using the name argument to getUtility().

>>> to_put_on = getUtility(IShoe, name=u'left-shoe')

Of course, we are still using the transient global utility registry, so these will diseappear when Zope is restarted. We could use local components instead (see below), or we could register them using ZCML. If we had defined the shoes left and right in a module shoes.py, we could write:

<utility
component=".shoes.left"
name="left"
/>

<utility
component=".shoes.right"
name="right"
/>

An alternative would have been to define two classes LeftShoe and RightShoe and use the factory attribute of the directive instead of component (which refers to an instance, rather than a class/factory).

Local components

The examples above all use global, transient registries that are reloaded each time Zope is restarted. That is certainly what you want for code and functionality. Sometimes, you would like for utilities to be a bit more like their CMF cousins and also manage persistent state. To achieve that you need to use local components, which are stored in the ZODB.

Prior to Zope 3.3, which is included in Zope 2.10, local components were a bit of a black art. Then came the jim-adapter branch and everything was greatly simplified. The theory is still the same, the API is just much more sane. Each time Zope executes a request (or if you implicitly invoke zope.component.setSite(), for example in a test), it discovers which is the nearest site to the context. In Plone, the site is normally the root of the Plone instance, but in theory any folder could be turned into a site.

A site has a local component registry, where local utilities and adapters may be defined. This means that a particular utility or adapter can be specific to a particular Plone site, not affecting other Plone instances in the same Zope instance. You cannot use ZCML to register local components, since ZCML is inherently global (at least for now) - it does not know anything about your particular sites. However, you can register them with Python code, e.g. in an Install.py or a GenericSetup profile, using calls like provideUtility() (and its equivalent, provideAdapter()) called on a local site manager instance:

>>> from zope.component import getSiteManager

>>> getUtility(IShoe, name=u'left-shoe) is left
True

>>> sm = getSiteManager(context)
>>> sm.provideUtility(myShoe, name=u'left-shoe')
>>> getUtility(IShoe, name=u'left-shoe) is myShoe
True

Unfortunately, Plone 2.5 does not run on Zope 2.10. We won't cover local components here, because, well, I never learnt how to do it the Zope 2.9 way, and what I saw of it scared me. I'm told it's not that bad, and there is documentation in Five and in Zope 3 itself. Local components will become more important in Plone 3.0, where Zope 2.10 or later will be required and more things that use local components will be part of the core.

b-org does not use local components yet, and we will see how the extension mechanism would benefit from local components so that you could have one b-org extension installed in one Plone instance and another extension installed in another Plone instance, without the two interfering. Luckily, to code that uses adapters and utilities, it is completely transparent whether they are global or local.

Conclusion

That's it! If you can master the concepts of interfaces, adapters and utilities you will go far in a Zope 3 world. They will become much more natural as you use them a few times, and you'll probably wonder how you ever managed without them. Hopefully, that point will come before the end of this tutorial, which is largely focused on showing how the principle of separation of concerns can be imposed upon your Archetypes and Plone code.

 
by Martin Aspeli last modified December 25, 2006 - 20:48 All content is copyright Plone Foundation and the individual contributors.

For any issues with the web site functionality, please file a ticket.

Please consult the policy on plone.org content if you want your content published on this site.

Servers and hosting by