[Zope-Checkins] CVS: StandaloneZODB/ZODB - PersistentMapping.py:1.17

Jeremy Hylton jeremy@zope.com
Thu, 29 Nov 2001 20:13:34 -0500


Update of /cvs-repository/StandaloneZODB/ZODB
In directory cvs.zope.org:/tmp/cvs-serv13338

Modified Files:
	PersistentMapping.py 
Log Message:
Ensure that new code produces pickles acceptable to old code.

Add __getstate__() and __setstate__() methods.

Thanks to Chris McDonough for helping me see the problem.

There are extensive comments that discuss the backwards compatibility
issues.  Basically, we must be careful that the pickles these methods
operate on are just like the pickles generated by the old code, or
developers couldn't switch back and forth between new and old code.
That's a incompatibility we aren't willing to live with for now --
perhaps not ever.

    # If the internal representation of PersistentMapping changes,
    # it causes compatibility problems for pickles generated by
    # different versions of the code.  Compatibility works in both
    # directions, because an application may want to share a database
    # between applications using different versions of the code.
    
    # Effectively, the original rep is part of the "API."  To provide
    # full compatibility, the getstate and setstate must read and
    # right objects using the old rep.

    # As a result, the PersistentMapping must save and restore the
    # actual internal dictionary using the name _container.



=== StandaloneZODB/ZODB/PersistentMapping.py 1.16 => 1.17 ===
     changes are registered.  As a side effect, mapping objects may be
     subclassed.
+
+    A subclass of PersistentMapping or any code that adds new
+    attributes should not create an attribute named _container.  This
+    is reserved for backwards compatibility reasons.
     """
 
+    # UserDict provides all of the mapping behavior.  The
+    # PersistentMapping class is responsible marking the persistent
+    # state as changed when a method actually changes the state.  At
+    # the mapping API evolves, we may need to add more methods here.
+
     __super_delitem = UserDict.__delitem__
     __super_setitem = UserDict.__setitem__
     __super_clear = UserDict.clear
@@ -31,21 +40,6 @@
     __super_setdefault = UserDict.setdefault
     __super_popitem = UserDict.popitem
 
-    def __setstate__(self, state):
-        # The old PersistentMapping used _container to hold the data.
-        # We need to make the current code work with objects pickled
-        # using the old code.  Unfortunately, this forces us to expose
-        # the rep of UserDict, because __init__() won't be called when
-        # a pickled object is being loaded.
-        if state.has_key('_container'):
-            assert not state.has_key('data'), \
-                   ("object state has _container and data attributes: %s"
-                    % repr(state))
-            self.data = state['_container']
-            del state['_container']
-        for k, v in state.items():
-            self.__dict__[k] = v
-
     def __delitem__(self, key):
         self.__super_delitem(key)
         self._p_changed = 1
@@ -73,3 +67,31 @@
     def popitem(self):
         self._p_changed = 1
         return self.__super_popitem()
+
+    # If the internal representation of PersistentMapping changes,
+    # it causes compatibility problems for pickles generated by
+    # different versions of the code.  Compatibility works in both
+    # directions, because an application may want to share a database
+    # between applications using different versions of the code.
+    
+    # Effectively, the original rep is part of the "API."  To provide
+    # full compatibility, the getstate and setstate must read and
+    # right objects using the old rep.
+
+    # As a result, the PersistentMapping must save and restore the
+    # actual internal dictionary using the name _container.
+
+    def __getstate__(self):
+        state = {}
+        state.update(self.__dict__)
+        state['_container'] = state['data']
+        del state['data']
+        return state
+
+    def __setstate__(self, state):
+        if state.has_key('_container'):
+            self.data = state['_container']
+            del state['_container']
+        elif not state.has_key('data'):
+            self.data = {}
+        self.__dict__.update(state)