Module
zope.app.container.constraints

Support for containment constraints

Either a container or an object can provide constraints on the containment relationship.

A container expresses constraints through a precondition on it's __setitem__ method in it's interface.

Preconditions can be simple callable objects, like functions. They should raise a zope.interface.Invalid exception to indicate that a constraint isn't satisfied:

>>> def preNoZ(container, name, ob):
...     "Silly precondition example"
...     if name.startswith("Z"):
...         raise zope.interface.Invalid("Names can not start with Z")
>>> class I1(zope.interface.Interface):
...     def __setitem__(name, on):
...         "Add an item"
...     __setitem__.precondition = preNoZ
>>> from zope.app.container.interfaces import IContainer
>>> class C1(object):
...     zope.interface.implements(I1, IContainer)
...     def __repr__(self):
...         return 'C1'

Given such a precondition, we can then check whether an object can be added:

>>> c1 = C1()
>>> checkObject(c1, "bob", None)
>>> checkObject(c1, "Zbob", None)
Traceback (most recent call last):
...
Invalid: Names can not start with Z

We can also express constaints on the containers an object can be added to. We do this by setting a field constraint on an object's __parent__ attribute:

>>> import zope.schema

A field constraint is a callable object that returns a boolean value:

>>> def con1(container):
...     "silly container constraint"
...     if not hasattr(container, 'x'):
...         return False
...     return True
>>> class I2(zope.interface.Interface):
...     __parent__ = zope.schema.Field(constraint = con1)
>>> class O(object):
...     zope.interface.implements(I2)

If the constraint isn't satisfied, we'll get a validation error when we check whether the object can be added:

>>> checkObject(c1, "bob", O())
Traceback (most recent call last):
...
ConstraintNotSatisfied: C1

Note that the validation error isn't very informative. For that reason, it's better for constraints to raise Invalid errors when they aren't satisfied:

>>> def con1(container):
...     "silly container constraint"
...     if not hasattr(container, 'x'):
...         raise zope.interface.Invalid("What, no x?")
...     return True
>>> class I2(zope.interface.Interface):
...     __parent__ = zope.schema.Field(constraint = con1)
>>> class O(object):
...     zope.interface.implements(I2)
>>> checkObject(c1, "bob", O())
Traceback (most recent call last):
...
Invalid: What, no x?
>>> c1.x = 1
>>> checkObject(c1, "bob", O())

The checkObject function is handy when checking whether we can add an existing object to a container, but, sometimes, we want to check whether an object produced by a factory can be added. To do this, we use checkFactory:

>>> class Factory(object):
...     def __call__(self):
...         return O()
...     def getInterfaces(self):
...         return zope.interface.implementedBy(O)
>>> factory = Factory()
>>> checkFactory(c1, "bob", factory)
True
>>> del c1.x
>>> checkFactory(c1, "bob", factory)
False

Unlike checkObject, checkFactory:

The container constraint we defined for C1 isn't actually used to check the factory:

>>> c1.x = 1
>>> checkFactory(c1, "Zbob", factory)
True

To work with checkFactory, a container precondition has to implement a factory method. This is because a factory, rather than an object is passed. To illustrate this, we'll make preNoZ its own factory method:

>>> preNoZ.factory = preNoZ

We can do this (silly thing) because preNoZ doesn't use the object argument.

>>> checkFactory(c1, "Zbob", factory)
False

$Id: constraints.py 73547 2007-03-25 09:03:04Z dobe $