[Zope-CVS] CVS: Products/ZopeVersionControl - container.py:1.1 IVersionControl.py:1.4 Repository.py:1.6 Version.py:1.8 ZopeRepository.py:1.4 ZopeVersion.py:1.6

Shane Hathaway shane@zope.com
Tue, 13 May 2003 18:12:32 -0400


Update of /cvs-repository/Products/ZopeVersionControl
In directory cvs.zope.org:/tmp/cvs-serv25790

Modified Files:
	IVersionControl.py Repository.py Version.py ZopeRepository.py 
	ZopeVersion.py 
Added Files:
	container.py 
Log Message:
Refined container version control.

- Added a new interface, IVersionedContainer, for separating contained
items from containers during version control operations.

- Added container.py, a module for adapting objects to
IVersionedContainer.  An object can implement this interface to
override the default container version control.

- Changed Version.py and Repository.py to use the new features in
container.py.

- Moved ObjectManager versioning to container.py.

- Renamed "object" to "obj" where I came across it.  This is for
Python 2.2+ compatibility.

- Removed unused imports.



=== Added File Products/ZopeVersionControl/container.py ===
##############################################################################
#
# Copyright (c) 2001 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
# 
##############################################################################
"""Container versioning support.

$Id: container.py,v 1.1 2003/05/13 22:12:31 shane Exp $
"""

from Acquisition import aq_base
from OFS.ObjectManager import ObjectManager

from IVersionControl import IVersionedContainer


def getContainerVCAdapter(obj):
    """Returns an IVersionedContainer for any object.

    This is a super-simplistic adapter implementation.
    """
    base = aq_base(obj)
    # If the object implements IVersionedContainer, let it say
    # what its items are.
    if IVersionedContainer.isImplementedBy(base):
        return obj
    # If the object is an ObjectManager, use the ObjectManager adapter.
    try:
        is_obj_mgr = isinstance(base, ObjectManager)
    except TypeError:
        # Python 2.1 isinstance() dislikes ExtensionClass instances.
        # This is an adequate workaround.
        pass
    else:
        if is_obj_mgr:
            return ObjectManagerVCAdapter(obj)
    # Otherwise use the do-nothing adapter.
    return DefaultVCAdapter(obj)


def getVCItems(obj):
    return getContainerVCAdapter(obj).getVCItems()

def removeVCItems(obj):
    getContainerVCAdapter(obj).removeVCItems()

def restoreVCItems(obj, dict):
    getContainerVCAdapter(obj).restoreVCItems(dict)



class DefaultVCAdapter:
    """Versioned container adapter for things that are not containers."""

    __implements__ = IVersionedContainer

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

    def getVCItems(self):
        """Returns a mapping that maps item ID to (unwrapped) item."""
        return ()

    def removeVCItems(self):
        """Removes all versionable items of this container."""
        pass

    def restoreVCItems(self, dict):
        """Restores items to this container."""
        pass


class ObjectManagerVCAdapter:
    """Versioned container adapter for object managers."""

    __implements__ = IVersionedContainer

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

    def getVCItems(self):
        """Returns a mapping that maps item ID to (unwrapped) item."""
        res = {}
        for name, value in self.obj.objectItems():
            res[name] = aq_base(value)
        return res

    def removeVCItems(self):
        """Removes all versionable items from this container."""
        obj = self.obj
        for name in obj.objectIds():
            obj._delOb(name)
        if obj._objects:
            obj._objects = ()

    def restoreVCItems(self, dict):
        """Restores items to this container."""
        # First build "ignore", a dictionary listing which
        # items of the object were stored in the repository.
        # Don't restore over those.
        obj = self.obj
        ignore = {}
        for name in obj.objectIds():
            ignore[name] = 1
        # Restore the items of the container.
        for name, value in dict.items():
            if not ignore.has_key(name):
                obj._setOb(name, aq_base(value))
                if not hasattr(obj, '_tree'):
                    # Avoid generating events, since nothing was ever really
                    # removed or added.
                    obj._objects += ({'meta_type': value.meta_type,
                                      'id': name},)
                # If there is a _tree attribute, it's very likely
                # a BTreeFolder2, which doesn't need or want the
                # _objects attribute.
                # XXX This is a hackish way to check for BTreeFolder2s.



=== Products/ZopeVersionControl/IVersionControl.py 1.3 => 1.4 ===
--- Products/ZopeVersionControl/IVersionControl.py:1.3	Tue Feb 11 16:49:23 2003
+++ Products/ZopeVersionControl/IVersionControl.py	Tue May 13 18:12:31 2003
@@ -177,7 +177,7 @@
         Permission: Use version control
         """
 
-class IVersionInfo:
+class IVersionInfo(Interface):
     """The IVersionInfo interface provides access to version control
        bookkeeping information. The fields provided by this interface
        are:
