What is zope.lifecycleevent?

zope.lifecycleevent

Build Status Documentation Status Coverage Status

Overview

In a loosely-coupled system, events can be used by parts of the system to inform each other about relevant occurrences. The zope.event package (optionally together with zope.interface and zope.component) provides a generic mechanism to dispatch objects representing those events to interested subscribers (e.g., functions). This package defines a specific set of event objects and API functions for describing the life-cycle of objects in the system: object creation, object modification, and object removal.

Documentation is hosted at https://zopelifecycleevent.readthedocs.io

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.

Creating and Sending Events

As discussed in Quick Start, most uses of zope.lifecycleevent will be satisfied with the high level API described by IZopeLifecycleEvent, but it is possible to create and send events manually, both those defined here and your own subclasses.

Provided Events

All of the functions described in Quick Start are very simple wrappers that create an event object defined by this package and then use zope.event.notify() to send it. You can do the same, as shown below, but there is usually little reason to do so.

>>> from zope.event import notify
>>> from zope.lifecycleevent import ObjectCreatedEvent
>>> from zope.lifecycleevent import ObjectCopiedEvent
>>> from zope.lifecycleevent import ObjectModifiedEvent
>>> from zope.lifecycleevent import ObjectMovedEvent
>>> from zope.lifecycleevent import ObjectRemovedEvent
>>> obj = object()
>>> notify(ObjectCreatedEvent(obj))
>>> notify(ObjectCopiedEvent(object(), obj))
>>> notify(ObjectMovedEvent(obj,
...        None, 'oldName',
...        None, 'newName'))
>>> notify(ObjectModifiedEvent(obj, "description 1", "description 2"))
>>> notify(ObjectRemovedEvent(obj, "oldParent", "oldName"))

Subclassing Events

It can sometimes be helpful to subclass one of the provided event classes. If you then want to send a notification of that class, you must manually construct and notify it.

One reason to create a subclass is to be able to add additional attributes to the event object, perhaps changing the constructor signature in the process. Another reason to create a subclass is to be able to easily subscribe to all events that are just of that class. The class zope.container.contained.ContainerModifiedEvent is used for this reason.

For example, in an application with distinct users, we might want to let subscribers know which user created the object. We might also want to be able to distinguish between objects that are created by a user and those that are automatically created as part of system operation or administration. The following subclass lets us do both.

>>> class ObjectCreatedByEvent(ObjectCreatedEvent):
...    "A created event that tells you who created the object."
...    def __init__(self, object, created_by):
...        super(ObjectCreatedByEvent, self).__init__(object)
...        self.created_by = created_by
>>> obj = object()
>>> notify(ObjectCreatedByEvent(obj, "Black Night"))

Handling Events

This document provides information on how to handle the lifycycle events defined and sent by this package.

Background information on handling events is found in zope.event's documentation.

Class Based Handling

zope.event includes a simple framework for dispatching events based on the class of the event. This could be used to provide handlers for each of the event classes defined by this package (ObjectCreatedEvent, etc). However, it doesn’t allow configuring handlers based on the kind of object the event contains. To do that, we need another level of dispatching.

Fortunately, that level of dispatching already exists within zope.component.

Component Based Handling

zope.component includes an event dispatching framework that lets us dispatch events based not just on the kind of the event, but also on the kind of object the event contains.

All of the events defined by this package are implementations of zope.interface.interfaces.IObjectEvent. zope.component includes special support for these kinds of events. That document walks through a generic example in Python code. Here we will show an example specific to life cycle events using the type of configuration that is more likely to be used in a real application.

For this to work, it’s important that zope.component is configured correctly. Usually this is done with ZCML executed at startup time (we will be using strings in this documentation, but usually this resides in files, most often named configure.zcml):

>>> from zope.configuration import xmlconfig
>>> _ = xmlconfig.string("""
...   <configure xmlns="http://namespaces.zope.org/zope">
...     <include package="zope.component" />
...   </configure>
... """)

First we will define an object we’re interested in getting events for:

>>> 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 = ''

Next, we will write our subscriber. Normally, zope.event subscribers take just one argument, the event object. But when we use the automatic dispatching that zope.component provides, our function will receive two arguments: the object of the event, and the event. We can use the decorators that zope.component supplies to annotate the function with the kinds of arguments it wants to handle. Alternatively, we could specify that information when we register the handler with zope.component (we’ll see an example of that later).

>>> from zope.component import adapter
>>> from zope.lifecycleevent import IObjectCreatedEvent
>>> @adapter(IFile, IObjectCreatedEvent)
... def on_file_created(file, event):
...    print("A file of type '%s' was created" % (file.__class__.__name__))

