[Zope-CVS] CVS: Products/ZopeVersionControl - nonversioned.py:1.1 CHANGES.txt:1.2 IVersionControl.py:1.6 Repository.py:1.10 Version.py:1.9 container.py:NONE

Shane Hathaway shane at zope.com
Fri Jan 30 14:00:10 EST 2004


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

Modified Files:
	CHANGES.txt IVersionControl.py Repository.py Version.py 
Added Files:
	nonversioned.py 
Removed Files:
	container.py 
Log Message:
Refined the pattern for keeping parts of objects out of version control.

This is a generalization of the mechanism for versioning container
items.  IVersionedContainer is now named INonVersionedData and has
more descriptive method names.

You can also now supply an attribute, __vc_ignore__, that lists the names
of attributes to be ignored when reverting to an earlier revision.



=== Added File Products/ZopeVersionControl/nonversioned.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
# 
##############################################################################
"""Support for non-versioned data embedded in versioned objects.

$Id: nonversioned.py,v 1.1 2004/01/30 18:59:39 shane Exp $
"""

from Acquisition import aq_base
from OFS.ObjectManager import ObjectManager

from IVersionControl import INonVersionedData


try:
    # Optional support for references.
    from Products.References.Proxy import proxyBase
except ImportError:
    isProxy = None
else:
    def isProxy(obj):
        return (proxyBase(obj) is not aq_base(obj))


def getNonVersionedDataAdapter(obj):
    """Returns an INonVersionedData adapter for any object.

    This is a super-simplistic adapter implementation.
    """
    base = aq_base(obj)
    # If the object implements INonVersionedData, let it say
    # what its items are.
    if INonVersionedData.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 ObjectManagerNonVersionedDataAdapter(obj)
    # Otherwise use the standard adapter.
    return StandardNonVersionedDataAdapter(obj)


def listNonVersionedObjects(obj):
    return getNonVersionedDataAdapter(obj).listNonVersionedObjects()

def getNonVersionedData(obj):
    return getNonVersionedDataAdapter(obj).getNonVersionedData()

def removeNonVersionedData(obj):
    getNonVersionedDataAdapter(obj).removeNonVersionedData()

def restoreNonVersionedData(obj, dict):
    getNonVersionedDataAdapter(obj).restoreNonVersionedData(dict)



class StandardNonVersionedDataAdapter:
    """Non-versioned data adapter for arbitrary things.
    """
    __implements__ = INonVersionedData

    def __init__(self, obj):
        self.obj = obj
        # __vc_ignore__, if set, is a tuple of attribute names to
        # manage independently of version control.
        self.attrs = getattr(obj, "__vc_ignore__", ())

    def listNonVersionedObjects(self):
        # Assume it's OK to clone all of the attributes.
        # They will be removed later by removeNonVersionedData.
        return ()

    def removeNonVersionedData(self):
        for attr in self.attrs:
            try:
                delattr(aq_base(self.obj), attr)
            except (AttributeError, KeyError):
                pass

    def getNonVersionedData(self):
        data = {}
        for attr in self.attrs:
            if hasattr(self.obj, attr):
                data[attr] = aq_base(getattr(aq_base(self.obj), attr))
        return data

    def restoreNonVersionedData(self, data):
        for attr in self.attrs:
            if data.has_key(attr):
                setattr(aq_base(self.obj), attr, data[attr])


class ObjectManagerNonVersionedDataAdapter(StandardNonVersionedDataAdapter):
    """Non-versioned data adapter for object managers.
    """
    __implements__ = INonVersionedData

    def listNonVersionedObjects(self):
        contents = self.getNonVersionedData()['contents']
        return contents.values()

    def removeNonVersionedData(self):
        StandardNonVersionedDataAdapter.removeNonVersionedData(self)
        obj = self.obj
        removed = {}
        contents = self.getNonVersionedData()['contents']
        for name, value in contents.items():
            obj._delOb(name)
            removed[name] = 1
        if obj._objects:
            obj._objects = tuple([info for info in obj._objects
                                  if not removed.has_key(info['id'])])

    def getNonVersionedData(self):
        contents = {}
        attributes = StandardNonVersionedDataAdapter.getNonVersionedData(self)
        for name, value in self.obj.objectItems():
            if value is not None and isProxy is not None:
                if not getattr(value, '_versionable', 1):
                    # Version the state of subobjects that won't be
                    # versioned independently.
                    continue
                if isProxy(value):
                    # Version references in their containers.
                    continue
            contents[name] = aq_base(value)
        return {'contents': contents, 'attributes': attributes}

    def restoreNonVersionedData(self, data):
        StandardNonVersionedDataAdapter.restoreNonVersionedData(
            self, data['attributes'])
        # First build "ignore", a dictionary that lists which
        # items 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 data['contents'].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/CHANGES.txt 1.1 => 1.2 ===
