[Checkins] SVN: Sandbox/malthe/ Spun off from zope.app.container.

Malthe Borch mborch at gmail.com
Tue Apr 22 08:01:21 EDT 2008


Log message for revision 85588:
  Spun off from zope.app.container.

Changed:
  A   Sandbox/malthe/
  A   Sandbox/malthe/zope.container/
  A   Sandbox/malthe/zope.container/.installed.cfg
  A   Sandbox/malthe/zope.container/CHANGES.txt
  A   Sandbox/malthe/zope.container/README.txt
  A   Sandbox/malthe/zope.container/bootstrap.py
  A   Sandbox/malthe/zope.container/buildout.cfg
  A   Sandbox/malthe/zope.container/setup.py
  A   Sandbox/malthe/zope.container/src/
  A   Sandbox/malthe/zope.container/src/zope/
  A   Sandbox/malthe/zope.container/src/zope/__init__.py
  A   Sandbox/malthe/zope.container/src/zope/container/
  A   Sandbox/malthe/zope.container/src/zope/container/__init__.py
  A   Sandbox/malthe/zope.container/src/zope/container/constraints.py
  A   Sandbox/malthe/zope.container/src/zope/container/constraints.txt
  A   Sandbox/malthe/zope.container/src/zope/container/find.py
  A   Sandbox/malthe/zope.container/src/zope/container/i18n.py
  A   Sandbox/malthe/zope.container/src/zope/container/interfaces.py
  A   Sandbox/malthe/zope.container/src/zope/container/property.py
  A   Sandbox/malthe/zope.container/src/zope/container/property.txt
  A   Sandbox/malthe/zope.container/src/zope/container/size.py
  A   Sandbox/malthe/zope.container/src/zope/container/tests/
  A   Sandbox/malthe/zope.container/src/zope/container/tests/__init__.py
  A   Sandbox/malthe/zope.container/src/zope/container/tests/test_constraints.py
  A   Sandbox/malthe/zope.container/src/zope/container/tests/test_find.py
  A   Sandbox/malthe/zope.container/src/zope/container/tests/test_property.py
  A   Sandbox/malthe/zope.container/src/zope/container/tests/test_size.py

-=-
Added: Sandbox/malthe/zope.container/.installed.cfg
===================================================================
--- Sandbox/malthe/zope.container/.installed.cfg	                        (rev 0)
+++ Sandbox/malthe/zope.container/.installed.cfg	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,50 @@
+[buildout]
+installed_develop_eggs = 
+parts = test
+
+[test]
+__buildout_installed__ = /Users/mborch/co/zope.container/parts/test
+	/Users/mborch/co/zope.container/bin/test
+__buildout_signature__ = zc.recipe.testrunner-1.0.0-py2.5.egg zc.recipe.egg-1.0.0-py2.5.egg setuptools-0.6c8-py2.5.egg zope.testing-3.5.1-py2.5.egg zc.buildout-1.0.1-py2.5.egg zc.buildout-1.0.1-py2.5.egg
+_b = /Users/mborch/co/zope.container/bin
+_d = /Users/mborch/co/zope.container/develop-eggs
+_e = /Users/mborch/Development/sources/buildout
+bin-directory = /Users/mborch/co/zope.container/bin
+develop-eggs-directory = /Users/mborch/co/zope.container/develop-eggs
+eggs = zope.container
+eggs-directory = /Users/mborch/Development/sources/buildout
+executable = /opt/local/bin/python2.5
+index = http://download.zope.org/ppix
+location = /Users/mborch/co/zope.container/parts/test
+recipe = zc.recipe.testrunner
+script = /Users/mborch/co/zope.container/bin/test
+
+[buildout]
+installed_develop_eggs = 
+
+[buildout]
+parts = test
+
+[buildout]
+installed_develop_eggs = 
+
+[buildout]
+parts = test
+
+[buildout]
+installed_develop_eggs = 
+
+[buildout]
+parts = test
+
+[buildout]
+installed_develop_eggs = 
+
+[buildout]
+parts = test
+
+[buildout]
+installed_develop_eggs = 
+
+[buildout]
+parts = test

Added: Sandbox/malthe/zope.container/CHANGES.txt
===================================================================
--- Sandbox/malthe/zope.container/CHANGES.txt	                        (rev 0)
+++ Sandbox/malthe/zope.container/CHANGES.txt	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,9 @@
+=======
+CHANGES
+=======
+
+3.6.0 (unreleased)
+------------------
+
+- Initial creation of package based on parts in ``zope.app.container``
+  that do not depend on the ZODB.

Added: Sandbox/malthe/zope.container/README.txt
===================================================================
--- Sandbox/malthe/zope.container/README.txt	                        (rev 0)
+++ Sandbox/malthe/zope.container/README.txt	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,2 @@
+This package define interfaces of container components and implements
+container constraints and utilities.

Added: Sandbox/malthe/zope.container/bootstrap.py
===================================================================
--- Sandbox/malthe/zope.container/bootstrap.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/bootstrap.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 77225 2007-06-29 09:20:13Z dobe $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+try:
+    import pkg_resources
+except ImportError:
+    ez = {}
+    exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                         ).read() in ez
+    ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)
+

Added: Sandbox/malthe/zope.container/buildout.cfg
===================================================================
--- Sandbox/malthe/zope.container/buildout.cfg	                        (rev 0)
+++ Sandbox/malthe/zope.container/buildout.cfg	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,8 @@
+[buildout]
+develop = .
+parts = test
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = zope.container
+

