[Zope3-checkins] CVS: Zope3/src/zope/app/location - __init__.py:1.1 interfaces.py:1.1 tests.py:1.1

Philipp von Weitershausen philikon at philikon.de
Tue Mar 2 12:51:53 EST 2004


Update of /cvs-repository/Zope3/src/zope/app/location
In directory cvs.zope.org:/tmp/cvs-serv28764/location

Added Files:
	__init__.py interfaces.py tests.py 
Log Message:
Moved location-related code, incl. interfaces and tests, to a new
zope.app.location package.


=== Added File Zope3/src/zope/app/location/__init__.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.
#
##############################################################################
"""Classes to support implenting IContained

$Id: __init__.py,v 1.1 2004/03/02 17:51:52 philikon Exp $
"""
import zope.interface
from zope.app import zapi
from zope.app.location.interfaces import ILocation
from zope.app.interfaces.traversing import IPhysicallyLocatable
from zope.app.interfaces.traversing import IContainmentRoot
from zope.app.interfaces.traversing import ITraverser
from zope.app.interfaces.services.service import ISite
from zope.proxy import removeAllProxies
from zope.proxy import ProxyBase, getProxiedObject
from zope.app.decorator import DecoratorSpecificationDescriptor
from zope.app.decorator import DecoratedSecurityCheckerDescriptor

import cPickle
import tempfile

class Location(object):
    """Stupid mix-in that defines __parent__ and __name__ attributes
    """

    zope.interface.implements(ILocation)

    __parent__ = __name__ = None

def locate(object, parent, name=None):
    """Locate an object in another

    This method should only be called from trusted code, because it
    sets attributes that are normally unsettable.
    """

    object = removeAllProxies(object)
    object.__parent__ = parent
    object.__name__ = name



def LocationIterator(object):
    while object is not None:
        yield object
        object = getattr(object, '__parent__', None)

class LocationPhysicallyLocatable:
    """Provide location information for location objects
    """

    zope.interface.implements(IPhysicallyLocatable)

    def __init__(self, context):
        self.context = context

    def getRoot(self):
        """Get the root location for a location.

        See IPhysicallyLocatable

        The root location is a location that contains the given
        location and that implements IContainmentRoot.

        >>> root = Location()
        >>> zope.interface.directlyProvides(root, IContainmentRoot)
        >>> LocationPhysicallyLocatable(root).getRoot() is root
        1

        >>> o1 = Location(); o1.__parent__ = root
        >>> LocationPhysicallyLocatable(o1).getRoot() is root
        1

        >>> o2 = Location(); o2.__parent__ = o1
        >>> LocationPhysicallyLocatable(o2).getRoot() is root
        1

        We'll get a TypeError if we try to get the location fo a
        rootless object:

        >>> o1.__parent__ = None
        >>> LocationPhysicallyLocatable(o1).getRoot()
        Traceback (most recent call last):
        ...
        TypeError: Not enough context to determine location root
        >>> LocationPhysicallyLocatable(o2).getRoot()
        Traceback (most recent call last):
        ...
        TypeError: Not enough context to determine location root

        If we screw up and create a location cycle, it will be caught:

        >>> o1.__parent__ = o2
        >>> LocationPhysicallyLocatable(o1).getRoot()
        Traceback (most recent call last):
        ...
        TypeError: Maximim location depth exceeded, """ \
                """probably due to a a location cycle.
        """
        context = self.context
        max = 9999
        while context is not None:
            if IContainmentRoot.isImplementedBy(context):
                return context
            context = context.__parent__
            max -= 1
            if max < 1:
                raise TypeError("Maximim location depth exceeded, "
                                "probably due to a a location cycle.")

        raise TypeError("Not enough context to determine location root")

    def getPath(self):
        """Get the path of a location.

        See IPhysicallyLocatable

        This is an "absolute path", rooted at a root object.

        >>> root = Location()
        >>> zope.interface.directlyProvides(root, IContainmentRoot)
        >>> LocationPhysicallyLocatable(root).getPath()
        u'/'

        >>> o1 = Location(); o1.__parent__ = root; o1.__name__ = 'o1'
        >>> LocationPhysicallyLocatable(o1).getPath()
        u'/o1'

        >>> o2 = Location(); o2.__parent__ = o1; o2.__name__ = u'o2'
        >>> LocationPhysicallyLocatable(o2).getPath()
        u'/o1/o2'

        It is an error to get the path of a rootless location:

        >>> o1.__parent__ = None
        >>> LocationPhysicallyLocatable(o1).getPath()
        Traceback (most recent call last):
        ...
        TypeError: Not enough context to determine location root

        >>> LocationPhysicallyLocatable(o2).getPath()
        Traceback (most recent call last):
        ...
        TypeError: Not enough context to determine location root

        If we screw up and create a location cycle, it will be caught:

        >>> o1.__parent__ = o2
        >>> LocationPhysicallyLocatable(o1).getPath()
        Traceback (most recent call last):
        ...
        TypeError: Maximim location depth exceeded, """ \
                """probably due to a a location cycle.

        """

        path = []
        context = self.context
        max = 9999
        while context is not None:
            if IContainmentRoot.isImplementedBy(context):
                if path:
                    path.append('')
                    path.reverse()
                    return u'/'.join(path)
                else:
                    return u'/'
            path.append(context.__name__)
            context = context.__parent__
            max -= 1
            if max < 1:
                raise TypeError("Maximim location depth exceeded, "
                                "probably due to a a location cycle.")

        raise TypeError("Not enough context to determine location root")

    def getName(self):
        """Get a location name

        See IPhysicallyLocatable.

        >>> o1 = Location(); o1.__name__ = 'o1'
        >>> LocationPhysicallyLocatable(o1).getName()
        'o1'

        """
        return self.context.__name__

    def getNearestSite(self):
        "return the nearest site, see IPhysicallyLocatable"
        if ISite.isImplementedBy(self.context):
            return self.context
        for parent in zapi.getParents(self.context):
            if ISite.isImplementedBy(parent):
                return parent
        return self.getRoot()

