[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