Added: Sandbox/malthe/zope.container/setup.py
===================================================================
--- Sandbox/malthe/zope.container/setup.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/setup.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,66 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Setup for zope.app.container package
+
+$Id: setup.py 81389 2007-11-01 21:09:32Z srichter $
+"""
+import os
+from setuptools import setup, find_packages, Extension
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+setup(name='zope.container',
+      version = '3.6.0',
+      author='Zope Corporation and Contributors',
+      author_email='zope3-dev at zope.org',
+      description='Zope Container',
+      long_description=(
+          read('README.txt')
+          + '\n\n' +
+          'Detailed Documentation\n'
+          '**********************\n'
+          + '\n\n' +
+          read('src', 'zope', 'container', 'constraints.txt')
+          + '\n\n' +
+          read('CHANGES.txt')
+          ),
+      keywords = "zope3 container",
+      classifiers = [
+          'Development Status :: 5 - Production/Stable',
+          'Environment :: Web Environment',
+          'Intended Audience :: Developers',
+          'License :: OSI Approved :: Zope Public License',
+          'Programming Language :: Python',
+          'Natural Language :: English',
+          'Operating System :: OS Independent',
+          'Topic :: Internet :: WWW/HTTP',
+          'Framework :: Zope3'],
+      url='http://cheeseshop.python.org/pypi/zope.container',
+      license='ZPL 2.1',
+      packages=find_packages('src'),
+      package_dir = {'': 'src'},
+      namespace_packages=['zope', ],
+      install_requires=['setuptools',
+                        'zope.interface',
+                        'zope.component',
+                        'zope.size',
+                        'zope.dottedname',
+                        'zope.location',
+                        'zope.lifecycleevent',
+                        'zope.deprecation',
+                        ],
+      include_package_data = True,
+      zip_safe = False,
+      )

Added: Sandbox/malthe/zope.container/src/zope/__init__.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/__init__.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/__init__.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+    import pkg_resources
+    pkg_resources.declare_namespace(__name__)
+except ImportError:
+    import pkgutil
+    __path__ = pkgutil.extend_path(__path__, __name__)

Added: Sandbox/malthe/zope.container/src/zope/container/__init__.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/__init__.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/__init__.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1 @@
+#

Added: Sandbox/malthe/zope.container/src/zope/container/constraints.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/constraints.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/constraints.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,462 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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
+
+   >>> from zope.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`:
+
+   - 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, "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 $
+   """
+__docformat__ = 'restructuredtext'
+
+import sys
+
+
+from zope.dottedname.resolve import resolve
+import zope.schema
+from zope.interface import providedBy
+from zope.container.interfaces import InvalidItemType, InvalidContainerType
+from zope.container.interfaces import IContainer
+from zope.container.i18n import ZopeMessageFactory as _
+from zope.container.property import readproperty
+
+def checkObject(container, name, object):
+    """Check containement constraints for an object and container
+    """
+
+    # check __setitem__ precondition
+    containerProvided = providedBy(container)
+    __setitem__ = containerProvided.get('__setitem__')
+    if __setitem__ is not None:
+        precondition = __setitem__.queryTaggedValue('precondition')
+        if precondition is not None:
+            precondition(container, name, object)
+
+    # check the constraint on __parent__
+    __parent__ = providedBy(object).get('__parent__')
+    if __parent__ is not None:
+        try:
+            validate = __parent__.validate
+        except AttributeError:
+            pass
+        else:
+            validate(container)
+
+
+    if not containerProvided.extends(IContainer):
+        # If it doesn't implement IContainer, it can't contain stuff.
+        raise TypeError(
+            _('Container is not a valid Zope container.')
+            )
+
+def checkFactory(container, name, factory):
+    __setitem__ = providedBy(container).get('__setitem__')
+    if __setitem__ is not None:
+        precondition = __setitem__.queryTaggedValue('precondition')
+        if precondition is not None:
+            try:
+                precondition = precondition.factory
+            except AttributeError:
+                pass
+            else:
+                try:
+                    precondition(container, name, factory)
+                except zope.interface.Invalid:
+                    return False
+
+    # check the constraint on __parent__
+    __parent__ = factory.getInterfaces().get('__parent__')
+    if __parent__ is not None:
+        try:
+            validate = __parent__.validate
+        except AttributeError:
+            pass
+        else:
+            try:
+                validate(container)
+            except zope.interface.Invalid:
+                return False
+
+    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 object is invalid.
+        """
+
+    def factory(container, name, factory):
+        """Test whether objects provided by the factory are acceptable
+
+        Return a boolean value.
+        """
+
+
+class _TypesBased(object):
+
+    @readproperty
+    def types(self):
+        raw_types, module = self.raw_types
+        types = []
+        for t in raw_types:
+            if isinstance(t, str):
+                t = resolve(t, module)
+            types.append(t)
+
+        self.types = types
+        return types
+
+    def __init__(self, *types, **kw):
+        if [t for t in types if isinstance(t, str)]:
+            # have dotted names
+            module = kw.get('module', sys._getframe(1).f_globals['__name__'])
+            self.raw_types = types, module
+        else:
+            self.types = types
+
+
+class ItemTypePrecondition(_TypesBased):
+    """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(object):
+    ...     pass
+    >>> ob = Ob()
+
+    >>> class Factory(object):
+    ...     def __call__(self):
+    ...         return Ob()
+    ...     def getInterfaces(self):
+    ...         return zope.interface.implementedBy(Ob)
+
+    >>> factory = Factory()
+
+    >>> 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', factory)
+    ... except InvalidItemType, v:
+    ...     print v[0], (v[1] is factory), (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', factory)
+
+    """
+
+    zope.interface.implements(IItemTypePrecondition)
+
+    def __call__(self, container, name, object):
+        for iface in self.types:
+            if iface.providedBy(object):
+                return
+        raise InvalidItemType(container, object, self.types)
+
+    def factory(self, container, name, factory):
+        implemented = factory.getInterfaces()
+
+        for iface in self.types:
+            if implemented.isOrExtends(iface):
+               return
+        raise InvalidItemType(container, factory, self.types)
+
+
+def contains(*types):
+    """Declare that a container type contains only the given types
+
+    This is used within a class suite defining an interface to create
+    a __setitem__ specification with a precondition allowing only the
+    given types:
+
+      >>> class IFoo(zope.interface.Interface):
+      ...     pass
+      >>> class IBar(zope.interface.Interface):
+      ...     pass
+      >>> class IFooBarContainer(IContainer):
+      ...     contains(IFoo, IBar)
+
+      >>> __setitem__ = IFooBarContainer['__setitem__']
+      >>> __setitem__.getTaggedValue('precondition').types == (IFoo, IBar)
+      True
+
+    It is invalid to call contains outside a class suite:
+
+      >>> contains(IFoo, IBar)
+      Traceback (most recent call last):
+      ...
+      TypeError: contains not called from suite
+    """
+
+    frame = sys._getframe(1)
+    f_locals = frame.f_locals
+    f_globals = frame.f_globals
+
+    if not (f_locals is not f_globals
+            and f_locals.get('__module__')
+            and f_locals.get('__module__') == f_globals.get('__name__')
+            ):
+        raise TypeError("contains not called from suite")
+
+    def __setitem__(key, value):
+        pass
+    __setitem__.__doc__ = IContainer['__setitem__'].__doc__
+    __setitem__.precondition = ItemTypePrecondition(
+        *types,
+        **dict(module=f_globals['__name__'])
+        )
+    f_locals['__setitem__'] = __setitem__
+
+
+class IContainerTypesConstraint(zope.interface.Interface):
+
+    def __call__(object):
+        """Test whether object is valid.
+
+        Return True if valid.
+        Raise zope.interface.Invalid if the objet is invalid.
+        """
+
+
+class ContainerTypesConstraint(_TypesBased):
+    """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(object):
+    ...     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())
+    True
+
+    """
+
+    zope.interface.implements(IContainerTypesConstraint)
+
+    def __call__(self, object):
+       for iface in self.types:
+           if iface.providedBy(object):
+               return True
+       else:
+           raise InvalidContainerType(object, self.types)
+
+
+def containers(*types):
+    """Declare the container types a type can be contained in
+
+    This is used within a class suite defining an interface to create
+    a __parent__ specification with a constraint allowing only the
+    given types:
+
+      >>> class IFoo(IContainer):
+      ...     pass
+      >>> class IBar(IContainer):
+      ...     pass
+
+      >>> from zope.container.interfaces import IContained
+      >>> class IFooBarContained(IContained):
+      ...     containers(IFoo, IBar)
+
+      >>> __parent__ = IFooBarContained['__parent__']
+      >>> __parent__.constraint.types == (IFoo, IBar)
+      True
+
+    It is invalid to call containers outside a class suite:
+
+      >>> containers(IFoo, IBar)
+      Traceback (most recent call last):
+      ...
+      TypeError: containers not called from suite
+    """
+
+    frame = sys._getframe(1)
+    f_locals = frame.f_locals
+    f_globals = frame.f_globals
+
+    if not (f_locals is not f_globals
+            and f_locals.get('__module__')
+            and f_locals.get('__module__') == f_globals.get('__name__')
+            ):
+        raise TypeError("containers not called from suite")
+
+    __parent__ = zope.schema.Field(
+        constraint = ContainerTypesConstraint(
+            *types,
+            **dict(module=f_globals['__name__'])
+            )
+        )
+    f_locals['__parent__'] = __parent__
+

Added: Sandbox/malthe/zope.container/src/zope/container/constraints.txt
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/constraints.txt	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/constraints.txt	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,97 @@
+Containment constraints
+=======================
+
+Containment constraints allow us to express restrictions on the types
+of items that can be placed in containers or on the types of
+containers an item can be placed in.  We express these constraints in
+interfaces.  Let's define some container and item interfaces:
+
+    >>> from zope.container.interfaces import IContainer, IContained
+    >>> from zope.container.constraints import containers, contains
+
+    >>> class IBuddyFolder(IContainer):
+    ...     contains('.IBuddy')
+
+
+In this example, we used the contains function to declare that objects
+that provide IBuddyFolder can only contain items that provide IBuddy.
+Note that we used a string containing a dotted name for the IBuddy
+interface. This is because IBuddy hasn't been defined yet.  When we
+define IBuddy, we can use IBuddyFolder directly:
+
+    >>> class IBuddy(IContained):
+    ...     containers(IBuddyFolder)
+
+
+Now, with these interfaces in place, we can define Buddy and
+BuddyFolder classes and verify that we can put buddies in buddy
+folders:
+
+    >>> from zope import interface
+
+    >>> class Buddy:
+    ...     interface.implements(IBuddy)
+
+    >>> class BuddyFolder:
+    ...     interface.implements(IBuddyFolder)
+
+    >>> from zope.container.constraints import checkObject, checkFactory
+    >>> from zope.component.factory import Factory
+
+    >>> checkObject(BuddyFolder(), 'x', Buddy())
+    >>> checkFactory(BuddyFolder(), 'x', Factory(Buddy))
+    True
+
+If we try to use other containers or folders, we'll get errors:
+
+    >>> class Container:
+    ...     interface.implements(IContainer)
+
+    >>> class Contained:
+    ...     interface.implements(IContained)
+
+    >>> checkObject(Container(), 'x', Buddy())
+    ... # doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    InvalidContainerType: ...
+
+    >>> checkFactory(Container(), 'x', Factory(Buddy))
+    False
+
+    >>> checkObject(BuddyFolder(), 'x', Contained())
+    ... # doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    InvalidItemType: ...
+
+    >>> checkFactory(BuddyFolder(), 'x', Factory(Contained))
+    False
+
+In the example, we defined the container first and then the items.  We
+could have defined these in the opposite order:
+
+    >>> class IContact(IContained):
+    ...     containers('.IContacts')
+
+    >>> class IContacts(IContainer):
+    ...     contains(IContact)
+
+    >>> class Contact:
+    ...     interface.implements(IContact)
+
+    >>> class Contacts:
+    ...     interface.implements(IContacts)
+
+    >>> checkObject(Contacts(), 'x', Contact())
+
+    >>> checkFactory(Contacts(), 'x', Factory(Contact))
+    True
+
+    >>> checkObject(Contacts(), 'x', Buddy())
+    ... # doctest: +ELLIPSIS
+    Traceback (most recent call last):
+    InvalidItemType: ...
+
+    >>> checkFactory(Contacts(), 'x', Factory(Buddy))
+    False
+
+

Added: Sandbox/malthe/zope.container/src/zope/container/find.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/find.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/find.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,89 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Find Support
+
+$Id: find.py 68442 2006-06-01 12:54:41Z mj $
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.interface import implements
+from interfaces import IFind, IIdFindFilter, IObjectFindFilter
+from interfaces import IReadContainer
+
+class FindAdapter(object):
+
+    implements(IFind)
+
+    __used_for__ = IReadContainer
+
+    def __init__(self, context):
+        self._context = context
+
+    def find(self, id_filters=None, object_filters=None):
+        'See IFind'
+        id_filters = id_filters or []
+        object_filters = object_filters or []
+        result = []
+        container = self._context
+        for id, object in container.items():
+            _find_helper(id, object, container,
+                         id_filters, object_filters,
+                         result)
+        return result
+
+
+def _find_helper(id, object, container, id_filters, object_filters, result):
+    for id_filter in id_filters:
+        if not id_filter.matches(id):
+            break
+    else:
+        # if we didn't break out of the loop, all name filters matched
+        # now check all object filters
+        for object_filter in object_filters:
+            if not object_filter.matches(object):
+                break
+        else:
+            # if we didn't break out of the loop, all filters matched
+            result.append(object)
+
+    if not IReadContainer.providedBy(object):
+        return
+
+    container = object
+    for id, object in container.items():
+        _find_helper(id, object, container, id_filters, object_filters, result)
+
+class SimpleIdFindFilter(object):
+
+    implements(IIdFindFilter)
+
+    def __init__(self, ids):
+        self._ids = ids
+
+    def matches(self, id):
+        'See INameFindFilter'
+        return id in self._ids
+    
+class SimpleInterfacesFindFilter(object):
+    """Filter objects on the provided interfaces"""
+    implements(IObjectFindFilter)
+    
+    def __init__(self, *interfaces):
+        self.interfaces = interfaces
+    
+    def matches(self, object):
+        for iface in self.interfaces:
+            if iface.providedBy(object):
+                return True
+        return False

Added: Sandbox/malthe/zope.container/src/zope/container/i18n.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/i18n.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/i18n.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,22 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Customization of zope.i18n for the Zope application server
+
+$Id: i18n.py 73547 2007-03-25 09:03:04Z dobe $
+"""
+__docformat__ = 'restructuredtext'
+
+# import this as _ to create i18n messages in the zope domain
+from zope.i18nmessageid import MessageFactory
+ZopeMessageFactory = MessageFactory('zope')

