Commit aa1f2622 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 3963e85d
...@@ -9,6 +9,10 @@ Bugs Fixed ...@@ -9,6 +9,10 @@ Bugs Fixed
---------- ----------
- The runzeo script didn't work without a configuration file. - 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) 3.9.0b5 (2009-08-06)
==================== ====================
......
...@@ -774,7 +774,7 @@ def multiple_storages_invalidation_queue_is_not_insane(): ...@@ -774,7 +774,7 @@ def multiple_storages_invalidation_queue_is_not_insane():
>>> from ZEO.StorageServer import StorageServer, ZEOStorage >>> from ZEO.StorageServer import StorageServer, ZEOStorage
>>> from ZODB.FileStorage import FileStorage >>> from ZODB.FileStorage import FileStorage
>>> from ZODB.DB import DB >>> from ZODB.DB import DB
>>> from persistent.dict import PersistentDict >>> from persistent.mapping import PersistentMapping
>>> from transaction import commit >>> from transaction import commit
>>> fs1 = FileStorage('t1.fs') >>> fs1 = FileStorage('t1.fs')
>>> fs2 = FileStorage('t2.fs') >>> fs2 = FileStorage('t2.fs')
...@@ -789,17 +789,17 @@ def multiple_storages_invalidation_queue_is_not_insane(): ...@@ -789,17 +789,17 @@ def multiple_storages_invalidation_queue_is_not_insane():
>>> commit() >>> commit()
>>> o1 = conn1.root() >>> o1 = conn1.root()
>>> for i in range(10): >>> for i in range(10):
... o1.x = PersistentDict(); o1 = o1.x ... o1.x = PersistentMapping(); o1 = o1.x
... commit() ... commit()
>>> last = fs1.lastTransaction() >>> last = fs1.lastTransaction()
>>> for i in range(5): >>> for i in range(5):
... o1.x = PersistentDict(); o1 = o1.x ... o1.x = PersistentMapping(); o1 = o1.x
... commit() ... commit()
>>> o2 = conn2.root() >>> o2 = conn2.root()
>>> for i in range(20): >>> for i in range(20):
... o2.x = PersistentDict(); o2 = o2.x ... o2.x = PersistentMapping(); o2 = o2.x
... commit() ... commit()
>>> trans, oids = s1.getInvalidations(last) >>> trans, oids = s1.getInvalidations(last)
...@@ -822,14 +822,14 @@ Let's create a file storage and stuff some data into it: ...@@ -822,14 +822,14 @@ Let's create a file storage and stuff some data into it:
>>> from ZEO.StorageServer import StorageServer, ZEOStorage >>> from ZEO.StorageServer import StorageServer, ZEOStorage
>>> from ZODB.FileStorage import FileStorage >>> from ZODB.FileStorage import FileStorage
>>> from ZODB.DB import DB >>> from ZODB.DB import DB
>>> from persistent.dict import PersistentDict >>> from persistent.mapping import PersistentMapping
>>> fs = FileStorage('t.fs') >>> fs = FileStorage('t.fs')
>>> db = DB(fs) >>> db = DB(fs)
>>> conn = db.open() >>> conn = db.open()
>>> from transaction import commit >>> from transaction import commit
>>> last = [] >>> last = []
>>> for i in range(100): >>> for i in range(100):
... conn.root()[i] = PersistentDict() ... conn.root()[i] = PersistentMapping()
... commit() ... commit()
... last.append(fs.lastTransaction()) ... last.append(fs.lastTransaction())
>>> db.close() >>> db.close()
...@@ -898,7 +898,7 @@ transaction, we'll get a result: ...@@ -898,7 +898,7 @@ transaction, we'll get a result:
>>> db = DB(st); conn = db.open() >>> db = DB(st); conn = db.open()
>>> ob = conn.root() >>> ob = conn.root()
>>> for i in range(5): >>> for i in range(5):
... ob.x = PersistentDict(); ob = ob.x ... ob.x = PersistentMapping(); ob = ob.x
... commit() ... commit()
... last.append(fs.lastTransaction()) ... last.append(fs.lastTransaction())
......
...@@ -66,12 +66,12 @@ seems best and set the next record to that: ...@@ -66,12 +66,12 @@ seems best and set the next record to that:
True True
>>> it = ZODB.FileStorage.FileIterator('data.fs', tids[70]) >>> 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] >>> it.next().tid == tids[70]
True True
>>> it = ZODB.FileStorage.FileIterator('data.fs', tids[-2]) >>> 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] >>> it.next().tid == tids[-2]
True True
...@@ -95,12 +95,12 @@ starting point, or just pick up where another iterator left off: ...@@ -95,12 +95,12 @@ starting point, or just pick up where another iterator left off:
True True
>>> it = ZODB.FileStorage.FileIterator('data.fs', tids[50], pos=poss[50]) >>> 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] >>> it.next().tid == tids[50]
True True
>>> it = ZODB.FileStorage.FileIterator('data.fs', tids[49], pos=poss[50]) >>> 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] >>> it.next().tid == tids[49]
True True
......
...@@ -24,10 +24,10 @@ Now lets have a look at the last transactions of this FileStorage: ...@@ -24,10 +24,10 @@ Now lets have a look at the last transactions of this FileStorage:
>>> from ZODB.scripts.fstail import main >>> from ZODB.scripts.fstail import main
>>> main(storagefile, 5) >>> main(storagefile, 5)
2007-11-10 15:18:48.543001: hash=b16422d09fabdb45d4e4325e4b42d7d6f021d3c3 2007-11-10 15:18:48.543001: hash=b16422d09fabdb45d4e4325e4b42d7d6f021d3c3
user='' description='' length=138 offset=191 user='' description='' length=132 offset=185
<BLANKLINE> <BLANKLINE>
2007-11-10 15:18:48.543001: hash=b16422d09fabdb45d4e4325e4b42d7d6f021d3c3 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> <BLANKLINE>
Now clean up the storage again: Now clean up the storage again:
......
...@@ -433,7 +433,7 @@ We force the root to be loaded and the cache grows: ...@@ -433,7 +433,7 @@ We force the root to be loaded and the cache grows:
>>> getattr(conn.root, 'z', None) >>> getattr(conn.root, 'z', None)
>>> conn._cache.total_estimated_size >>> conn._cache.total_estimated_size
128 64
We add some data and the cache grows: We add some data and the cache grows:
......
...@@ -17,7 +17,7 @@ $Id$ ...@@ -17,7 +17,7 @@ $Id$
""" """
import unittest import unittest
from zope.testing import doctest from zope.testing import doctest
import persistent.dict import persistent.mapping
import transaction import transaction
def testAddingThenModifyThenAbort(): def testAddingThenModifyThenAbort():
...@@ -39,7 +39,7 @@ savepoint. ...@@ -39,7 +39,7 @@ savepoint.
>>> connection = db.open() >>> connection = db.open()
>>> root = connection.root() >>> root = connection.root()
>>> ob = persistent.dict.PersistentDict() >>> ob = persistent.mapping.PersistentMapping()
>>> root['ob'] = ob >>> root['ob'] = ob
>>> sp = transaction.savepoint() >>> sp = transaction.savepoint()
>>> ob.x = 1 >>> ob.x = 1
......
...@@ -509,10 +509,10 @@ track of the transactions along the way: ...@@ -509,10 +509,10 @@ track of the transactions along the way:
>>> fs = ZODB.FileStorage.FileStorage('t.fs', create=True) >>> fs = ZODB.FileStorage.FileStorage('t.fs', create=True)
>>> db = DB(fs) >>> db = DB(fs)
>>> conn = db.open() >>> conn = db.open()
>>> from persistent.dict import PersistentDict >>> from persistent.mapping import PersistentMapping
>>> last = [] >>> last = []
>>> for i in range(100): >>> for i in range(100):
... conn.root()[i] = PersistentDict() ... conn.root()[i] = PersistentMapping()
... transaction.commit() ... transaction.commit()
... last.append(fs.lastTransaction()) ... last.append(fs.lastTransaction())
......
...@@ -41,7 +41,7 @@ Create a root object and try again: ...@@ -41,7 +41,7 @@ Create a root object and try again:
>>> fsdump(path) #doctest: +ELLIPSIS >>> fsdump(path) #doctest: +ELLIPSIS
Trans #00000 tid=... time=... offset=52 Trans #00000 tid=... time=... offset=52
status=' ' user='' description='initial database creation' 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. Now we see first transaction with root object.
...@@ -54,10 +54,10 @@ Let's add a BTree: ...@@ -54,10 +54,10 @@ Let's add a BTree:
>>> fsdump(path) #doctest: +ELLIPSIS >>> fsdump(path) #doctest: +ELLIPSIS
Trans #00000 tid=... time=... offset=52 Trans #00000 tid=... time=... offset=52
status=' ' user='' description='initial database creation' 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
Trans #00001 tid=... time=... offset=207 Trans #00001 tid=... time=... offset=201
status=' ' user='' description='added an OOBTree' 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 data #00001 oid=0000000000000001 size=29 class=BTrees.OOBTree.OOBTree
Now we see two transactions and two changed objects. Now we see two transactions and two changed objects.
......
...@@ -87,17 +87,17 @@ oid 0x00 persistent.mapping.PersistentMapping 2 revisions ...@@ -87,17 +87,17 @@ oid 0x00 persistent.mapping.PersistentMapping 2 revisions
tid user='' tid user=''
tid description='initial database creation' tid description='initial database creation'
new revision persistent.mapping.PersistentMapping at 52 new revision persistent.mapping.PersistentMapping at 52
tid 0x... offset=168 ... tid 0x... offset=162 ...
tid user='' tid user=''
tid description='added an OOBTree' tid description='added an OOBTree'
new revision persistent.mapping.PersistentMapping at 207 new revision persistent.mapping.PersistentMapping at 201
references 0x01 BTrees.OOBTree.OOBTree at 207 references 0x01 BTrees.OOBTree.OOBTree at 201
oid 0x01 BTrees.OOBTree.OOBTree 1 revision oid 0x01 BTrees.OOBTree.OOBTree 1 revision
tid 0x... offset=168 ... tid 0x... offset=162 ...
tid user='' tid user=''
tid description='added an OOBTree' tid description='added an OOBTree'
new revision BTrees.OOBTree.OOBTree at 362 new revision BTrees.OOBTree.OOBTree at 350
referenced by 0x00 persistent.mapping.PersistentMapping at 207 referenced by 0x00 persistent.mapping.PersistentMapping at 201
So there are two revisions of oid 0 now, and the second references oid 1. 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 ...@@ -115,26 +115,26 @@ oid 0x00 persistent.mapping.PersistentMapping 2 revisions
tid user='' tid user=''
tid description='initial database creation' tid description='initial database creation'
new revision persistent.mapping.PersistentMapping at 52 new revision persistent.mapping.PersistentMapping at 52
tid 0x... offset=168 ... tid 0x... offset=162 ...
tid user='' tid user=''
tid description='added an OOBTree' tid description='added an OOBTree'
new revision persistent.mapping.PersistentMapping at 207 new revision persistent.mapping.PersistentMapping at 201
references 0x01 BTrees.OOBTree.OOBTree at 207 references 0x01 BTrees.OOBTree.OOBTree at 201
tid 0x... offset=441 ... tid 0x... offset=429 ...
tid user='' tid user=''
tid description='circling back to the root' 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 oid 0x01 BTrees.OOBTree.OOBTree 2 revisions
tid 0x... offset=168 ... tid 0x... offset=162 ...
tid user='' tid user=''
tid description='added an OOBTree' tid description='added an OOBTree'
new revision BTrees.OOBTree.OOBTree at 362 new revision BTrees.OOBTree.OOBTree at 350
referenced by 0x00 persistent.mapping.PersistentMapping at 207 referenced by 0x00 persistent.mapping.PersistentMapping at 201
tid 0x... offset=441 ... tid 0x... offset=429 ...
tid user='' tid user=''
tid description='circling back to the root' tid description='circling back to the root'
new revision BTrees.OOBTree.OOBTree at 489 new revision BTrees.OOBTree.OOBTree at 477
references 0x00 persistent.mapping.PersistentMapping at 489 references 0x00 persistent.mapping.PersistentMapping at 477
oid 0x02 <unknown> 0 revisions oid 0x02 <unknown> 0 revisions
this oid was not defined (no data record for it found) 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. # All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
...@@ -11,72 +11,6 @@ ...@@ -11,72 +11,6 @@
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
"""Python implementation of persistent container type
$Id$ # persistent.dict is deprecated. User persistent.mapping
""" from persistent.mapping import PersistentMapping as PersistentDict
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()
...@@ -17,9 +17,20 @@ ...@@ -17,9 +17,20 @@
$Id$""" $Id$"""
import persistent 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. """A persistent wrapper for mapping objects.
This class allows wrapping of mapping objects so that object This class allows wrapping of mapping objects so that object
...@@ -36,13 +47,13 @@ class PersistentMapping(UserDict, persistent.Persistent): ...@@ -36,13 +47,13 @@ class PersistentMapping(UserDict, persistent.Persistent):
# state as changed when a method actually changes the state. At # state as changed when a method actually changes the state. At
# the mapping API evolves, we may need to add more methods here. # the mapping API evolves, we may need to add more methods here.
__super_delitem = UserDict.__delitem__ __super_delitem = UserDict.IterableUserDict.__delitem__
__super_setitem = UserDict.__setitem__ __super_setitem = UserDict.IterableUserDict.__setitem__
__super_clear = UserDict.clear __super_clear = UserDict.IterableUserDict.clear
__super_update = UserDict.update __super_update = UserDict.IterableUserDict.update
__super_setdefault = UserDict.setdefault __super_setdefault = UserDict.IterableUserDict.setdefault
__super_pop = UserDict.pop __super_pop = UserDict.IterableUserDict.pop
__super_popitem = UserDict.popitem __super_popitem = UserDict.IterableUserDict.popitem
def __delitem__(self, key): def __delitem__(self, key):
self.__super_delitem(key) self.__super_delitem(key)
...@@ -76,38 +87,16 @@ class PersistentMapping(UserDict, persistent.Persistent): ...@@ -76,38 +87,16 @@ class PersistentMapping(UserDict, persistent.Persistent):
self._p_changed = 1 self._p_changed = 1
return self.__super_popitem() return self.__super_popitem()
# __iter__ was added in ZODB 3.4.2, but should have been added long # Old implementations used _container rather than data.
# before. We could inherit from Python's IterableUserDict instead # Use a descriptor to provide data when we have _container 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 @default
# other methods. def data(self):
def __iter__(self): # We don't want to cause a write on read, so wer're careful not to
return iter(self.data) # do anything that would cause us to become marked as changed, however,
# if we're modified, then the saved record will have data, not
# If the internal representation of PersistentMapping changes, # _container.
# it causes compatibility problems for pickles generated by data = self.__dict__.pop('_container')
# different versions of the code. Compatibility works in both self.__dict__['data'] = data
# directions, because an application may want to share a database
# between applications using different versions of the code. return data
# 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)
############################################################################## ##############################################################################
# #
# Copyright (c) 2006 Zope Corporation and Contributors. # Copyright (c) Zope Foundation and Contributors.
# All Rights Reserved. # All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # 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 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
"""Test PersistentMapping
"""
import unittest import unittest
from zope.testing import doctest, setupstack
def test_suite():
return unittest.TestSuite((
doctest.DocFileSuite('README.txt'),
))
l0 = {} l0 = {}
l1 = {0:0} l1 = {0:0}
...@@ -164,8 +168,54 @@ class MappingTests(unittest.TestCase): ...@@ -164,8 +168,54 @@ class MappingTests(unittest.TestCase):
u2.clear() u2.clear()
eq(u2, {}, "u2 == {}") eq(u2, {}, "u2 == {}")
def test_suite(): def test_legacy_data():
return unittest.makeSuite(MappingTests) """
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__': In the past, it used a _container attribute. For some time, the
unittest.main(defaultTest='test_suite') 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