home contents changes options help subscribe

Components

Note

Here you will learn the basics of zope component architecture. Keywords: adapters, utilities.

Introduction

Components are reusable objects, so cohesive but least coupled by nature. The basic idea is same, a component provides an interface. Zope component architecture is all about two basic kinds of components, adapters and utilities.

Utilities

A utility component can be looked up by an interface and a name. If name is not given, it defaults to an empty string.

Look at this IHost? interface (a modified version from previous chapter):

>>> from zope import interface

>>> class IHost(interface.Interface):
...     """A host object"""
...
...     def goodmorning():
...         """Say good morning"""

And this Host implementation:

>>> class Host(object):
...     interface.implements(IHost)
...
...     def __init__(self, guest=u''):
...         self.guest = guest
...
...     def goodmorning(self):
...         """Say good morning"""
...
...         return "Good morning, %s!" % self.guest

Then register an instance of this class using zope.component.provideUtility:

>>> from zope import component

>>> jack = Host('Jack')
>>> component.provideUtility(jack)

Now you have registered a utility component. This is a simple utility with one method, goodmorning. When calling goodmorning, it will return 'Good morning, Jack!'. Now you can look up the utility which provides IHost? interface. There are two functions to lookup utilities, component.queryUtility and component.getUtility. You can query for the utility component like this.:

>>> host = component.queryUtility(IHost)
>>> host.goodmorning()
'Good morning, Jack!'

When registering utilities it is possible to specify the interface and a name for utility.

>>> jill = Host('Jill')
>>> component.provideUtility(jill, IHost, 'jill only')
>>> component.queryUtility(IHost, 'jill only').goodmorning()
'Good morning, Jill!'

ComponentLookupError? is raised from component.getUtility function when look up fails. See this example for a comparison of both methods:

>>> component.queryUtility(IHost, 'anonymous')
>>> component.queryUtility(IHost, 'anonymous', 'not found')
'not found'
>>> component.getUtility(IHost, 'anonymous')
... # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ComponentLookupError: (<InterfaceClass ...IHost>, 'anonymous')

If third argument is not given for component.queryUtility, None is returned. And if it is given the third argument is returned . There is no third argument for component.getUtility.

In Zope 3 you can use ZCML for registering utilities instead of component.provideUtility function. In this example it is assumed that the package is zguide and there is host.py with Host implementation and interfaces.py with definition of IHost?.

So in configure.zcml you can write like this:

<utility
    component="zguide.host.Host"
    provides="zguide.interfaces.IHost"
    name="jill only"
    />

Adapters

Adapters could be considered the glue components of the architecture, since they connect one interface with another and allow various advanced programming techniques such as Aspect-Oriented Programming. An adapter takes a component implementing one interface and uses this object to provide another interface. This allows the developer to split up the functionality into small API pieces and keep the functionality manageable.

Here again you are going to use the IHost? interface:

>>> class IHost(interface.Interface):
...     """A host object"""
...
...     def goodmorning():
...         """Say good morning"""

And see this IGuest? interface and an implementation of IHost?:

>>> class IGuest(interface.Interface):
...     name = interface.Attribute("Name")

>>> class Host(object):
...
...     component.adapts(IGuest)
...     interface.implements(IHost)
...
...     def __init__(self, guest):
...         self.guest = guest
...
...     def goodmorning(self):
...         return "Good morning, %s!" % self.guest.name

Note the component.adapts function make this class an adapter class. Here Host implements IHost? interface and adapts the IGuest? interface. So if you have an object which provides IGuest? interface, then you query for an adapter which provides IHost?. To query, first register the Host class using component.provideAdapter function:

>>> component.provideAdapter(Host)

Now implement IGuest? interface and create an object:

>>> class Guest(object):
...     interface.implements(IGuest)
...
...     def __init__(self, name):
...         self.name = name

>>> jack = Guest("Jack")

Now query the adapter like this:

>>> component.queryAdapter(jack, IHost).goodmorning()
'Good morning, Jack!'

or like this:

>>> IHost(jack).goodmorning()
'Good morning, Jack!'

Now look in to another implementation of IHost?, this is another host which says good morning in another style:

>>> class NiceHost(object):
...     interface.implements(IHost)
...
...     def __init__(self, guest):
...         self.guest = guest
...
...     def goodmorning(self):
...         return "Hello %s! Good morning!" % self.guest.name

>>> component.provideAdapter(NiceHost, [IGuest], IHost, 'nice')

Try to query for adapter:

>>> component.queryAdapter(jack, IHost).goodmorning()
'Good morning, Jack!'
>>> component.queryAdapter(jack, IHost, '').goodmorning()
'Good morning, Jack!'

But the out put is same as above, why? . To get the desired output, you have to specify the name explicitly for named adapters:

