[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