Added: Sandbox/malthe/zope.container/src/zope/container/interfaces.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/interfaces.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/interfaces.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,335 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Container-related interfaces
+
+$Id: interfaces.py 85414 2008-04-16 02:10:55Z fdrake $
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.deprecation import deprecated
+
+from zope.interface import Interface, Attribute, Invalid
+from zope.component.interfaces import IView, IObjectEvent
+from zope.interface.common.mapping import IItemMapping
+from zope.interface.common.mapping import IReadMapping, IEnumerableMapping
+from zope.location.interfaces import ILocation
+from zope.lifecycleevent.interfaces import IObjectModifiedEvent
+
+class DuplicateIDError(KeyError):
+    pass
+
+
+class ContainerError(Exception):
+    """An error of a container with one of its components."""
+
+
+class InvalidContainerType(Invalid, TypeError):
+    """The type of a container is not valid."""
+
+
+class InvalidItemType(Invalid, TypeError):
+    """The type of an item is not valid."""
+
+
+class InvalidType(Invalid, TypeError):
+    """The type of an object is not valid."""
+
+
+class IContained(ILocation):
+    """Objects contained in containers."""
+
+
+class IItemContainer(IItemMapping):
+    """Minimal readable container."""
+
+
+class ISimpleReadContainer(IItemContainer, IReadMapping):
+    """Readable content containers."""
+
+
+class IReadContainer(ISimpleReadContainer, IEnumerableMapping):
+    """Readable containers that can be enumerated."""
+
+
+class IWriteContainer(Interface):
+    """An interface for the write aspects of a container."""
+
+    def __setitem__(name, object):
+        """Add the given `object` to the container under the given name.
+
+        Raises a ``TypeError`` if the key is not a unicode or ascii string.
+        Raises a ``ValueError`` if key is empty.
+
+        The container might choose to add a different object than the
+        one passed to this method.
+
+        If the object doesn't implement `IContained`, then one of two
+        things must be done:
+
+        1. If the object implements `ILocation`, then the `IContained`
+           interface must be declared for the object.
+
+        2. Otherwise, a `ContainedProxy` is created for the object and
+           stored.
+
+        The object's `__parent__` and `__name__` attributes are set to the
+        container and the given name.
+
+        If the old parent was ``None``, then an `IObjectAddedEvent` is
+        generated, otherwise, an `IObjectMovedEvent` is generated.  An
+        `IContainerModifiedEvent` is generated for the container.
+
+        If the object replaces another object, then the old object is
+        deleted before the new object is added, unless the container
+        vetos the replacement by raising an exception.
+
+        If the object's `__parent__` and `__name__` were already set to
+        the container and the name, then no events are generated and
+        no hooks.  This allows advanced clients to take over event
+        generation.
+
+        """
+
+    def __delitem__(name):
+        """Delete the named object from the container.
+
+        Raises a ``KeyError`` if the object is not found.
+
+        If the deleted object's `__parent__` and `__name__` match the
+        container and given name, then an `IObjectRemovedEvent` is
+        generated and the attributes are set to ``None``. If the object
+        can be adapted to `IObjectMovedEvent`, then the adapter's
+        `moveNotify` method is called with the event.
+
+        Unless the object's `__parent__` and `__name__` attributes were
+        initially ``None``, generate an `IContainerModifiedEvent` for the
+        container.
+
+        If the object's `__parent__` and `__name__` were already set to
+        ``None``, then no events are generated.  This allows advanced
+        clients to take over event generation.
+
+        """
+
+
+class IItemWriteContainer(IWriteContainer, IItemContainer):
+    """A write container that also supports minimal reads."""
+
+
+class IContainer(IReadContainer, IWriteContainer):
+    """Readable and writable content container."""
+
+
+class IBTreeContainer(IContainer):
+    """Container that supports BTree semantics for some methods."""
+
+    def items(key=None):
+        """Return an iterator over the key-value pairs in the container.
+
+        If ``None`` is passed as `key`, this method behaves as if no argument
+        were passed; exactly as required for ``IContainer.items()``.
+
+        If `key` is in the container, the first item provided by the iterator
+        will correspond to that key.  Otherwise, the first item will be for
+        the key that would come next if `key` were in the container.
+
+        """
+
+    def keys(key=None):
+        """Return an iterator over the keys in the container.
+
+        If ``None`` is passed as `key`, this method behaves as if no argument
+        were passed; exactly as required for ``IContainer.keys()``.
+
+        If `key` is in the container, the first key provided by the iterator
+        will be that key.  Otherwise, the first key will be the one that would
+        come next if `key` were in the container.
+
+        """
+
+    def values(key=None):
+        """Return an iterator over the values in the container.
+
+        If ``None`` is passed as `key`, this method behaves as if no argument
+        were passed; exactly as required for ``IContainer.values()``.
+
+        If `key` is in the container, the first value provided by the iterator
+        will correspond to that key.  Otherwise, the first value will be for
+        the key that would come next if `key` were in the container.
+
+        """
+
+
+class IOrderedContainer(IContainer):
+    """Containers whose contents are maintained in order."""
+
+    def updateOrder(order):
+        """Revise the order of keys, replacing the current ordering.
+
+        order is a list or a tuple containing the set of existing keys in
+        the new order. `order` must contain ``len(keys())`` items and cannot
+        contain duplicate keys.
+
+        Raises ``TypeError`` if order is not a tuple or a list.
+
+        Raises ``ValueError`` if order contains an invalid set of keys.
+        """
+
+
+class IContainerNamesContainer(IContainer):
+    """Containers that always choose names for their items."""
+
+
+##############################################################################
+# Moving Objects
+
+class IObjectMovedEvent(IObjectEvent):
+    """An object has been moved."""
+
+    oldParent = Attribute("The old location parent for the object.")
+    oldName = Attribute("The old location name for the object.")
+    newParent = Attribute("The new location parent for the object.")
+    newName = Attribute("The new location name for the object.")
+
+
+##############################################################################
+# Adding objects
+
+class UnaddableError(ContainerError):
+    """An object cannot be added to a container."""
+
+    def __init__(self, container, obj, message=""):
+        self.container = container
+        self.obj = obj
+        self.message = message and ": %s" % message
+
+    def __str__(self):
+        return ("%(obj)s cannot be added "
+                "to %(container)s%(message)s" % self.__dict__)
+
+
+class IObjectAddedEvent(IObjectMovedEvent):
+    """An object has been added to a container."""
+
+
+class IAdding(IView):
+
+    def add(content):
+        """Add content object to container.
+
+        Add using the name in `contentName`.  Returns the added object
+        in the context of its container.
+
+        If `contentName` is already used in container, raises
+        ``DuplicateIDError``.
+        """
+
+    contentName = Attribute(
+         """The content name, as usually set by the Adder traverser.
+
+         If the content name hasn't been defined yet, returns ``None``.
+
+         Some creation views might use this to optionally display the
+         name on forms.
+         """
+         )
+
+    def nextURL():
+        """Return the URL that the creation view should redirect to.
+
+        This is called by the creation view after calling add.
+
+        It is the adder's responsibility, not the creation view's to
+        decide what page to display after content is added.
+        """
+
+    def nameAllowed():
+        """Return whether names can be input by the user."""
+
+    def addingInfo():
+        """Return add menu data as a sequence of mappings.
+
+        Each mapping contains 'action', 'title', and possibly other keys.
+
+        The result is sorted by title.
+        """
+
+    def isSingleMenuItem():
+        """Return whether there is single menu item or not."""
+
+    def hasCustomAddView():
+        "This should be called only if there is `singleMenuItem` else return 0"
+
+
+class INameChooser(Interface):
+
+    def checkName(name, object):
+        """Check whether an object name is valid.
+
+        Raises a user error if the name is not valid.
+        """
+
+    def chooseName(name, object):
+        """Choose a unique valid name for the object
+
+        The given name and object may be taken into account when
+        choosing the name.
+        """
+
+
+##############################################################################
+# Removing objects
+
+
+class IObjectRemovedEvent(IObjectMovedEvent):
+    """An object has been removed from a container."""
+
+
+##############################################################################
+# Modifying containers
+
+
+class IContainerModifiedEvent(IObjectModifiedEvent):
+    """The container has been modified.
+
+    This event is specific to "containerness" modifications, which means
+    addition, removal or reordering of sub-objects.
+    """
+
+
+##############################################################################
+# Finding objects
+
+class IFind(Interface):
+    """
+    Find support for containers.
+    """
+
+    def find(id_filters=None, object_filters=None):
+        """Find object that matches all filters in all sub-objects.
+
+        This container itself is not included.
+        """
+
+
+class IObjectFindFilter(Interface):
+
+    def matches(object):
+        """Return True if the object matches the filter criteria."""
+
+
+class IIdFindFilter(Interface):
+
+    def matches(id):
+        """Return True if the id matches the filter criteria."""

