[Zope3-checkins] CVS: Zope3/src/zope/app/fssync - fspickle.py:1.2 committer.py:1.19 classes.py:1.15

Jim Fulton jim at zope.com
Sun Sep 21 13:32:42 EDT 2003


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

Modified Files:
	committer.py classes.py 
Added Files:
	fspickle.py 
Log Message:
Changed the way objects are xml-pickled to handle parent references.


=== Zope3/src/zope/app/fssync/fspickle.py 1.1 => 1.2 ===
--- /dev/null	Sun Sep 21 13:32:41 2003
+++ Zope3/src/zope/app/fssync/fspickle.py	Sun Sep 21 13:32:11 2003
@@ -0,0 +1,203 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Pickle support functions for fssync.
+
+The functions here generate pickles that understand their location in
+the object tree without causing the entire tree to be stored in the
+pickle.  Persistent objects stored inside the outermost object are
+stored entirely in the pickle, and objects stored outside by outermost
+object but referenced from within are stored as persistent references.
+The parent of the outermost object is treated specially so that the
+pickle can be 'unpacked' with a new parent to create a copy in the new
+location; unpacking a pickle containing a parent reference requires
+passing an object to use as the parent as the second argument to the
+loads() function.  The name of the outermost object is not stored in
+the pickle unless it is stored in the object.
+
+>>> root = location.TLocation()
+>>> zope.interface.directlyProvides(root, IContainmentRoot)
+>>> o1 = DataLocation('o1', root, 12)
+>>> o2 = DataLocation('o2', root, 24)
+>>> o3 = DataLocation('o3', o1, 36)
+>>> o4 = DataLocation('o4', o3, 48)
+>>> o1.foo = o2
+
+>>> s = dumps(o1)
+>>> c1 = loads(s, o1.__parent__)
+>>> c1 is not o1
+1
+>>> c1.data == o1.data
+1
+>>> c1.__parent__ is o1.__parent__
+1
+>>> c1.foo is o2
+1
+>>> c3 = c1.o3
+>>> c3 is o3
+0
+>>> c3.__parent__ is c1
+1
+>>> c3.data == o3.data
+1
+>>> c4 = c3.o4
+>>> c4 is o4
+0
+>>> c4.data == o4.data
+1
+>>> c4.__parent__ is c3
+1
+
+$Id$
+"""
+
+import cPickle
+
+from cStringIO import StringIO
+
+import zope.interface
+
+from zope.app import location
+from zope.app import zapi
+from zope.app.interfaces.location import ILocation
+from zope.app.interfaces.traversing import IContainmentRoot
+from zope.app.interfaces.traversing import ITraverser
+
+
+PARENT_MARKER = ".."
+
+# We're not ready to use protocol 2 yet; this can be changed when
+# zope.xmlpickle.ppml gets updated to support protocol 2.
+PICKLE_PROTOCOL = 1
+
+
+def dumps(ob):
+    parent = getattr(ob, '__parent__', None)
+    if parent is None:
+        return cPickle.dumps(ob)
+    sio = StringIO()
+    persistent = ParentPersistentIdGenerator(ob)
+    p = cPickle.Pickler(sio, PICKLE_PROTOCOL)
+    p.persistent_id = persistent.id
+    p.dump(ob)
+    data = sio.getvalue()
+    return data
+
+def loads(data, parent=None):
+    if parent is None:
+        return cPickle.loads(data)
+    sio = StringIO(data)
+    persistent = ParentPersistentLoader(parent)
+    u = cPickle.Unpickler(sio)
+    u.persistent_load = persistent.load
+    return u.load()
+
+
+class ParentPersistentIdGenerator:
+    """
+
+    >>> from zope.app.location import TLocation
+    >>> root = TLocation()
+    >>> zope.interface.directlyProvides(root, IContainmentRoot)
+    >>> o1 = TLocation(); o1.__parent__ = root; o1.__name__ = 'o1'
+    >>> o2 = TLocation(); o2.__parent__ = root; o2.__name__ = 'o2'
+    >>> o3 = TLocation(); o3.__parent__ = o1; o3.__name__ = 'o3'
+    >>> root.o1 = o1
+    >>> root.o2 = o2
+    >>> o1.foo = o2
+    >>> o1.o3 = o3
+
+    >>> gen = ParentPersistentIdGenerator(o1)
+    >>> gen.id(root)
+    '..'
+    >>> gen.id(o2)
+    u'/o2'
+    >>> gen.id(o3)
+    >>> gen.id(o1)
+
+    >>> gen = ParentPersistentIdGenerator(o3)
+    >>> gen.id(root)
+    u'/'
+
+    """
+
+    def __init__(self, top):
+        self.location = top
+        self.parent = getattr(top, "__parent__", None)
+        self.root = location.LocationPhysicallyLocatable(top).getRoot()
+
+    def id(self, object):
+        if ILocation.isImplementedBy(object):
+            if location.inside(object, self.location):
+                return None
+            elif object is self.parent:
+                # XXX emit special parent marker
+                return PARENT_MARKER
+            elif location.inside(object, self.root):
+                return location.LocationPhysicallyLocatable(object).getPath()
+            raise ValueError(
+                "object implementing ILocation found outside tree")
+        else:
+            return None
+
+
+class ParentPersistentLoader:
+    """
+    >>> from zope.app.location import TLocation
+    >>> root = TLocation()
+    >>> zope.interface.directlyProvides(root, IContainmentRoot)
+    >>> o1 = TLocation(); o1.__parent__ = root; o1.__name__ = 'o1'
+    >>> o2 = TLocation(); o2.__parent__ = root; o2.__name__ = 'o2'
+    >>> o3 = TLocation(); o3.__parent__ = o1; o3.__name__ = 'o3'
+    >>> root.o1 = o1
+    >>> root.o2 = o2
+    >>> o1.foo = o2
+    >>> o1.o3 = o3
+
+    >>> loader = ParentPersistentLoader(o1)
+    >>> loader.load(PARENT_MARKER) is o1
+    1
+    >>> loader.load('/') is root
+    1
+    >>> loader.load('/o2') is o2
+    1
+
+    """
+
+    def __init__(self, parent):
+        self.parent = parent
+        self.root = location.LocationPhysicallyLocatable(parent).getRoot()
+        self.traverse = zapi.getAdapter(self.root, ITraverser).traverse
+
+    def load(self, path):
+        if path[:1] == u"/":
+            # outside object:
+            if path == "/":
+                return self.root
+            else:
+                return self.traverse(path[1:])
+        elif path == PARENT_MARKER:
+            return self.parent
+        raise ValueError("unknown persistent object reference: %r" % path)
+
+
+class DataLocation(location.TLocation):
+    """Sample data container class used in doctests."""
+
+    def __init__(self, name, parent, data):
+        self.__name__ = name
+        self.__parent__ = parent
+        if parent is not None:
+            setattr(parent, name, self)
+        self.data = data
+        super(DataLocation, self).__init__()


=== Zope3/src/zope/app/fssync/committer.py 1.18 => 1.19 ===
--- Zope3/src/zope/app/fssync/committer.py:1.18	Fri Sep  5 14:41:16 2003
+++ Zope3/src/zope/app/fssync/committer.py	Sun Sep 21 13:32:11 2003
@@ -19,22 +19,23 @@
 import os
 
 from zope.component import getAdapter, getService
-from zope.xmlpickle import loads
 from zope.configuration.name import resolve
-from zope.proxy import removeAllProxies
-
-from zope.fssync.metadata import Metadata
 from zope.fssync import fsutil
+from zope.fssync.metadata import Metadata
+from zope.proxy import removeAllProxies
+from zope.xmlpickle import fromxml
 
+from zope.app import zapi
+from zope.app.fssync import fspickle
+from zope.app.interfaces.container import IContainer
 from zope.app.interfaces.fssync import IObjectDirectory, IObjectFile
-
-from zope.app.context import ContextWrapper
-from zope.app.interfaces.container import IContainer, IZopeContainer
+from zope.app.interfaces.container import IContainer
 from zope.app.traversing import traverseName, getName
 from zope.app.interfaces.file import IFileFactory, IDirectoryFactory
 from zope.app.event import publish
 from zope.app.event.objectevent import ObjectCreatedEvent
 from zope.app.event.objectevent import ObjectModifiedEvent
+from zope.app.container.contained import contained
 
 class SynchronizationError(Exception):
     pass
@@ -344,7 +345,7 @@
         # A given factory overrides everything
         factory = resolve(factory_name)
         obj = factory()
-        obj = ContextWrapper(obj, container, name=name)
+        obj = contained(obj, container, name=name)
         adapter = get_adapter(obj)
         if IObjectFile.isImplementedBy(adapter):
             data = read_file(fspath)
@@ -382,8 +383,10 @@
                 obj = factory(name, None, data)
                 obj = removeAllProxies(obj)
             else:
-                # Oh well, assume the file is an xml pickle
-                obj = load_file(fspath)
+                # The file must contain an xml pickle, or we can't load it:
+                s = read_file(fspath)
+                s = fromxml(s)
+                obj = fspickle.loads(s, container)
 
     set_item(container, name, obj, replace)
 
@@ -392,33 +395,18 @@
     if IContainer.isImplementedBy(container):
         if not replace:
             publish(container, ObjectCreatedEvent(obj))
-        container = getAdapter(container, IZopeContainer)
         if replace:
             del container[name]
-        newname = container.setObject(name, obj)
-        if newname != name:
-            raise SynchronizationError(
-                "Container generated new name for %s (new name %s)" %
-                (name, newname))
-    else:
-        # Not a container, must be a mapping
-        # (This is used for extras and annotations)
-        container[name] = obj
+
+    container[name] = obj
 
 def delete_item(container, name):
     """Helper to delete an item from a container or mapping."""
-    if IContainer.isImplementedBy(container):
-        container = getAdapter(container, IZopeContainer)
     del container[name]
 
-def load_file(fspath):
-    """Helper to load an xml pickle from a file."""
-    return loads(read_file(fspath, "r"))
-
-def read_file(fspath, mode="rb"):
+def read_file(fspath):
     """Helper to read the data from a file."""
-    assert mode in ("r", "rb")
-    f = open(fspath, mode)
+    f = open(fspath, "rb")
     try:
         data = f.read()
     finally:


=== Zope3/src/zope/app/fssync/classes.py 1.14 => 1.15 ===
--- Zope3/src/zope/app/fssync/classes.py:1.14	Tue Sep  2 16:32:09 2003
+++ Zope3/src/zope/app/fssync/classes.py	Sun Sep 21 13:32:11 2003
@@ -16,10 +16,11 @@
 $Id$
 """
 
+from zope.app.fssync import fspickle
 from zope.app.interfaces.fssync import IObjectFile
 from zope.app.interfaces.annotation import IAnnotations
 from zope.component import queryAdapter
-from zope.xmlpickle import dumps
+from zope.xmlpickle import toxml
 from zope.proxy import removeAllProxies
 from zope.interface import implements
 
@@ -95,7 +96,8 @@
 
     def getBody(self):
         "See IObjectFile"
-        return dumps(self.context)
+        s = fspickle.dumps(self.context)
+        return toxml(s)
 
     def setBody(self, body):
         "See IObjectFile"




More information about the Zope3-Checkins mailing list