def inside(l1, l2):
    """Is l1 inside l2

    L1 is inside l2 if l2 is an ancestor of l1.

    >>> o1 = Location()
    >>> o2 = Location(); o2.__parent__ = o1
    >>> o3 = Location(); o3.__parent__ = o2
    >>> o4 = Location(); o4.__parent__ = o3

    >>> inside(o1, o1)
    1
    >>> inside(o2, o1)
    1
    >>> inside(o3, o1)
    1
    >>> inside(o4, o1)
    1

    >>> inside(o1, o4)
    0

    >>> inside(o1, None)
    0

    """

    while l1 is not None:
        if l1 is l2:
            return True
        l1 = l1.__parent__

    return False


def locationCopy(loc):
    """Return a copy of an object,, and anything in it

    If object in the location refer to objects outside of the
    location, then the copies of the objects in the location refer to
    the same outside objects.

    For example, suppose we have an object (location) hierarchy like this:

           o1
          /  \
        o2    o3
        |     |
        o4    o5

    >>> o1 = Location()
    >>> o1.o2 = Location(); o1.o2.__parent__ = o1
    >>> o1.o3 = Location(); o1.o3.__parent__ = o1
    >>> o1.o2.o4 = Location(); o1.o2.o4.__parent__ = o1.o2
    >>> o1.o3.o5 = Location(); o1.o3.o5.__parent__ = o1.o3

    In addition, o3 has a non-locatin reference to o4.

    >>> o1.o3.o4 = o1.o2.o4

    When we copy o3, we should get a copy of o3 and o5, with
    references to o1 and o4.

    >>> c3 = locationCopy(o1.o3)
    >>> c3 is o1.o3
    0
    >>> c3.__parent__ is o1
    1
    >>> c3.o5 is o1.o3.o5
    0
    >>> c3.o5.__parent__ is c3
    1
    >>> c3.o4 is o1.o2.o4
    1

    """
    tmp = tempfile.TemporaryFile()
    persistent = CopyPersistent(loc)

    # Pickle the object to a temporary file
    pickler = cPickle.Pickler(tmp, 1) # XXX disable until Python 2.3.4 
    pickler.persistent_id = persistent.id
    pickler.dump(loc)

    # Now load it back
    tmp.seek(0)
    unpickler = cPickle.Unpickler(tmp)
    unpickler.persistent_load = persistent.load

    return unpickler.load()

class CopyPersistent:
    """Persistence hooks for copying locations

    See locationCopy above.

    We get initialized with an initial location:

    >>> o1 = Location()
    >>> persistent = CopyPersistent(o1)

    We provide an id function that returns None when given a non-location:

    >>> persistent.id(42)

    Or when given a location that is inside the initial location:

    >>> persistent.id(o1)
    >>> o2 = Location(); o2.__parent__ = o1
    >>> persistent.id(o2)

    But, if we get a location outside the original location, we assign
    it an id and return the id:

    >>> o3 = Location()
    >>> id3 = persistent.id(o3)
    >>> id3 is None
    0
    >>> o4 = Location()
    >>> id4 = persistent.id(o4)
    >>> id4 is None
    0
    >>> id4 is id3
    0

    If we ask for the id of an outside location more than once, we
    always get the same id back:

    >> persistent.id(o4) == id4
    1

    We also provide a load function that returns the objects for which
    we were given ids:

    >>> persistent.load(id3) is o3
    1
    >>> persistent.load(id4) is o4
    1

    """

    def __init__(self, location):
        self.location = location
        self.pids_by_id = {}
        self.others_by_pid = {}
        self.load = self.others_by_pid.get

    def id(self, object):
        if ILocation.isImplementedBy(object):
            if not inside(object, self.location):
                if id(object) in self.pids_by_id:
                    return self.pids_by_id[id(object)]
                pid = len(self.others_by_pid)

                # The following is needed to overcome a bug
                # in pickle.py. The pickle checks the boolean value
                # if the id, rather than whether it is None.
                pid += 1
                
                self.pids_by_id[id(object)] = pid
                self.others_by_pid[pid] = object
                return pid

        return None