Added: Sandbox/malthe/zope.container/src/zope/container/property.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/property.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/property.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,106 @@
+##############################################################################
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+# 
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+##############################################################################
+"""Cached properties
+
+See the CachedProperty class.
+
+$Id: property.py 75652 2007-05-09 13:11:30Z zagy $
+"""
+
+ncaches = 0l
+
+
+class CachedProperty(object):
+    """Cached Properties.
+    """
+
+    def __init__(self, func, *names):
+        global ncaches
+        ncaches += 1
+        self.data = (func, names,
+                     "_v_cached_property_key_%s" % ncaches,
+                     "_v_cached_property_value_%s" % ncaches)
+
+    def __get__(self, inst, class_):
+        if inst is None:
+            return self
+
+        func, names, key_name, value_name = self.data
+
+        key = names and [getattr(inst, name) for name in names]
+        value = getattr(inst, value_name, self)
+
+        if value is not self:
+            # We have a cached value
+            if key == getattr(inst, key_name, self):
+                # Cache is still good!
+                return value
+
+        # We need to compute and cache the value
+
+        value = func(inst)
+        setattr(inst, key_name, key)
+        setattr(inst, value_name, value)
+
+        return value
+
+
+class Lazy(object):
+    """Lazy Attributes.
+    """
+
+    def __init__(self, func, name=None):
+        if name is None:
+            name = func.__name__
+        self.data = (func, name)
+
+    def __get__(self, inst, class_):
+        if inst is None:
+            return self
+
+        func, name = self.data
+        value = func(inst)
+        inst.__dict__[name] = value
+
+        return value
+
+
+class readproperty(object):
+
+    def __init__(self, func):
+        self.func = func
+
+    def __get__(self, inst, class_):
+        if inst is None:
+            return self
+
+        func = self.func
+        return func(inst)
+
+
+class cachedIn(object):
+    """Cached property with given cache attribute."""
+
+    def __init__(self, attribute_name):
+        self.attribute_name = attribute_name
+
+    def __call__(self, func):
+
+        def get(instance):
+            try:
+                value = getattr(instance, self.attribute_name)
+            except AttributeError:
+                value = func(instance)
+                setattr(instance, self.attribute_name, value)
+            return value
+
+        return property(get)

