[Zope3-checkins] CVS: Zope3/src/zope/app/fssync - fspickle.py:1.1.2.1 classes.py:1.14.2.1 committer.py:1.18.2.3

Fred L. Drake, Jr. fred at zope.com
Fri Sep 12 12:31:25 EDT 2003


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

Modified Files:
      Tag: parentgeddon-branch
	classes.py committer.py 
Added Files:
      Tag: parentgeddon-branch
	fspickle.py 
Log Message:
when serializing objects using XML pickles, handle parent references as
relative to the object being serialized, and other references to external
objects as absolute path references


=== Added File Zope3/src/zope/app/fssync/fspickle.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.
#
##############################################################################
"""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: fspickle.py,v 1.1.2.1 2003/09/12 16:30:54 fdrake Exp $
"""

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/classes.py 1.14 => 1.14.2.1 ===
--- Zope3/src/zope/app/fssync/classes.py:1.14	Tue Sep  2 16:32:09 2003
+++ Zope3/src/zope/app/fssync/classes.py	Fri Sep 12 12:30:54 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"


=== Zope3/src/zope/app/fssync/committer.py 1.18.2.2 => 1.18.2.3 ===
--- Zope3/src/zope/app/fssync/committer.py:1.18.2.2	Mon Sep  8 18:36:53 2003
+++ Zope3/src/zope/app/fssync/committer.py	Fri Sep 12 12:30:54 2003
@@ -18,18 +18,17 @@
 
 import os
 
-from zope.app import zapi
 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.interfaces.fssync import IObjectDirectory, IObjectFile
-
+from zope.app import zapi
+from zope.app.fssync import fspickle
 from zope.app.interfaces.container import IContainer, IRemoveSource, IAddTarget
+from zope.app.interfaces.fssync import IObjectDirectory, IObjectFile
 from zope.app.traversing import traverseName, getName
 from zope.app.interfaces.file import IFileFactory, IDirectoryFactory
 from zope.app.event import publish
@@ -383,8 +382,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)
 
@@ -415,14 +416,9 @@
     else:
         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:




More information about the Zope3-Checkins mailing list