Finally, we will register our handler with zope.component. This is also usually done with ZCML executed at startup time:

>>> _ = xmlconfig.string("""
...   <configure xmlns="http://namespaces.zope.org/zope">
...     <include package="zope.component" file="meta.zcml" />
...     <subscriber handler="__main__.on_file_created"/>
...   </configure>
... """)

Now we can send an event noting that a file was created, and our handler will be called:

>>> from zope.lifecycleevent import created
>>> file = File()
>>> created(file)
A file of type 'File' was created

Other types of objects don’t trigger our handler:

>>> created(object)

The hierarchy is respected, so if we define a subclass of File and indeed, even a sub-interface of IFile, our handler will be invoked.

>>> class SubFile(File): pass
>>> created(SubFile())
A file of type 'SubFile' was created
>>> class ISubFile(IFile): pass
>>> @implementer(ISubFile)
... class IndependentSubFile(object):
...     data = name = ''
>>> created(IndependentSubFile())
A file of type 'IndependentSubFile' was created

We can further register a handler just for the subinterface we created. Here we’ll also demonstrate supplying this information in ZCML.

>>> def generic_object_event(obj, event):
...    print("Got '%s' for an object of type '%s'" % (event.__class__.__name__, obj.__class__.__name__))
>>> _ = xmlconfig.string("""
...   <configure xmlns="http://namespaces.zope.org/zope">
...     <include package="zope.component" file="meta.zcml" />
...     <subscriber handler="__main__.generic_object_event"
...                 for="__main__.ISubFile zope.lifecycleevent.IObjectCreatedEvent" />
...   </configure>
... """)

Now both handlers will be called for implementations of ISubFile, but still only the original implementation will be called for base IFiles.

>>> created(IndependentSubFile())
A file of type 'IndependentSubFile' was created
Got 'ObjectCreatedEvent' for an object of type 'IndependentSubFile'
>>> created(File())
A file of type 'File' was created
Projects That Rely on Dispatched Events

Handlers for life cycle events are commonly registered with zope.component as a means for keeping projects uncoupled. This section provides a partial list of such projects for reference.

As mentioned in Quick Start, the containers provided by zope.container generally automatically send the correct life cycle events.

At a low-level, there are utilities that assign integer IDs to objects as they are created such as zope.intid and zc.intid. zc.intid, in particular, documents the way it uses events.

zope.catalog can automatically index documents as part of handling life cycle events.

Containers and Sublocations

The events ObjectAddedEvent and ObjectRemovedEvent usually need to be (eventually) sent in pairs for any given object. That is, when an added event is sent for an object, for symmetry eventually a removed event should be sent too. This makes sure that proper cleanup can happen.

Sometimes one object can be said to contain other objects. This is obvious in the case of lists, dictionaries and the container objects provided by zope.container, but the same can sometimes be said for other types of objects too that reference objects in their own attributes.

What happens when a life cycle event for such an object is sent? By default, nothing. This may leave the system in an inconsistent state.

For example, lets create a container and add some objects to it. First we’ll set up a generic event handler so we can see the events that go out.

>>> _ = xmlconfig.string("""
...   <configure xmlns="http://namespaces.zope.org/zope">
...     <include package="zope.component" file="meta.zcml" />
...     <subscriber handler="__main__.generic_object_event"
...                 for="* zope.interface.interfaces.IObjectEvent" />
...   </configure>
... """)
Got...
>>> from zope.lifecycleevent import added
>>> container = {}
>>> created(container)
Got 'ObjectCreatedEvent' for an object of type 'dict'
>>> object1 = object()
>>> container['object1'] = object1
>>> added(object1, container, 'object1')
Got 'ObjectAddedEvent' for an object of type 'object'

We can see that we got an “added” event for the object we stored in the container. What happens when we remove the container?

>>> from zope.lifecycleevent import removed
>>> tmp = container
>>> del container
>>> removed(tmp, '', '')
Got 'ObjectRemovedEvent' for an object of type 'dict'
>>> del tmp

We only got an event for the container, not the objects it contained! If the handlers that fired when we added “object1” had done anything that needed to be undone for symmetry when “object1” was removed (e.g., if it had been indexed and needed to be unindexed) the system is now corrupt because those handlers never got the ObjectRemovedEvent for “object1”.

The solution to this problem comes from zope.container. It defines the concept of ISubLocations: a way for any given object to inform other objects about the objects it contains (and it provides a default implementation of ISubLocations for containers). It also provides a function that will send events that happen to the parent object for all the child objects it contains.

In this way, its possible for any arbitrary life cycle event to automatically be propagated to its children without any specific caller of remove, say, needing to have any specific knowledge about containment relationships.

