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 $