@@ -202,7 +202,7 @@
          information was created.
          """
 
-class ILogEntry:
+class ILogEntry(Interface):
     """The ILogEntry interface provides access to the information in an
        audit log entry. The fields provided by this interface are:
 
@@ -224,4 +224,21 @@
          path - the path to the object upon which the action was taken.
 
          """
+
+
+class IVersionedContainer(Interface):
+    """Container version control modifier.
+
+    Containerish objects implement this interface to allow the items they
+    contain to be versioned independently of the container.
+    """
+
+    def getVCItems():
+        """Returns a mapping that maps item ID to (unwrapped) item."""
+
+    def removeVCItems():
+        """Removes independently versioned items from this container."""
+
+    def restoreVCItems(dict):
+        """Restores versioned items to this container."""
 


=== Products/ZopeVersionControl/Repository.py 1.5 => 1.6 ===
--- Products/ZopeVersionControl/Repository.py:1.5	Tue Feb 11 16:49:23 2003
+++ Products/ZopeVersionControl/Repository.py	Tue May 13 18:12:31 2003
@@ -13,6 +13,8 @@
 
 __version__='$Revision$'[11:-2]
 
+import time
+
 from Acquisition import Implicit, aq_parent, aq_inner
 from ZopeVersionHistory import ZopeVersionHistory
 from Globals import InitializeClass, Persistent
@@ -22,8 +24,10 @@
 from DateTime.DateTime import DateTime
 from BTrees.OOBTree import OOBTree
 from BTrees.OIBTree import OIBTree
+
 from EventLog import LogEntry
-import time, Utility
+import Utility
+from container import getVCItems, restoreVCItems
 
 
 class Repository(Implicit, Persistent):
@@ -59,13 +63,17 @@
         return self._histories[history_id].__of__(self)
 
     security.declarePrivate('replaceObject')
-    def replaceObject(self, object, new_object):
+    def replaceObject(self, obj, new_obj):
         """Internal: replace a persistent object in its container."""
-        container = aq_parent(aq_inner(object))
-        item_id = object.getId()
+        subitems = getVCItems(obj)
+        container = aq_parent(aq_inner(obj))
+        item_id = obj.getId()
         container._delOb(item_id)
-        container._setOb(item_id, new_object)
-        return container._getOb(item_id)
+        container._setOb(item_id, new_obj)
+        res = container._getOb(item_id)
+        if subitems:
+            restoreVCItems(res, subitems)
+        return res
 
     #####################################################################
     # This is the implementation of the public version control interface.


=== Products/ZopeVersionControl/Version.py 1.7 => 1.8 ===
--- Products/ZopeVersionControl/Version.py:1.7	Wed Jan 15 17:17:46 2003
+++ Products/ZopeVersionControl/Version.py	Tue May 13 18:12:31 2003
@@ -24,9 +24,10 @@
 from BTrees.OOBTree import OOBTree
 
 from Utility import VersionControlError
+from container import getVCItems, removeVCItems
 
 
-def cloneByPickle(object, ignore_list=()):
+def cloneByPickle(obj, ignore_list=()):
     """Makes a copy of a ZODB object, loading ghosts as needed.
 
     Ignores specified objects along the way, replacing them with None
@@ -50,7 +51,7 @@
     stream = StringIO()
     p = Pickler(stream, 1)
     p.persistent_id = persistent_id
-    p.dump(object)
+    p.dump(obj)
     stream.seek(0)
     u = Unpickler(stream)
     u.persistent_load = persistent_load
@@ -63,7 +64,7 @@
        version is created by checking in a checked-out resource. The state
        of a version of a version-controlled resource never changes."""
 
-    def __init__(self, version_id, object):
+    def __init__(self, version_id, obj):
         self.id = version_id
         self.date_created = time.time()
         self._data = None
@@ -84,10 +85,10 @@
         return self.id
 
     security.declarePrivate('saveState')
-    def saveState(self, object):
+    def saveState(self, obj):
         """Save the state of object as the state for this version of
            a version-controlled resource."""
-        self._data = self.stateCopy(object, self)
+        self._data = self.stateCopy(obj, self)
 
     security.declarePrivate('copyState')
     def copyState(self):
@@ -95,10 +96,18 @@
         return self.stateCopy(self._data, self)
 
     security.declarePrivate('stateCopy')
-    def stateCopy(self, object, container):
+    def stateCopy(self, obj, container):
         """Get a deep copy of the state of an object, breaking any database
            identity references."""
-        return cloneByPickle(aq_base(object))
+        map = getVCItems(obj)
+        ignore = []
+        if map:
+            for k, v in map.items():
+                ignore.append(aq_base(v))
+        res = cloneByPickle(aq_base(obj), ignore)
+        if map:
+            removeVCItems(res)
+        return res
 
 
 InitializeClass(Version)


=== Products/ZopeVersionControl/ZopeRepository.py 1.3 => 1.4 ===
--- Products/ZopeVersionControl/ZopeRepository.py:1.3	Wed Jan 15 17:17:46 2003
+++ Products/ZopeVersionControl/ZopeRepository.py	Tue May 13 18:12:31 2003
@@ -15,10 +15,8 @@
 
 from Globals import DTMLFile, InitializeClass
 from SequenceWrapper import SequenceWrapper
-import OFS, AccessControl, Acquisition
+import OFS, AccessControl
 import Repository
-from Acquisition import aq_base
-from OFS.ObjectManager import ObjectManager
 
 
 class ZopeRepository(
@@ -85,56 +83,6 @@
     security.declarePrivate('objectItems')
     def objectItems(self, spec=None):
         return SequenceWrapper(self, self._histories.items(), 1)
-
-    security.declarePrivate('listItems')
-    def listContainedItems(self, object):
-        """Internal: list the items of a container as [(id, value)]."""
-        items = []
-        try:
-            v = isinstance(object, ObjectManager)
-        except TypeError:
-            # Python 2.1 isinstance() dislikes ExtensionClass instances.
-            # This is an adequate workaround.
-            v = 0
-        if v:
-            # Return the items.
-            for name, value in object.objectItems():
-                items.append((name, aq_base(value)))
-        return items
-
-    security.declarePrivate('addItems')
-    def restoreContainedItems(self, ob, items):
-        """Internal: restore the items of a container."""
-        # First build "keep", a dictionary listing which
-        # items the object were provided by the version controlled object.
-        # Don't restore over those.
-        keep = {}
-        for name in ob.objectIds():
-            keep[name] = 1
-        # Restore the items.
-        for name, value in items:
-            if not keep.has_key(name):
-                ob._setOb(name, value)
-                if not hasattr(ob, '_tree'):
-                    # Avoid generating events, since nothing was ever really
-                    # removed or added.
-                    ob._objects += ({'meta_type': value.meta_type,
-                                     'id': name},)
-                # If there is a _tree attribute, it's very likely
-                # a BTreeFolder2, which doesn't need or want the
-                # _objects attribute.
-                # XXX This is a hackish way to check for BTreeFolder2s.
-
-    security.declarePrivate('replaceObject')
-    def replaceObject(self, object, new_object):
-        """Internal: replace a persistent object in its container."""
-        items = self.listContainedItems(object)
-        ob = Repository.Repository.replaceObject(self, object, new_object)
-        if items:
-            self.restoreContainedItems(ob, items)
-        return ob
-
-
 
 InitializeClass(ZopeRepository)
 


=== Products/ZopeVersionControl/ZopeVersion.py 1.5 => 1.6 ===
--- Products/ZopeVersionControl/ZopeVersion.py:1.5	Wed Jan 15 17:34:57 2003
+++ Products/ZopeVersionControl/ZopeVersion.py	Tue May 13 18:12:31 2003
@@ -14,23 +14,8 @@
 __version__='$Revision$'[11:-2]
 
 from Globals import DTMLFile, InitializeClass
-import OFS, AccessControl, Acquisition
+import OFS, AccessControl
 import Version
-from Acquisition import aq_base
-from OFS.ObjectManager import ObjectManager
-
-
-def cloneObjectManager(object):
-    """Makes a copy of an object manager without its subitems.
-    """
-    ids = list(object.objectIds())
-    values = [aq_base(o) for o in object.objectValues()]
-    object = Version.cloneByPickle(object, values)
-    for id in ids:
-        object._delOb(id)
-    if object._objects:
-        object._objects = ()
-    return object
 
 
 class ZopeVersion(
@@ -77,22 +62,5 @@
                 self, REQUEST, manage_tabs_message=message
 
                 )
-
-    security.declarePrivate('stateCopy')
-    def stateCopy(self, object, container):
-        """Get a deep copy of the state of an object, breaking any database
-           identity references."""
-        object = aq_base(object)
-        try:
-            v = isinstance(object, ObjectManager)
-        except TypeError:
-            # Python 2.1 isinstance() dislikes ExtensionClass instances.
-            # This is an adequate workaround.
-            v = 0
-        if v:
-            return cloneObjectManager(object)
-        else:
-            return Version.cloneByPickle(object)
-
 
 InitializeClass(ZopeVersion)