Commit dee3bca2 authored by Jim Fulton's avatar Jim Fulton

Officially deprecated PersistentDict

(https://bugs.launchpad.net/zodb/+bug/400775)

Also, stop producing legacy PersistentMapping state, while still
accepting it.
parent 432c76aa
......@@ -9,6 +9,10 @@ Bugs Fixed
----------
- The runzeo script didn't work without a configuration file.
(https://bugs.launchpad.net/zodb/+bug/410571)
- Officially deprecated PersistentDict
(https://bugs.launchpad.net/zodb/+bug/400775)
3.9.0b5 (2009-08-06)
====================
......
......@@ -774,7 +774,7 @@ def multiple_storages_invalidation_queue_is_not_insane():
>>> from ZEO.StorageServer import StorageServer, ZEOStorage
>>> from ZODB.FileStorage import FileStorage
>>> from ZODB.DB import DB
>>> from persistent.dict import PersistentDict
>>> from persistent.mapping import PersistentMapping
>>> from transaction import commit
>>> fs1 = FileStorage('t1.fs')
>>> fs2 = FileStorage('t2.fs')
......@@ -789,17 +789,17 @@ def multiple_storages_invalidation_queue_is_not_insane():
>>> commit()
>>> o1 = conn1.root()
>>> for i in range(10):
... o1.x = PersistentDict(); o1 = o1.x
... o1.x = PersistentMapping(); o1 = o1.x
... commit()
>>> last = fs1.lastTransaction()
>>> for i in range(5):
... o1.x = PersistentDict(); o1 = o1.x
... o1.x = PersistentMapping(); o1 = o1.x
... commit()
>>> o2 = conn2.root()
>>> for i in range(20):
... o2.x = PersistentDict(); o2 = o2.x
... o2.x = PersistentMapping(); o2 = o2.x
... commit()
>>> trans, oids = s1.getInvalidations(last)
......@@ -822,14 +822,14 @@ Let's create a file storage and stuff some data into it:
>>> from ZEO.StorageServer import StorageServer, ZEOStorage
>>> from ZODB.FileStorage import FileStorage
>>> from ZODB.DB import DB
>>> from persistent.dict import PersistentDict
>>> from persistent.mapping import PersistentMapping
>>> fs = FileStorage('t.fs')
>>> db = DB(fs)
>>> conn = db.open()
>>> from transaction import commit
>>> last = []
>>> for i in range(100):
... conn.root()[i] = PersistentDict()
... conn.root()[i] = PersistentMapping()
... commit()
... last.append(fs.lastTransaction())
>>> db.close()
......@@ -898,7 +898,7 @@ transaction, we'll get a result:
>>> db = DB(st); conn = db.open()
>>> ob = conn.root()
>>> for i in range(5):
... ob.x = PersistentDict(); ob = ob.x
... ob.x = PersistentMapping(); ob = ob.x
... commit()
... last.append(fs.lastTransaction())
......
......@@ -66,12 +66,12 @@ seems best and set the next record to that:
True
>>> it = ZODB.FileStorage.FileIterator('data.fs', tids[70])
Scan backward data.fs:118274 looking for '\x03z\xbd\xd8\xed\xa7>\xcc'
Scan backward data.fs:117080 looking for '\x03z\xbd\xd8\xed\xa7>\xcc'
>>> it.next().tid == tids[70]
True
>>> it = ZODB.FileStorage.FileIterator('data.fs', tids[-2])
Scan backward data.fs:118274 looking for '\x03z\xbd\xd8\xfa\x06\xd0\xcc'
Scan backward data.fs:117080 looking for '\x03z\xbd\xd8\xfa\x06\xd0\xcc'
>>> it.next().tid == tids[-2]
True
......@@ -95,12 +95,12 @@ starting point, or just pick up where another iterator left off:
True
>>> it = ZODB.FileStorage.FileIterator('data.fs', tids[50], pos=poss[50])
Scan backward data.fs:36542 looking for '\x03z\xbd\xd8\xe5\x1e\xb6\xcc'
Scan backward data.fs:35936 looking for '\x03z\xbd\xd8\xe5\x1e\xb6\xcc'
>>> it.next().tid == tids[50]
True
>>> it = ZODB.FileStorage.FileIterator('data.fs', tids[49], pos=poss[50])
Scan backward data.fs:36542 looking for '\x03z\xbd\xd8\xe4\xb1|\xcc'
Scan backward data.fs:35936 looking for '\x03z\xbd\xd8\xe4\xb1|\xcc'
>>> it.next().tid == tids[49]
True
......
......@@ -24,10 +24,10 @@ Now lets have a look at the last transactions of this FileStorage:
>>> from ZODB.scripts.fstail import main
>>> main(storagefile, 5)
2007-11-10 15:18:48.543001: hash=b16422d09fabdb45d4e4325e4b42d7d6f021d3c3
user='' description='' length=138 offset=191
user='' description='' length=132 offset=185
<BLANKLINE>
2007-11-10 15:18:48.543001: hash=b16422d09fabdb45d4e4325e4b42d7d6f021d3c3
user='' description='initial database creation' length=156 offset=52
user='' description='initial database creation' length=150 offset=52
<BLANKLINE>
Now clean up the storage again:
......
......@@ -433,7 +433,7 @@ We force the root to be loaded and the cache grows:
>>> getattr(conn.root, 'z', None)
>>> conn._cache.total_estimated_size
128
64
We add some data and the cache grows:
......
......@@ -17,7 +17,7 @@ $Id$
"""
import unittest
from zope.testing import doctest
import persistent.dict
import persistent.mapping
import transaction
def testAddingThenModifyThenAbort():
......@@ -39,7 +39,7 @@ savepoint.
>>> connection = db.open()
>>> root = connection.root()
>>> ob = persistent.dict.PersistentDict()
>>> ob = persistent.mapping.PersistentMapping()
>>> root['ob'] = ob
>>> sp = transaction.savepoint()
>>> ob.x = 1
......
......@@ -509,10 +509,10 @@ track of the transactions along the way:
>>> fs = ZODB.FileStorage.FileStorage('t.fs', create=True)
>>> db = DB(fs)
>>> conn = db.open()
>>> from persistent.dict import PersistentDict
>>> from persistent.mapping import PersistentMapping
>>> last = []
>>> for i in range(100):
... conn.root()[i] = PersistentDict()
... conn.root()[i] = PersistentMapping()
... transaction.commit()
... last.append(fs.lastTransaction())
......
......@@ -41,7 +41,7 @@ Create a root object and try again:
>>> fsdump(path) #doctest: +ELLIPSIS
Trans #00000 tid=... time=... offset=52
status=' ' user='' description='initial database creation'
data #00000 oid=0000000000000000 size=66 class=persistent.mapping.PersistentMapping
data #00000 oid=0000000000000000 size=60 class=persistent.mapping.PersistentMapping
Now we see first transaction with root object.
......@@ -54,10 +54,10 @@ Let's add a BTree:
>>> fsdump(path) #doctest: +ELLIPSIS
Trans #00000 tid=... time=... offset=52
status=' ' user='' description='initial database creation'
data #00000 oid=0000000000000000 size=66 class=persistent.mapping.PersistentMapping
Trans #00001 tid=... time=... offset=207
data #00000 oid=0000000000000000 size=60 class=persistent.mapping.PersistentMapping
Trans #00001 tid=... time=... offset=201
status=' ' user='' description='added an OOBTree'
data #00000 oid=0000000000000000 size=113 class=persistent.mapping.PersistentMapping
data #00000 oid=0000000000000000 size=107 class=persistent.mapping.PersistentMapping
data #00001 oid=0000000000000001 size=29 class=BTrees.OOBTree.OOBTree
Now we see two transactions and two changed objects.
......
......@@ -87,17 +87,17 @@ oid 0x00 persistent.mapping.PersistentMapping 2 revisions
tid user=''
tid description='initial database creation'
new revision persistent.mapping.PersistentMapping at 52
tid 0x... offset=168 ...
tid 0x... offset=162 ...
tid user=''
tid description='added an OOBTree'
new revision persistent.mapping.PersistentMapping at 207
references 0x01 BTrees.OOBTree.OOBTree at 207
new revision persistent.mapping.PersistentMapping at 201
references 0x01 BTrees.OOBTree.OOBTree at 201
oid 0x01 BTrees.OOBTree.OOBTree 1 revision
tid 0x... offset=168 ...
tid 0x... offset=162 ...
tid user=''
tid description='added an OOBTree'
new revision BTrees.OOBTree.OOBTree at 362
referenced by 0x00 persistent.mapping.PersistentMapping at 207
new revision BTrees.OOBTree.OOBTree at 350
referenced by 0x00 persistent.mapping.PersistentMapping at 201
So there are two revisions of oid 0 now, and the second references oid 1.
......@@ -115,26 +115,26 @@ oid 0x00 persistent.mapping.PersistentMapping 2 revisions
tid user=''
tid description='initial database creation'
new revision persistent.mapping.PersistentMapping at 52
tid 0x... offset=168 ...
tid 0x... offset=162 ...
tid user=''
tid description='added an OOBTree'
new revision persistent.mapping.PersistentMapping at 207
references 0x01 BTrees.OOBTree.OOBTree at 207
tid 0x... offset=441 ...
new revision persistent.mapping.PersistentMapping at 201
references 0x01 BTrees.OOBTree.OOBTree at 201
tid 0x... offset=429 ...
tid user=''
tid description='circling back to the root'
referenced by 0x01 BTrees.OOBTree.OOBTree at 489
referenced by 0x01 BTrees.OOBTree.OOBTree at 477
oid 0x01 BTrees.OOBTree.OOBTree 2 revisions
tid 0x... offset=168 ...
tid 0x... offset=162 ...
tid user=''
tid description='added an OOBTree'
new revision BTrees.OOBTree.OOBTree at 362
referenced by 0x00 persistent.mapping.PersistentMapping at 207
tid 0x... offset=441 ...
new revision BTrees.OOBTree.OOBTree at 350
referenced by 0x00 persistent.mapping.PersistentMapping at 201
tid 0x... offset=429 ...
tid user=''
tid description='circling back to the root'
new revision BTrees.OOBTree.OOBTree at 489
references 0x00 persistent.mapping.PersistentMapping at 489
new revision BTrees.OOBTree.OOBTree at 477
references 0x00 persistent.mapping.PersistentMapping at 477
oid 0x02 <unknown> 0 revisions
this oid was not defined (no data record for it found)
......
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# Copyright Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
......@@ -11,72 +11,6 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Python implementation of persistent container type
$Id$
"""
import persistent
from UserDict import IterableUserDict
__metaclass__ = type
class PersistentDict(persistent.Persistent, IterableUserDict):
"""A persistent wrapper for mapping objects.
This class allows wrapping of mapping objects so that object
changes are registered. As a side effect, mapping objects may be
subclassed.
"""
# IterableUserDict provides all of the mapping behavior. The
# PersistentDict 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 = IterableUserDict.__delitem__
__super_setitem = IterableUserDict.__setitem__
__super_clear = IterableUserDict.clear
__super_update = IterableUserDict.update
__super_setdefault = IterableUserDict.setdefault
__super_pop = IterableUserDict.pop
__super_popitem = IterableUserDict.popitem
__super_p_init = persistent.Persistent.__init__
__super_init = IterableUserDict.__init__
def __init__(self, dict=None):
self.__super_init(dict)
self.__super_p_init()
def __delitem__(self, key):
self.__super_delitem(key)
self._p_changed = True
def __setitem__(self, key, v):
self.__super_setitem(key, v)
self._p_changed = True
def clear(self):
self.__super_clear()
self._p_changed = True
def update(self, b):
self.__super_update(b)
self._p_changed = True
def setdefault(self, key, failobj=None):
# We could inline all of UserDict's implementation into the
# method here, but I'd rather not depend at all on the
# implementation in UserDict (simple as it is).
if not self.has_key(key):
self._p_changed = True
return self.__super_setdefault(key, failobj)
def pop(self, key, *args):
self._p_changed = True
return self.__super_pop(key, *args)
def popitem(self):
self._p_changed = True
return self.__super_popitem()
# persistent.dict is deprecated. User persistent.mapping
from persistent.mapping import PersistentMapping as PersistentDict
......@@ -17,9 +17,20 @@
$Id$"""
import persistent
from UserDict import UserDict
import UserDict
class PersistentMapping(UserDict, persistent.Persistent):
class default(object):
def __init__(self, func):
self.func = func
def __get__(self, inst, class_):
if inst is None:
return self
return self.func(inst)
class PersistentMapping(UserDict.IterableUserDict, persistent.Persistent):
"""A persistent wrapper for mapping objects.
This class allows wrapping of mapping objects so that object
......@@ -36,13 +47,13 @@ class PersistentMapping(UserDict, persistent.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
__super_update = UserDict.update
__super_setdefault = UserDict.setdefault
__super_pop = UserDict.pop
__super_popitem = UserDict.popitem
__super_delitem = UserDict.IterableUserDict.__delitem__
__super_setitem = UserDict.IterableUserDict.__setitem__
__super_clear = UserDict.IterableUserDict.clear
__super_update = UserDict.IterableUserDict.update
__super_setdefault = UserDict.IterableUserDict.setdefault
__super_pop = UserDict.IterableUserDict.pop
__super_popitem = UserDict.IterableUserDict.popitem
def __delitem__(self, key):
self.__super_delitem(key)
......@@ -76,38 +87,16 @@ class PersistentMapping(UserDict, persistent.Persistent):
self._p_changed = 1
return self.__super_popitem()
# __iter__ was added in ZODB 3.4.2, but should have been added long
# before. We could inherit from Python's IterableUserDict instead
# (which just adds __iter__ to Python's UserDict), but that class isn't
# documented, and it would add another level of lookup for all the
# other methods.
def __iter__(self):
return iter(self.data)
# 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
# write 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 = dict([x for x in self.__dict__.items()
if not x[0].startswith('_v_')])
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)
# Old implementations used _container rather than data.
# Use a descriptor to provide data when we have _container instead
@default
def data(self):
# We don't want to cause a write on read, so wer're careful not to
# do anything that would cause us to become marked as changed, however,
# if we're modified, then the saved record will have data, not
# _container.
data = self.__dict__.pop('_container')
self.__dict__['data'] = data
return data
##############################################################################
#
# Copyright (c) 2006 Zope Corporation and Contributors.
# Copyright (c) Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# 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.
#
##############################################################################
"""Test PersistentMapping
"""
import unittest
from zope.testing import doctest, setupstack
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite('README.txt'),
))
l0 = {}
l1 = {0:0}
......@@ -164,8 +168,54 @@ class MappingTests(unittest.TestCase):
u2.clear()
eq(u2, {}, "u2 == {}")
def test_suite():
return unittest.makeSuite(MappingTests)
def test_legacy_data():
"""
We've deprecated PersistentDict. If you import
persistent.dict.PersistentDict, you'll get
persistent.mapping.PersistentMapping.
>>> import persistent.dict, persistent.mapping
>>> persistent.dict.PersistentDict is persistent.mapping.PersistentMapping
True
PersistentMapping uses a data attribute for it's mapping data:
>>> m = persistent.mapping.PersistentMapping()
>>> m.__dict__
{'data': {}}
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
In the past, it used a _container attribute. For some time, the
implementation continued to use a _container attribute in pickles
(__get/setstate__) to be compatible with older releases. This isn't
really necessary any more. In fact, releases for which this might
matter can no longer share databases with current releases. Because
releases as recent as 3.9.0b5 still use _container in saved state, we
need to accept such state, but we stop producing it.
If we reset it's __dict__ with legacy data:
>>> m.__dict__.clear()
>>> m.__dict__['_container'] = {'a': 1}
>>> m.__dict__
{'_container': {'a': 1}}
>>> m._p_changed = 0
But when we perform any operations on it, the data will be converted
without marking the object as changed:
>>> m
{'a': 1}
>>> m.__dict__
{'data': {'a': 1}}
>>> m._p_changed
0
>>> m.__getstate__()
{'data': {'a': 1}}
"""
def test_suite():
return unittest.TestSuite((
doctest.DocTestSuite(),
unittest.makeSuite(MappingTests),
))
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