Added: Sandbox/malthe/zope.container/src/zope/container/property.txt
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/property.txt	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/property.txt	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,208 @@
+Cached Properties
+=================
+
+Cached properties are computed properties that cache their computed
+values.  They take into account instance attributes that they depend
+on, so when the instance attributes change, the properties will change
+the values they return.
+
+Cached properties cache their data in _v_ attributes, so they are
+also useful for managing the computation of volatile attributes for
+persistent objects. Let's look at an example::
+
+    >>> from zope.container import property
+    >>> import math
+
+    >>> class Point:
+    ... 
+    ...     def __init__(self, x, y):
+    ...         self.x, self.y = x, y
+    ...
+    ...     def radius(self):
+    ...         print 'computing radius'
+    ...         return math.sqrt(self.x**2 + self.y**2)
+    ...     radius = property.CachedProperty(radius, 'x', 'y')
+
+    >>> point = Point(1.0, 2.0)   
+
+If we ask for the radius the first time::
+
+    >>> '%.2f' % point.radius
+    computing radius
+    '2.24'
+
+We see that the radius function is called, but if we ask for it again::
+
+    >>> '%.2f' % point.radius
+    '2.24'
+
+The function isn't called.  If we change one of the attribute the
+radius depends on, it will be recomputed::
+
+    >>> point.x = 2.0
+    >>> '%.2f' % point.radius
+    computing radius
+    '2.83'
+
+But changing other attributes doesn't cause recomputation::
+
+    >>> point.q = 1
+    >>> '%.2f' % point.radius
+    '2.83'
+
+Note that we don't have any non-volitile attributes added::
+
+    >>> names = [name for name in point.__dict__ if not name.startswith('_v_')]
+    >>> names.sort()
+    >>> names
+    ['q', 'x', 'y']
+
+
+Lazy Computed Attributes
+------------------------
+
+The `property` module provides another descriptor that supports a
+slightly different caching model: lazy attributes.  Like cached
+proprties, they are computed the first time they are used. however,
+they aren't stored in volatile attributes and they aren't
+automatically updated when other attributes change.  Furthermore, the
+store their data using their attribute name, thus overriding
+themselves. This provides much faster attribute access after the
+attribute has been computed. Let's look at the previous example using
+lazy attributes::
+
+    >>> class Point:
+    ... 
+    ...     def __init__(self, x, y):
+    ...         self.x, self.y = x, y
+    ...
+    ...     def radius(self):
+    ...         print 'computing radius'
+    ...         return math.sqrt(self.x**2 + self.y**2)
+    ...     radius = property.Lazy(radius)
+
+    >>> point = Point(1.0, 2.0)   
+
+If we ask for the radius the first time::
+
+    >>> '%.2f' % point.radius
+    computing radius
+    '2.24'
+
+We see that the radius function is called, but if we ask for it again::
+
+    >>> '%.2f' % point.radius
+    '2.24'
+
+The function isn't called.  If we change one of the attribute the
+radius depends on, it still isn't called::
+    
+    >>> point.x = 2.0
+    >>> '%.2f' % point.radius
+    '2.24'
+
+If we want the radius to be recomputed, we have to manually delete it::
+
+    >>> del point.radius
+
+    >>> point.x = 2.0
+    >>> '%.2f' % point.radius
+    computing radius
+    '2.83'
+
+Note that the radius is stored in the instance dictionary::
+
+    >>> '%.2f' % point.__dict__['radius']
+    '2.83'
+
+The lazy attribute needs to know the attribute name.  It normally
+deduces the attribute name from the name of the function passed. If we
+want to use a different name, we need to pass it::
+
+    >>> def d(point):
+    ...     print 'computing diameter'
+    ...     return 2*point.radius
+
+    >>> Point.diameter = property.Lazy(d, 'diameter')
+    >>> '%.2f' % point.diameter
+    computing diameter
+    '5.66'
+
+
+readproperties
+==============
+
+readproperties are like lazy computed attributes except that the
+attribute isn't set by the property::
+
+
+    >>> class Point:
+    ... 
+    ...     def __init__(self, x, y):
+    ...         self.x, self.y = x, y
+    ...
+    ...     def radius(self):
+    ...         print 'computing radius'
+    ...         return math.sqrt(self.x**2 + self.y**2)
+    ...     radius = property.readproperty(radius)
+
+    >>> point = Point(1.0, 2.0)   
+
+    >>> '%.2f' % point.radius
+    computing radius
+    '2.24'
+
+    >>> '%.2f' % point.radius
+    computing radius
+    '2.24'
+
+But you *can* replace the property by setting a value. This is the major
+difference to the builtin `property`::
+
+    >>> point.radius = 5
+    >>> point.radius
+    5
+
+
+cachedIn
+========
+
+The `cachedIn` property allows to specify the attribute where to store the
+computed value::
+
+    >>> class Point:
+    ... 
+    ...     def __init__(self, x, y):
+    ...         self.x, self.y = x, y
+    ...
+    ...     @property.cachedIn('_radius_attribute')
+    ...     def radius(self):
+    ...         print 'computing radius'
+    ...         return math.sqrt(self.x**2 + self.y**2)
+    
+    >>> point = Point(1.0, 2.0)   
+
+    >>> '%.2f' % point.radius
+    computing radius
+    '2.24'
+
+    >>> '%.2f' % point.radius
+    '2.24'
+
+The radius is cached in the attribute with the given name, `_radius_attribute`
+in this case::
+
+    >>> '%.2f' % point._radius_attribute
+    '2.24'
+
+When the attribute is removed the radius is re-calculated once. This allows
+invalidation::
+
+    >>> del point._radius_attribute
+
+    >>> '%.2f' % point.radius
+    computing radius
+    '2.24'
+
+    >>> '%.2f' % point.radius
+    '2.24'

Added: Sandbox/malthe/zope.container/src/zope/container/size.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/size.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/size.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,41 @@
+
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Adapters that give the size of an object.
+
+$Id: size.py 73547 2007-03-25 09:03:04Z dobe $
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.container.i18n import ZopeMessageFactory as _
+from zope.size.interfaces import ISized
+from zope.interface import implements
+
+class ContainerSized(object):
+
+    implements(ISized)
+
+    def __init__(self, container):
+        self._container = container
+
+    def sizeForSorting(self):
+        """See `ISized`"""
+        return ('item', len(self._container))
+
+    def sizeForDisplay(self):
+        """See `ISized`"""
+        num_items = len(self._container)
+        if num_items == 1:
+            return _('1 item')
+        return _('${items} items', mapping={'items': str(num_items)})

Added: Sandbox/malthe/zope.container/src/zope/container/tests/__init__.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/tests/__init__.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/tests/__init__.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1 @@
+#

Added: Sandbox/malthe/zope.container/src/zope/container/tests/test_constraints.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/tests/test_constraints.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/tests/test_constraints.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,34 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Container constraint tests
+
+$Id: test_constraints.py 40495 2005-12-02 17:51:22Z efge $
+"""
+import unittest
+from zope.testing import doctest, module
+
+def setUp(test):
+    module.setUp(test, 'zope.container.constraints_txt')
+
+def tearDown(test):
+    module.tearDown(test, 'zope.container.constraints_txt')
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocTestSuite('zope.container.constraints'),
+        doctest.DocFileSuite('../constraints.txt',
+                             setUp=setUp, tearDown=tearDown),
+        ))
+
+if __name__ == '__main__': unittest.main()

Added: Sandbox/malthe/zope.container/src/zope/container/tests/test_find.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/tests/test_find.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/tests/test_find.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,174 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Find functionality tests
+
+$Id: test_find.py 68442 2006-06-01 12:54:41Z mj $
+"""
+from unittest import TestCase, main, makeSuite
+from zope.container.interfaces import IReadContainer
+from zope.container.interfaces import IObjectFindFilter
+from zope.container.find import FindAdapter, SimpleIdFindFilter
+from zope.container.find import SimpleInterfacesFindFilter
+from zope.interface import implements, Interface, directlyProvides
+
+class FakeContainer(object):
+    implements(IReadContainer)
+
+    def __init__(self, id, objects):
+        self._id = id
+        self._objects = objects
+
+    def keys(self):
+        return [object._id for object in self._objects]
+
+    def values(self):
+        return self._objects
+
+    def items(self):
+        return [(object._id, object) for object in self._objects]
+
+    def __getitem__(self, id):
+        for object in self._objects:
+            if object._id == id:
+                return object
+        raise KeyError("Could not find %s" % id)
+
+    def get(self, id, default=None):
+        for object in self._objects:
+            if object._id == id:
+                return object
+
+        return default
+
+    def __contains__(self, id):
+        for object in self._objects:
+            if object.id == id:
+                return True
+        return False
+
+    def __len__(self):
+        return len(self._objects)
+    
+class FakeInterfaceFoo(Interface):
+    """Test interface Foo"""
+    
+class FakeInterfaceBar(Interface):
+    """Test interface Bar"""
+    
+class FakeInterfaceSpam(Interface):
+    """Test interface Spam"""
+
+class TestObjectFindFilter(object):
+    implements(IObjectFindFilter)
+
+    def __init__(self, count):
+        self._count = count
+
+    def matches(self, object):
+        if IReadContainer.providedBy(object):
+            return len(object) == self._count
+        else:
+            return False
+
+class Test(TestCase):
+    def test_idFind(self):
+        alpha = FakeContainer('alpha', [])
+        delta = FakeContainer('delta', [])
+        beta = FakeContainer('beta', [delta])
+        gamma = FakeContainer('gamma', [])
+        tree = FakeContainer(
+            'tree',
+            [alpha, beta, gamma])
+        find = FindAdapter(tree)
+        # some simple searches
+        result = find.find([SimpleIdFindFilter(['beta'])])
+        self.assertEquals([beta], result)
+        result = find.find([SimpleIdFindFilter(['gamma'])])
+        self.assertEquals([gamma], result)
+        result = find.find([SimpleIdFindFilter(['delta'])])
+        self.assertEquals([delta], result)
+        # we should not find the container we search on
+        result = find.find([SimpleIdFindFilter(['tree'])])
+        self.assertEquals([], result)
+        # search for multiple ids
+        result = find.find([SimpleIdFindFilter(['alpha', 'beta'])])
+        self.assertEquals([alpha, beta], result)
+        result = find.find([SimpleIdFindFilter(['beta', 'delta'])])
+        self.assertEquals([beta, delta], result)
+        # search without any filters, find everything
+        result = find.find([])
+        self.assertEquals([alpha, beta, delta, gamma], result)
+        # search for something that doesn't exist
+        result = find.find([SimpleIdFindFilter(['foo'])])
+        self.assertEquals([], result)
+        # find for something that has two ids at the same time,
+        # can't ever be the case
+        result = find.find([SimpleIdFindFilter(['alpha']),
+                            SimpleIdFindFilter(['beta'])])
+        self.assertEquals([], result)
+
+    def test_objectFind(self):
+        alpha = FakeContainer('alpha', [])
+        delta = FakeContainer('delta', [])
+        beta = FakeContainer('beta', [delta])
+        gamma = FakeContainer('gamma', [])
+        tree = FakeContainer(
+            'tree',
+            [alpha, beta, gamma])
+        find = FindAdapter(tree)
+        result = find.find(object_filters=[TestObjectFindFilter(0)])
+        self.assertEquals([alpha, delta, gamma], result)
+        result = find.find(object_filters=[TestObjectFindFilter(1)])
+        self.assertEquals([beta], result)
+        result = find.find(object_filters=[TestObjectFindFilter(2)])
+        self.assertEquals([], result)
+
+    def test_combinedFind(self):
+        alpha = FakeContainer('alpha', [])
+        delta = FakeContainer('delta', [])
+        beta = FakeContainer('beta', [delta])
+        gamma = FakeContainer('gamma', [])
+        tree = FakeContainer(
+            'tree',
+            [alpha, beta, gamma])
+        find = FindAdapter(tree)
+        result = find.find(id_filters=[SimpleIdFindFilter(['alpha'])],
+                           object_filters=[TestObjectFindFilter(0)])
+        self.assertEquals([alpha], result)
+
+        result = find.find(id_filters=[SimpleIdFindFilter(['alpha'])],
+                           object_filters=[TestObjectFindFilter(1)])
+        self.assertEquals([], result)
+        
+    def test_interfaceFind(self):
+        alpha = FakeContainer('alpha', [])
+        directlyProvides(alpha, FakeInterfaceBar)
+        delta = FakeContainer('delta', [])
+        directlyProvides(delta, FakeInterfaceFoo)
+        beta = FakeContainer('beta', [delta])
+        directlyProvides(beta, FakeInterfaceSpam)
+        gamma = FakeContainer('gamma', [])
+        tree = FakeContainer(
+            'tree',
+            [alpha, beta, gamma])
+        find = FindAdapter(tree)
+        result = find.find(object_filters=[
+            SimpleInterfacesFindFilter(FakeInterfaceFoo, FakeInterfaceSpam)])
+        self.assertEqual([beta, delta], result)
+
+def test_suite():
+    return makeSuite(Test)
+
+if __name__=='__main__':
+    main(defaultTest='test_suite')