>>> component.queryAdapter(jack, IHost, 'nice').goodmorning()
'Hello Jack! Good morning!'
>>> component.getAdapter(jack, IHost, 'nice').goodmorning()
'Hello Jack! Good morning!'

An adapter can adapt more that one interfaces, this is called multi-adapter:

>>> class IRoom(interface.Interface):
...     number = interface.Attribute("Room number")

>>> class SuperHost(object):
...     component.adapts(IGuest, IRoom)
...     interface.implements(IHost)
...
...     def __init__(self, guest, room):
...         self.guest = guest
...         self.room = room
...
...     def goodmorning(self):
...         return "Good morning, %s! Your room number is %s" % (self.guest.name,
...                 self.room.number)

Registering the multi-adapter is same as a simple adapter:

>>> component.provideAdapter(SuperHost)

To test this, first create a room object:

>>> class Room(object):
...     interface.implements(IRoom)
...
...     def __init__(self, number):
...         self.number = number
>>> room14 = Room(14)

Use component.queryMultiAdapter or component.getMultiAdapter to query/get the adapter:

>>> component.queryMultiAdapter((jack, room14),
...                                  IHost).goodmorning()
'Good morning, Jack! Your room number is 14'

You will see that BrowserView? is a multi-adapter, there is context and request. For example:

>>> from zope.app.publisher.browser import BrowserView

>>> class MyView(BrowserView):
...
...     def __init__(self, context, request):
...         self.context = context
...         self.request = request

More about adapters

Subscription adapters:

>>> class IGuard(interface.Interface):
...
...     def check():
...         """Check visitors"""

>>> class SecurityGuard(object):
...     component.adapts(IGuest)
...     interface.implements(IGuard)
...
...     def __init__(self, guest):
...         self.guest = guest
...
...     def check(self):
...         return "Please show me your card, %s" % self.guest.name

>>> component.provideSubscriptionAdapter(SecurityGuard)

>>> component.subscribers([jack], IGuard)[0].check()
'Please show me your card, Jack'

Event subscribers (Handlers):

>>> import datetime

>>> class IGuestArrived(interface.Interface):
...     guest = interface.Attribute("The guest has arrived")

>>> class GuestArrived(object):
...     interface.implements(IGuestArrived)
...
...     def __init__(self, guest):
...         self.guest = guest

>>> @component.adapter(IGuestArrived)
... def guestArrived(event):
...     event.guest.arrived = datetime.datetime.utcnow()

>>> component.provideHandler(guestArrived)

>>> component.handle(GuestArrived(jack))
>>> jack.arrived.__class__.__name__
  'datetime'

... --gardsted, Mon, 18 Dec 2006 08:37:19 -0800 reply

ZopeGuideComponents notes:

Nice work, Baiju Muthukadan, here are my comments as I read on...

When first reading about Utilities: As a new reader, i could use some explanation of what these utilities are good for. or a pointer to some other doc explaining this.

Editing in the enterpreter (>>>) I might have needed a pointer to doc about how to be able to use python prompt with zope and it's libraries.

Also: my interpreters didn't allow me to put in blank lines inside the definition of a class when entering it interactively (i tried 2.4.4 on linux and 2.5 on windows).

All in all: I am lost for now - I have failed to understand the smart things about adapters and utilities ... So I will seek knowledge elsewhere and return later...

kind regards jørgen

... --Austin, Thu, 11 Sep 2008 02:35:30 -0400 reply

The following is incomplete:

"So in configure.zcml you can write like this:

<utility
component="zguide.host.Host" provides="zguide.interfaces.IHost?" name="jill only" />

"

Because if you look just above that bit, you have:

"component.provideUtility(jill, IHost?, 'jill only')"

where the object "jill" is instantiated and registered. However, in the zcml there is no instantiation. So what is going on here?

... --nadako, Thu, 11 Sep 2008 04:43:05 -0400 reply

That's a mistake I guess. You should provide a path to an instantiated object to the "component" parameter of the "utility" directive, I believe. Or you can pass "factory" parameter to the "utility" directive, but it will be called without any parameters, so it will create Host with empty "guest" name. :-)

... --Austin, Thu, 11 Sep 2008 07:36:22 -0400 reply

So how do you do that exactly?

Do I do the instantiation in an __init__.py file?

The API docs on ZCML are unclear.

... --nadako, Thu, 11 Sep 2008 08:20:17 -0400 reply

I personally do it different ways depending on situation.

Most times I just create the object where I need it and specify it in "component" param of "utility" directive. The module will be imported anyway and the object will be created, so there's no need to create object in __init__.py, just do it where you want it to live.

I also use "factory" param instead of "component" when I have simple component without any initializer arguments and I can't imagine a situation when the created object can be used directly (not by utility lookup, but by importing it into python code). That way my modules don't get polluted by extra global objects that are never used directly.