For this to work, two things must be done:

  1. Configure zope.container. This too is usually done in ZCML with <include package="zope.container"/>.
  2. Provide an adapter to ISubLocations when some object can contain other objects that need events.

Reference

Interfaces

Event-related interfaces

interface zope.lifecycleevent.interfaces.IZopeLifecycleEvent[source]

High-level functions for sending events.

These are implemented by the zope.lifecycleevent module.

created(object)

Send an IObjectCreatedEvent for object.

modified(object, *descriptions)

Send an IObjectModifiedEvent for object.

descriptions is a sequence of interfaces or fields which were updated. The IAttributes and ISequence helpers can be used.

copied(object, original)

Send an IObjectCopiedEvent for object.

original is the object the copy was created from.

moved(object, oldParent, oldName, newParent, newName)

Send an IObjectMovedEvent for object.

oldParent is the container object was removed from. oldName was the name used to store object in oldParent. newParent is the container object was added to. newName is the name used to store object in newParent.

Note that newParent and oldParent may be the same if the names are different, and vice versa.

added(object, newParent=None, newName=None)

Send an IObjectAddedEvent for object.

newParent is the container object was added to. newName is the name used to store object in the container.

If either of these is not provided or is None, they will be taken from the values of object.__parent__ or object.__name__, respectively.

removed(object, oldParent=None, oldName=None)

Send an IObjectRemovedEvent for object.

oldParent is the container object was removed from. oldName was the name used to store object in oldParent.

If either of these is not provided or is None, they will be taken from the values of object.__parent__ or object.__name__, respectively.

interface zope.lifecycleevent.interfaces.IObjectCreatedEvent[source]

Extends: zope.interface.interfaces.IObjectEvent

An object has been created.

The object attribute will commonly have a value of None for its __name__ and __parent__ values (if it has those attributes at all).

interface zope.lifecycleevent.interfaces.IObjectCopiedEvent[source]

Extends: zope.lifecycleevent.interfaces.IObjectCreatedEvent

An object has been copied.

original

The original from which the copy was made.

interface zope.lifecycleevent.interfaces.IObjectModifiedEvent[source]

Extends: zope.interface.interfaces.IObjectEvent

An object has been modified

descriptions

The supplied modification descriptions.

These may be interfaces or implementations of IModificationDescription such as Attributes or Sequence

interface zope.lifecycleevent.interfaces.IModificationDescription[source]

Marker interface for descriptions of object modifications.

Can be used as a parameter of an IObjectModifiedEvent.

interface zope.lifecycleevent.interfaces.IAttributes[source]

Extends: zope.lifecycleevent.interfaces.IModificationDescription

Describes the attributes of an interface.

interface

The involved interface.

attributes

A sequence of modified attributes.

interface zope.lifecycleevent.interfaces.ISequence[source]

Extends: zope.lifecycleevent.interfaces.IModificationDescription

Describes the modified keys of a sequence-like interface.

interface

The involved interface.

keys

A sequence of modified keys.

interface zope.lifecycleevent.interfaces.IObjectMovedEvent[source]

Extends: zope.interface.interfaces.IObjectEvent

An object has been moved.

oldParent

The old location parent for the object.

oldName

The old location name for the object.

newParent

The new location parent for the object.

newName

The new location name for the object.

interface zope.lifecycleevent.interfaces.IObjectAddedEvent[source]

Extends: zope.lifecycleevent.interfaces.IObjectMovedEvent

An object has been added to a container.

interface zope.lifecycleevent.interfaces.IObjectRemovedEvent[source]

Extends: zope.lifecycleevent.interfaces.IObjectMovedEvent

An object has been removed from a container.

Implementation

Life cycle events.

This module provides the IZopeLifecycleEvent interface, in addition to concrete classes implementing the various event interfaces.

class zope.lifecycleevent.ObjectCreatedEvent(object)[source]

Bases: zope.interface.interfaces.ObjectEvent

An object has been created

zope.lifecycleevent.created(object)[source]

Send an IObjectCreatedEvent for object.

class zope.lifecycleevent.Attributes(interface, *attributes)[source]

Describes modified attributes of an interface.

class zope.lifecycleevent.Sequence(interface, *keys)[source]

Describes modified keys of an interface.

class zope.lifecycleevent.ObjectModifiedEvent(object, *descriptions)[source]

Bases: zope.interface.interfaces.ObjectEvent

An object has been modified

Init with a list of modification descriptions.

zope.lifecycleevent.modified(object, *descriptions)[source]

Send an IObjectModifiedEvent for object.

descriptions is a sequence of interfaces or fields which were updated. The IAttributes and ISequence helpers can be used.