Added: Sandbox/malthe/zope.container/src/zope/container/tests/test_property.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/tests/test_property.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/tests/test_property.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,33 @@
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Container constraint tests
+
+$Id: test_constraints.py 40495 2005-12-02 17:51:22Z efge $
+"""
+import unittest
+from zope.testing import doctest, module
+
+def setUp(test):
+    module.setUp(test, 'zope.container.property_txt')
+
+def tearDown(test):
+    module.tearDown(test, 'zope.container.property_txt')
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('../property.txt',
+                             setUp=setUp, tearDown=tearDown),
+        ))
+
+if __name__ == '__main__': unittest.main()

Added: Sandbox/malthe/zope.container/src/zope/container/tests/test_size.py
===================================================================
--- Sandbox/malthe/zope.container/src/zope/container/tests/test_size.py	                        (rev 0)
+++ Sandbox/malthe/zope.container/src/zope/container/tests/test_size.py	2008-04-22 12:01:20 UTC (rev 85588)
@@ -0,0 +1,70 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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.
+#
+##############################################################################
+"""Test container ISized adapter.
+
+$Id: test_size.py 67630 2006-04-27 00:54:03Z jim $
+"""
+import unittest
+
+from zope.interface import implements
+from zope.size.interfaces import ISized
+from zope.container.interfaces import IContainer
+
+class DummyContainer(object):
+
+    implements(IContainer)
+
+    def __init__(self, numitems):
+        self._numitems = numitems
+
+    def __len__(self):
+        return self._numitems
+
+
+class Test(unittest.TestCase):
+
+    def testImplementsISized(self):
+        from zope.container.size import ContainerSized
+        sized = ContainerSized(DummyContainer(23))
+        self.assert_(ISized.providedBy(sized))
+
+    def testEmptyContainer(self):
+        from zope.container.size import ContainerSized
+        obj = DummyContainer(0)
+        sized = ContainerSized(obj)
+        self.assertEqual(sized.sizeForSorting(), ('item', 0))
+        self.assertEqual(sized.sizeForDisplay(), u'${items} items')
+        self.assertEqual(sized.sizeForDisplay().mapping['items'], '0')
+
+    def testOneItem(self):
+        from zope.container.size import ContainerSized
+        obj = DummyContainer(1)
+        sized = ContainerSized(obj)
+        self.assertEqual(sized.sizeForSorting(), ('item', 1))
+        self.assertEqual(sized.sizeForDisplay(), u'1 item')
+
+    def testSeveralItems(self):
+        from zope.container.size import ContainerSized
+        obj = DummyContainer(2)
+        sized = ContainerSized(obj)
+        self.assertEqual(sized.sizeForSorting(), ('item', 2))
+        self.assertEqual(sized.sizeForDisplay(), u'${items} items')
+        self.assertEqual(sized.sizeForDisplay().mapping['items'], '2')
+
+def test_suite():
+    loader = unittest.TestLoader()
+    return loader.loadTestsFromTestCase(Test)
+
+if __name__=='__main__':
+    unittest.TextTestRunner().run(test_suite())



More information about the Checkins mailing list