[Zope3-checkins] CVS: Zope3/src/zope/app/container -
	constraints.py:1.1
    Jim Fulton 
    cvs-admin at zope.org
       
    Mon Dec  1 11:19:21 EST 2003
    
    
  
Update of /cvs-repository/Zope3/src/zope/app/container
In directory cvs.zope.org:/tmp/cvs-serv11238/src/zope/app/container
Added Files:
	constraints.py 
Log Message:
Added facilities for expressing and testing containment constraints.
=== Added File Zope3/src/zope/app/container/constraints.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""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
   >>> class C1:
   ...     zope.interface.implements(I1)
   ...     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:
   ...     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):
   ...
   ValidationError: (u'Constraint not satisfied', 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:
   ...     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:
   >>> checkFactory(c1, "bob", O)
   True
   >>> del c1.x
   >>> checkFactory(c1, "bob", O)
   False
   
   Unlike checkObject, checkFactory:
   - Returns a boolean value
   - Takes a factory (e.g. a class) rather than an argument.
   The container constraint we defined for C1 isn't actually used to
   check the factory:
       
   >>> c1.x = 1
   >>> checkFactory(c1, "bob", O)
   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.  Ti illistrate this, we'll make preNoZ it's
   own factory method:
   >>> preNoZ.factory = preNoZ
   We can do this (silly thing) because preNoZ doesn't use the object
   argument.
   
   >>> checkFactory(c1, "bob", O)
   False
   $Id: constraints.py,v 1.1 2003/12/01 16:19:21 jim Exp $
   """
import zope.interface
from zope.app.interfaces.container import InvalidItemType, InvalidContainerType
def checkObject(container, name, object):
    """Check containement constraints for an object and container
    """
    
    # check __setitem__ precondition
    for iface in zope.interface.providedBy(container):
        __setitem__ = iface.get('__setitem__')
        if __setitem__ is not None:
            precondition = __setitem__.queryTaggedValue('precondition')
            if precondition is not None:
                precondition(container, name, object)
            break
    # check the constraint on __parent__
    for iface in zope.interface.providedBy(object):
        __parent__ = iface.get('__parent__')
        if __parent__ is not None:
            try:
                validate = __parent__.validate
            except AttributeError:
                pass
            else:
                validate(container)
            break
def checkFactory(container, name, factory):
    for iface in zope.interface.providedBy(container):
        __setitem__ = iface.get('__setitem__')
        if __setitem__ is not None:
            precondition = __setitem__.queryTaggedValue('precondition')
            if precondition is not None:
                try:
                    precondition = precondition.factory
                except AttributeError:
                    pass
                else:
                    if not precondition(container, name, factory):
                        return False
            break
    # check the constraint on __parent__
    for iface in zope.interface.implementedBy(factory):
        __parent__ = iface.get('__parent__')
        if __parent__ is not None:
            try:
                validate = __parent__.validate
            except AttributeError:
                pass
            else:
                try:
                    validate(container)
                except zope.interface.Invalid:
                    return False
            break
    return True
    
class IItemTypePrecondition(zope.interface.Interface):
    def __call__(container, name, object):
        """Test whether container setitem arguments are valid.
        Raise zope.interface.Invalid if the objet is invalid.
        """
    def factory(container, name, factory):
        """Test whether objects provided by the factory are acceptable
        Return a boolean value.
        """
class ItemTypePrecondition:
    """Specify a __setitem__ precondition that restricts item types
    Items must be one of the given types.  
    >>> class I1(zope.interface.Interface):
    ...     pass
    >>> class I2(zope.interface.Interface):
    ...     pass
    >>> precondition = ItemTypePrecondition(I1, I2)
    >>> class Ob:
    ...     pass
    >>> ob = Ob()
    
    >>> try:
    ...     precondition(None, 'foo', ob)
    ... except InvalidItemType, v:
    ...     print v[0], (v[1] is ob), (v[2] == (I1, I2))
    ... else:
    ...     print 'Should have failed'
    None True True
    
    >>> try:
    ...     precondition.factory(None, 'foo', Ob)
    ... except InvalidItemType, v:
    ...     print v[0], (v[1] is Ob), (v[2] == (I1, I2))
    ... else:
    ...     print 'Should have failed'
    None True True
    >>> zope.interface.classImplements(Ob, I2)
    >>> precondition(None, 'foo', ob)
    >>> precondition.factory(None, 'foo', Ob)
    """ 
    zope.interface.implements(IItemTypePrecondition)
    def __init__(self, *types):
        self.types = types
    def __call__(self, container, name, object):
        for iface in self.types:
            if iface.isImplementedBy(object):
                return
        raise InvalidItemType(container, object, self.types)
    def factory(self, container, name, factory):
        implemented = zope.interface.implementedBy(factory)
        
        for iface in self.types:
            if implemented.isOrExtends(iface):
               return
        raise InvalidItemType(container, factory, self.types)
        
class ContainerTypesConstraint:
    """Constrain a container to be one of a number of types
    >>> class I1(zope.interface.Interface):
    ...     pass
    >>> class I2(zope.interface.Interface):
    ...     pass
    >>> class Ob:
    ...     pass
    >>> ob = Ob()
    >>> constraint = ContainerTypesConstraint(I1, I2)
    >>> try:
    ...     constraint(ob)
    ... except InvalidContainerType, v:
    ...     print (v[0] is ob), (v[1] == (I1, I2))
    ... else:
    ...     print 'Should have failed'
    True True
    >>> zope.interface.classImplements(Ob, I2)
    >>> constraint(Ob())
       
    """ 
    def __init__(self, *types):
        self.types = types
    def __call__(self, object):
       for iface in self.types:
           if iface.isImplementedBy(object):
               return
       else:
           raise InvalidContainerType(object, self.types)
    
    
More information about the Zope3-Checkins
mailing list