[Zope3-checkins] CVS: Zope3/src/zope/app - location.py:1.2

Jim Fulton jim at zope.com
Sun Sep 21 13:30:14 EDT 2003


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

Added Files:
	location.py 
Log Message:
Added a location-support module.


=== Zope3/src/zope/app/location.py 1.1 => 1.2 ===
--- /dev/null	Sun Sep 21 13:30:13 2003
+++ Zope3/src/zope/app/location.py	Sun Sep 21 13:30:13 2003
@@ -0,0 +1,497 @@
+##############################################################################
+#
+# 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$
+"""
+from __future__ import generators
+import zope.interface
+from zope.app import zapi
+from zope.app.interfaces.location 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.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 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, 2)
+    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)
+                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 implemeny 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




More information about the Zope3-Checkins mailing list