Commit 8e9260c5 authored by Jeremy Hylton's avatar Jeremy Hylton

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.
parent c64281cf
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
"""Python implementation of persistent base types """Python implementation of persistent base types
$Id: PersistentMapping.py,v 1.16 2001/11/28 23:59:02 jeremy Exp $""" $Id: PersistentMapping.py,v 1.17 2001/11/30 01:13:34 jeremy Exp $"""
__version__='$Revision: 1.16 $'[11:-2] __version__='$Revision: 1.17 $'[11:-2]
import Persistence import Persistence
from UserDict import UserDict from UserDict import UserDict
...@@ -22,8 +22,17 @@ class PersistentMapping(UserDict, Persistence.Persistent): ...@@ -22,8 +22,17 @@ class PersistentMapping(UserDict, Persistence.Persistent):
This class allows wrapping of mapping objects so that object This class allows wrapping of mapping objects so that object
changes are registered. As a side effect, mapping objects may be changes are registered. As a side effect, mapping objects may be
subclassed. 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_delitem = UserDict.__delitem__
__super_setitem = UserDict.__setitem__ __super_setitem = UserDict.__setitem__
__super_clear = UserDict.clear __super_clear = UserDict.clear
...@@ -31,21 +40,6 @@ class PersistentMapping(UserDict, Persistence.Persistent): ...@@ -31,21 +40,6 @@ class PersistentMapping(UserDict, Persistence.Persistent):
__super_setdefault = UserDict.setdefault __super_setdefault = UserDict.setdefault
__super_popitem = UserDict.popitem __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): def __delitem__(self, key):
self.__super_delitem(key) self.__super_delitem(key)
self._p_changed = 1 self._p_changed = 1
...@@ -73,3 +67,31 @@ class PersistentMapping(UserDict, Persistence.Persistent): ...@@ -73,3 +67,31 @@ class PersistentMapping(UserDict, Persistence.Persistent):
def popitem(self): def popitem(self):
self._p_changed = 1 self._p_changed = 1
return self.__super_popitem() 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)
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
"""Python implementation of persistent base types """Python implementation of persistent base types
$Id: PersistentMapping.py,v 1.16 2001/11/28 23:59:02 jeremy Exp $""" $Id: PersistentMapping.py,v 1.17 2001/11/30 01:13:34 jeremy Exp $"""
__version__='$Revision: 1.16 $'[11:-2] __version__='$Revision: 1.17 $'[11:-2]
import Persistence import Persistence
from UserDict import UserDict from UserDict import UserDict
...@@ -22,8 +22,17 @@ class PersistentMapping(UserDict, Persistence.Persistent): ...@@ -22,8 +22,17 @@ class PersistentMapping(UserDict, Persistence.Persistent):
This class allows wrapping of mapping objects so that object This class allows wrapping of mapping objects so that object
changes are registered. As a side effect, mapping objects may be changes are registered. As a side effect, mapping objects may be
subclassed. 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_delitem = UserDict.__delitem__
__super_setitem = UserDict.__setitem__ __super_setitem = UserDict.__setitem__
__super_clear = UserDict.clear __super_clear = UserDict.clear
...@@ -31,21 +40,6 @@ class PersistentMapping(UserDict, Persistence.Persistent): ...@@ -31,21 +40,6 @@ class PersistentMapping(UserDict, Persistence.Persistent):
__super_setdefault = UserDict.setdefault __super_setdefault = UserDict.setdefault
__super_popitem = UserDict.popitem __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): def __delitem__(self, key):
self.__super_delitem(key) self.__super_delitem(key)
self._p_changed = 1 self._p_changed = 1
...@@ -73,3 +67,31 @@ class PersistentMapping(UserDict, Persistence.Persistent): ...@@ -73,3 +67,31 @@ class PersistentMapping(UserDict, Persistence.Persistent):
def popitem(self): def popitem(self):
self._p_changed = 1 self._p_changed = 1
return self.__super_popitem() 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)
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
"""Python implementation of persistent base types """Python implementation of persistent base types
$Id: mapping.py,v 1.16 2001/11/28 23:59:02 jeremy Exp $""" $Id: mapping.py,v 1.17 2001/11/30 01:13:34 jeremy Exp $"""
__version__='$Revision: 1.16 $'[11:-2] __version__='$Revision: 1.17 $'[11:-2]
import Persistence import Persistence
from UserDict import UserDict from UserDict import UserDict
...@@ -22,8 +22,17 @@ class PersistentMapping(UserDict, Persistence.Persistent): ...@@ -22,8 +22,17 @@ class PersistentMapping(UserDict, Persistence.Persistent):
This class allows wrapping of mapping objects so that object This class allows wrapping of mapping objects so that object
changes are registered. As a side effect, mapping objects may be changes are registered. As a side effect, mapping objects may be
subclassed. 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_delitem = UserDict.__delitem__
__super_setitem = UserDict.__setitem__ __super_setitem = UserDict.__setitem__
__super_clear = UserDict.clear __super_clear = UserDict.clear
...@@ -31,21 +40,6 @@ class PersistentMapping(UserDict, Persistence.Persistent): ...@@ -31,21 +40,6 @@ class PersistentMapping(UserDict, Persistence.Persistent):
__super_setdefault = UserDict.setdefault __super_setdefault = UserDict.setdefault
__super_popitem = UserDict.popitem __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): def __delitem__(self, key):
self.__super_delitem(key) self.__super_delitem(key)
self._p_changed = 1 self._p_changed = 1
...@@ -73,3 +67,31 @@ class PersistentMapping(UserDict, Persistence.Persistent): ...@@ -73,3 +67,31 @@ class PersistentMapping(UserDict, Persistence.Persistent):
def popitem(self): def popitem(self):
self._p_changed = 1 self._p_changed = 1
return self.__super_popitem() 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)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment