Quick Start

This document describes the various event types defined by this package and provides some basic examples of using them to inform parts of the system about object changes.

All events have three components: an interface defining the event’s structure, a default implementation of that interface (the event object), and a high-level convenience function (defined by the IZopeLifecycleEvent interface) for easily sending that event in a single function call.

Note

The convenience functions are simple wrappers for constructing an event object and sending it via zope.event.notify(). Here we will only discuss using these functions; for more information on the advanced usage of when and how to construct and send event objects manually, see Creating and Sending Events.

Note

This document will not discuss actually handling these events (setting up subscribers for them). For information on that topic, see Handling Events.

We will go through the events in approximate order of how they would be used to follow the life-cycle of an object.

Creation

The first event is IObjectCreatedEvent, implemented by ObjectCreatedEvent, which is used to communicate that a single object has been created. It can be sent with the zope.lifecycleevent.created() function.

For example:

>>> from zope.lifecycleevent import created
>>> obj = {}
>>> created(obj)

Copying

Copying an object is a special case of creating one. It can happen at any time and is implemented with IObjectCopiedEvent, ObjectCopiedEvent, or the API zope.lifecycleevent.copied().

>>> from zope.lifecycleevent import copied
>>> import pickle
>>> copy = pickle.loads(pickle.dumps(obj))
>>> copied(copy, obj)

Note

Handlers for IObjectCreatedEvent can expect to receive events for IObjectCopiedEvent as well.

Addition

After objects are created, it is common to add them somewhere for storage or access. This can be accomplished with the IObjectAddedEvent and its implementation ObjectAddedEvent, or the API zope.lifecycleevent.added().

>>> from zope.lifecycleevent import ObjectAddedEvent
>>> from zope.lifecycleevent import added
>>> container = {}
>>> container['name'] = obj
>>> added(obj, container, 'name')

If the object being added has a non-None __name__ or __parent__ attribute, we can omit those values when we call added and the attributes will be used.

>>> class Location(object):
...    __parent__ = None
...    __name__ = None
>>> location = Location()
>>> location.__name__ = "location"
>>> location.__parent__ = container
>>> container[location.__name__] = location
>>> added(location)

Tip

The interface zope.location.interfaces.ILocation defines these attributes (although we don’t require the object to implement that interface), and containers that implement zope.container.interfaces.IWriteContainer are expected to set them (such containers will also automatically send the IObjectAddedEvent).

Modification

One of the most common types of events used from this package is the IObjectModifiedEvent (implemented by ObjectModifiedEvent) that represents object modification.

In the simplest case, it may be enough to simply notify interested parties that the object has changed. Like the other events, this can be done manually or through the convenience API (zope.lifecycleevent.modified()):

>>> obj['key'] = 42
>>> from zope.lifecycleevent import modified
>>> modified(obj)

Providing Additional Information

Some event consumers like indexes (catalogs) and caches may need more information to update themselves in an efficient manner. The necessary information can be provided as optional “modification descriptions” of the ObjectModifiedEvent (or again, via the modified() function).

This package doesn’t strictly define what a “modification description” must be. The most common (and thus most interoperable) descriptions are based on interfaces.

We could simply pass an interface itself to say “something about the way this object implements the interface changed”:

>>> from zope.interface import Interface, Attribute, implementer
>>> class IFile(Interface):
...     data = Attribute("The data of the file.")
...     name = Attribute("The name of the file.")
>>> @implementer(IFile)
... class File(object):
...     data = ''
...     name = ''
>>> file = File()
>>> created(file)
>>> file.data = "123"
>>> modified(file, IFile)

Attributes

We can also be more specific in a case like this where we know exactly what attribute of the interface we modified. There is a helper class zope.lifecycleevent.Attributes that assists:

>>> from zope.lifecycleevent import Attributes
>>> file.data = "abc"
>>> modified(file, Attributes(IFile, "data"))

If we modify multiple attributes of an interface at the same time, we can include that information in a single Attributes object:

>>> file.data = "123"
>>> file.name = "123.txt"
>>> modified(file, Attributes(IFile, "data", "name"))

Sometimes we may change attributes from multiple interfaces at the same time. We can also represent this by including more than one Attributes instance:

>>> import time
>>> class IModified(Interface):
...    lastModified = Attribute("The timestamp when the object was modified.")
>>> @implementer(IModified)
... class ModifiedFile(File):
...    lastModified = 0
>>> file = ModifiedFile()
>>> created(file)
>>> file.data = "abc"
>>> file.lastModified = time.time()
>>> modified(file,
...          Attributes(IFile, "data"),
...          Attributes(IModified, "lastModified"))

Sequences

When an object is a sequence or container, we can specify the individual indexes or keys that we changed using zope.lifecycleevent.Sequence.

First we’ll need to define a sequence and create an instance:

>>> from zope.interface.common.sequence import ISequence
>>> class IFileList(ISequence):
...    "A sequence of IFile objects."
>>> @implementer(IFileList)
... class FileList(list):
...   pass
>>> files = FileList()
>>> created(files)

Now we can modify the sequence by adding an object to it:

>>> files.append(File())
>>> from zope.lifecycleevent import Sequence
>>> modified(files, Sequence(IFileList, len(files) - 1))

We can also replace an existing object:

>>> files[0] = File()
>>> modified(files, Sequence(IFileList, 0))

Of course Attributes and Sequences can be combined in any order and length necessary to describe the modifications fully.

Modification Descriptions

Although this package does not require any particular definition or implementation of modification descriptions, it provides the two that we’ve already seen: Attributes and Sequence. Both of these classes implement the marker interface IModificationDescription. If you implement custom modification descriptions, consider implementing this marker interface.

Movement

Sometimes objects move from one place to another. This can be described with the interface IObjectMovedEvent, its implementation ObjectMovedEvent or the API zope.lifecycleevent.moved().

Objects may move within a single container by changing their name:

>>> from zope.lifecycleevent import moved
>>> container['new name'] = obj
>>> del container['name']
>>> moved(obj,
...       oldParent=container, oldName='name',
...       newParent=container, newName='new name')

Or they may move to a new container (under the same name, or a different name):

>>> container2 = {}
>>> container2['new name'] = obj
>>> del container['new name']
>>> moved(obj,
...       oldParent=container,  oldName='new name',
...       newParent=container2, newName='new name')

Unlike addition, any __name__ and __parent__ attribute on the object are ignored and must be provided explicitly.

Tip

Much like the addition of objects, zope.container.interfaces.IWriteContainer implementations are expected to update the __name__ and __parent__ attributes automatically, and to automatically send the appropriate movement event.

Removal

Finally, objects can be removed from the system altogether with IObjectRemovedEvent, ObjectRemovedEvent and zope.lifecycleevent.removed().

>>> from zope.lifecycleevent import removed
>>> del container2['new name']
>>> removed(obj, container2, 'new name')

Note

This is a special case of movement where the new parent and new name are always None. Handlers for IObjectMovedEvent can expect to receive events for IObjectRemovedEvent as well.

If the object being removed provides the __name__ or __parent__ attribute, those arguments can be omitted and the attributes will be used instead.

>>> location = container['location']
>>> del container[location.__name__]
>>> removed(location)

Tip

Once again, IWriteContainer implementations will send the correct event automatically.