class PathPersistent:
    """Persistence hooks for pickling locations

    See locationCopy above.

    Unlike copy persistent, we use paths for ids of outside locations
    so that we can separate pickling and unpickling in time.  We have
    to compute paths and traverse objects to load paths, but paths can
    be stored for later use, unlike the ids used by CopyPersistent.

    We require outside locations that can be adapted to ITraversable.
    To simplify the example, we'll use a simple traversable location
    defined in zope.app.location, TLocation.

    Normally, general adapters are used to make objects traversable.

    We get initialized with an initial location:

    >>> o1 = Location()
    >>> persistent = PathPersistent(o1)

    We provide an id function that returns None when given a non-location:

    >>> persistent.id(42)

    Or when given a location that is inside the initial location:

    >>> persistent.id(o1)
    >>> o2 = Location(); o2.__parent__ = o1
    >>> persistent.id(o2)

    But, if we get a location outside the original location, we return it's
    path. To compute it's path, it must be rooted:

    >>> root = TLocation()
    >>> zope.interface.directlyProvides(root, IContainmentRoot)
    >>> o3 = TLocation(); o3.__name__ = 'o3'
    >>> o3.__parent__ = root; root.o3 = o3
    >>> persistent.id(o3)
    u'/o3'

    >>> o4 = TLocation(); o4.__name__ = 'o4'
    >>> o4.__parent__ = o3; o3.o4 = o4
    >>> persistent.id(o4)
    u'/o3/o4'


    We also provide a load function that returns objects by traversing
    given paths.  It has to find the root based on the object given to
    the constructor.  Therefore, that object must also be rooted:

    >>> o1.__parent__ = root
    >>> persistent.load(u'/o3') is o3
    1
    >>> persistent.load(u'/o3/o4') is o4
    1

    """

    def __init__(self, location):
        self.location = location

    def id(self, object):
        if ILocation.isImplementedBy(object):
            if not inside(object, self.location):
                return LocationPhysicallyLocatable(object).getPath()

        return None

    def load(self, path):
        if path[:1] != u'/':
            raise ValueError("ZPersistent paths must be absolute", path)
        root = LocationPhysicallyLocatable(self.location).getRoot()
        return zapi.getAdapter(root, ITraverser).traverse(path[1:])


class LocationProxy(ProxyBase):
    """Contained-object proxy

    This is a non-picklable proxy that can be put around objects that
    don't implement ILocation.

    >>> l = [1, 2, 3]
    >>> p = LocationProxy(l, "Dad", "p")
    >>> p
    [1, 2, 3]
    >>> p.__parent__
    'Dad'
    >>> p.__name__
    'p'

    >>> import pickle
    >>> p2 = pickle.dumps(p)
    Traceback (most recent call last):
    ...
    TypeError: Not picklable


    """

    zope.interface.implements(ILocation)

    __slots__ = '__parent__', '__name__'
    __safe_for_unpickling__ = True

    def __new__(self, ob, container=None, name=None):
        return ProxyBase.__new__(self, ob)

    def __init__(self, ob, container=None, name=None):
        ProxyBase.__init__(self, ob)
        self.__parent__ = container
        self.__name__ = name

    def __reduce__(self, proto=None):
        raise TypeError, "Not picklable"

    __reduce_ex__ = __reduce__

    __providedBy__ = DecoratorSpecificationDescriptor()

    __Security_checker__ = DecoratedSecurityCheckerDescriptor()


class TLocation(Location):
    """Simple traversable location used in examples."""

    zope.interface.implements(ITraverser)

    def traverse(self, path, default=None, request=None):
        o = self
        for name in path.split(u'/'):
           o = getattr(o, name)
        return o


=== Added File Zope3/src/zope/app/location/interfaces.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.
#
##############################################################################
"""Location framework

$Id: interfaces.py,v 1.1 2004/03/02 17:51:52 philikon Exp $
"""

from zope.interface import Interface, Attribute
from zope import schema

class ILocation(Interface):
    """Objects that have a structural location
    """

    __parent__ = Attribute("The parent in the location hierarchy")

    __name__ = schema.TextLine(
        __doc__=
        """The name within the parent

        The parent can be traversed with this name to get the object.
        """)


=== Added File Zope3/src/zope/app/location/tests.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.
#
##############################################################################
"""Location support tests

$Id: tests.py,v 1.1 2004/03/02 17:51:52 philikon Exp $
"""
import unittest
from zope.testing.doctestunit import DocTestSuite


def test_suite():
    return DocTestSuite('zope.app.location')

if __name__ == '__main__':
    unittest.main(defaultTest='test_suite')




More information about the Zope3-Checkins mailing list