--- Products/ZopeVersionControl/CHANGES.txt:1.1	Mon Oct 27 15:19:15 2003
+++ Products/ZopeVersionControl/CHANGES.txt	Fri Jan 30 13:59:39 2004
@@ -1,4 +1,9 @@
-Version 0.3
+Version 0.3 (not yet released)
+
+  - Refined the pattern for maintaining parts of objects independently
+    of version control.  This is a generalization of the mechanism for
+    versioning container items.  IVersionedContainer is now named
+    INonVersionedData and has more descriptive method names.
 
   - updateResource() and uncheckoutResource() now retain the identity
     of the object being versioned.  That is, they never replace an


=== Products/ZopeVersionControl/IVersionControl.py 1.5 => 1.6 ===
--- Products/ZopeVersionControl/IVersionControl.py:1.5	Mon Jan 19 15:32:59 2004
+++ Products/ZopeVersionControl/IVersionControl.py	Fri Jan 30 13:59:39 2004
@@ -229,19 +229,39 @@
          """
 
 
-class IVersionedContainer(Interface):
-    """Container version control modifier.
+class INonVersionedData(Interface):
+    """Controls what parts of an object fall outside version control.
 
     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 listNonVersionedObjects():
+        """Returns a list of subobjects that should not be pickled.
 
-    def removeVCItems():
-        """Removes independently versioned items from this container."""
+        The objects in the list must not be wrapped, because only the
+        identity of the objects will be considered.  The version
+        repository uses this method to avoid cloning subobjects that
+        will soon be removed by removeNonVersionedData.
+        """
 
-    def restoreVCItems(dict):
-        """Restores versioned items to this container."""
+    def removeNonVersionedData():
+        """Removes the non-versioned data from this object.
 
+        The version repository uses this method before storing an
+        object in the version repository.
+        """
+
+    def getNonVersionedData():
+        """Returns an opaque object containing the non-versioned data.
+
+        The version repository uses this method before reverting an
+        object to a revision.
+        """
+
+    def restoreNonVersionedData(dict):
+        """Restores non-versioned data to this object.
+
+        The version repository uses this method after reverting an
+        object to a revision.
+        """


=== Products/ZopeVersionControl/Repository.py 1.9 => 1.10 ===
--- Products/ZopeVersionControl/Repository.py:1.9	Mon Jan 19 15:32:59 2004
+++ Products/ZopeVersionControl/Repository.py	Fri Jan 30 13:59:39 2004
@@ -27,7 +27,7 @@
 
 from EventLog import LogEntry
 import Utility
-from container import getVCItems, restoreVCItems
+from nonversioned import getNonVersionedData, restoreNonVersionedData
 
 
 class Repository(Implicit, Persistent):
@@ -66,12 +66,12 @@
     def replaceState(self, obj, new_state):
         """Internal: replace the state of a persistent object.
         """
-        subitems = getVCItems(obj)
+        non_versioned = getNonVersionedData(obj)
         # XXX There ought to be some way to do this more cleanly.
         # This fills the __dict__ of the old object with new state.
-        # This replace the object in its container, but applications
-        # that use version control turned out much simpler when the
-        # identity of the object was retained.
+        # The other way to achieve the desired effect is to replace
+        # the object in its container, but this method preserves the
+        # identity of the object.
         if obj.__class__ is not new_state.__class__:
             raise VersionControlError(
                 "The class of the versioned object has changed. %s != %s"
@@ -82,8 +82,9 @@
                 del obj.__dict__[key]
         for key, value in new_state.__dict__.items():
             obj.__dict__[key] = value
-        if subitems:
-            restoreVCItems(obj, subitems)
+        if non_versioned:
+            # Restore the non-versioned data into the new state.
+            restoreNonVersionedData(obj, non_versioned)
         return obj
 
     #####################################################################


=== Products/ZopeVersionControl/Version.py 1.8 => 1.9 ===
--- Products/ZopeVersionControl/Version.py:1.8	Tue May 13 18:12:31 2003
+++ Products/ZopeVersionControl/Version.py	Fri Jan 30 13:59:39 2004
@@ -24,7 +24,7 @@
 from BTrees.OOBTree import OOBTree
 
 from Utility import VersionControlError
-from container import getVCItems, removeVCItems
+from nonversioned import listNonVersionedObjects, removeNonVersionedData
 
 
 def cloneByPickle(obj, ignore_list=()):
@@ -97,18 +97,14 @@
 
     security.declarePrivate('stateCopy')
     def stateCopy(self, obj, container):
-        """Get a deep copy of the state of an object, breaking any database
-           identity references."""
-        map = getVCItems(obj)
-        ignore = []
-        if map:
-            for k, v in map.items():
-                ignore.append(aq_base(v))
+        """Get a deep copy of the state of an object.
+
+        Breaks any database identity references.
+        """
+        ignore = listNonVersionedObjects(obj)
         res = cloneByPickle(aq_base(obj), ignore)
-        if map:
-            removeVCItems(res)
+        removeNonVersionedData(res)
         return res
 
 
 InitializeClass(Version)
-

=== Removed File Products/ZopeVersionControl/container.py ===




More information about the Zope-CVS mailing list