class zope.lifecycleevent.ObjectCopiedEvent(object, original)[source]

Bases: zope.lifecycleevent.ObjectCreatedEvent

An object has been copied

zope.lifecycleevent.copied(object, original)[source]

Send an IObjectCopiedEvent for object.

original is the object the copy was created from.

class zope.lifecycleevent.ObjectMovedEvent(object, oldParent, oldName, newParent, newName)[source]

Bases: zope.interface.interfaces.ObjectEvent

An object has been moved

zope.lifecycleevent.moved(object, oldParent, oldName, newParent, newName)[source]

Send an IObjectMovedEvent for object.

oldParent is the container object was removed from. oldName was the name used to store object in oldParent. newParent is the container object was added to. newName is the name used to store object in newParent.

Note that newParent and oldParent may be the same if the names are different, and vice versa.

class zope.lifecycleevent.ObjectAddedEvent(object, newParent=None, newName=None)[source]

Bases: zope.lifecycleevent.ObjectMovedEvent

An object has been added to a container.

If newParent or newName is not provided or is None, they will be taken from the values of object.__parent__ or object.__name__, respectively.

zope.lifecycleevent.added(object, newParent=None, newName=None)[source]

Send an IObjectAddedEvent for object.

newParent is the container object was added to. newName is the name used to store object in the container.

If either of these is not provided or is None, they will be taken from the values of object.__parent__ or object.__name__, respectively.

class zope.lifecycleevent.ObjectRemovedEvent(object, oldParent=None, oldName=None)[source]

Bases: zope.lifecycleevent.ObjectMovedEvent

An object has been removed from a container.

If oldParent or oldName is not provided or is None, they will be taken from the values of object.__parent__ or object.__name__, respectively.

zope.lifecycleevent.removed(object, oldParent=None, oldName=None)[source]

Send an IObjectRemovedEvent for object.

oldParent is the container object was removed from. oldName was the name used to store object in oldParent.

If either of these is not provided or is None, they will be taken from the values of object.__parent__ or object.__name__, respectively.

Changes

5.1 (unreleased)

  • Nothing changed yet.

5.0 (2023-07-06)

  • Drop support for Python 2.7, 3.5, 3.6.
  • Add support for Python 3.11.

4.4 (2022-05-09)

  • Add support for Python 3.8, 3,9, 3.10.
  • Drop support for Python 3.4.

4.3 (2018-10-05)

  • Add support for Python 3.7.

4.2.0 (2017-07-12)

4.1.0 (2014-12-27)

  • Add support for PyPy3.
  • Add support for Python 3.4.

4.0.3 (2013-09-12)

  • Drop the dependency on zope.component as the interface and implementation of ObjectEvent is now in zope.interface. Retained the dependency for the tests.
  • Fix: .moved tried to notify the wrong event.

4.0.2 (2013-03-08)

  • Add Trove classifiers indicating CPython and PyPy support.

4.0.1 (2013-02-11)

  • Add tox.ini.

4.0.0 (2013-02-11)

  • Test coverage at 100%.
  • Add support for Python 3.3 and PyPy.
  • Replace deprecated zope.interface.implements usage with equivalent zope.interface.implementer decorator.
  • Drop support for Python 2.4 and 2.5.

3.7.0 (2011-03-17)

  • Add convenience functions to parallel zope.lifecycleevent.modified for the other events defined in this package.

3.6.2 (2010-09-25)

  • Add not declared, but needed test dependency on zope.component [test].

3.6.1 (2010-04-30)

  • Remove dependency on undeclared zope.testing.doctest.

3.6.0 (2009-12-29)

  • Refactor tests to lose zope.annotation and zope.dublincore as dependencies.

3.5.2 (2009-05-17)

  • Copy IObjectMovedEvent, IObjectAddedEvent, IObjectRemovedEvent interfaces and ObjectMovedEvent, ObjectAddedEvent and ObjectRemovedEvent classes here from zope.container (plus tests). The intent is to allow packages that rely on these interfaces or the event classes to rely on zope.lifecycleevent (which has few dependencies) instead of zope.container (which has many).

3.5.1 (2009-03-09)

  • Remove deprecated code and therefore dependency on zope.deferredimport.
  • Change package’s mailing list address to zope-dev at zope.org, as zope3-dev at zope.org is now retired.
  • Update package’s description and documentation.

3.5.0 (2009-01-31)

  • Remove old module declarations from classes.
  • Use zope.container instead of zope.app.container.

3.4.0 (2007-09-01)

Initial release as an independent package

Development

zope.lifecycleevent is hosted at GitHub:

Project URLs

Indices and tables