[Zodb-checkins] SVN: ZODB/branches/tseaver-python_picklecache/src/persistent/ Snapshot.

Tres Seaver tseaver at palladion.com
Thu Mar 19 11:36:43 EDT 2009


Log message for revision 98274:
  Snapshot.

Changed:
  U   ZODB/branches/tseaver-python_picklecache/src/persistent/__init__.py
  U   ZODB/branches/tseaver-python_picklecache/src/persistent/interfaces.py
  A   ZODB/branches/tseaver-python_picklecache/src/persistent/picklecache.py
  A   ZODB/branches/tseaver-python_picklecache/src/persistent/tests/test_picklecache.py

-=-
Modified: ZODB/branches/tseaver-python_picklecache/src/persistent/__init__.py
===================================================================
--- ZODB/branches/tseaver-python_picklecache/src/persistent/__init__.py	2009-03-19 15:34:54 UTC (rev 98273)
+++ ZODB/branches/tseaver-python_picklecache/src/persistent/__init__.py	2009-03-19 15:36:43 UTC (rev 98274)
@@ -15,20 +15,37 @@
 
 $Id$
 """
+try:
+    from cPersistence import Persistent
+    from cPersistence import GHOST
+    from cPersistence import UPTODATE
+    from cPersistence import CHANGED
+    from cPersistence import STICKY
+    from cPersistence import simple_new
+except ImportError: # XXX need pure-Python fallback
+    _HAVE_CPERSISTECE = False
+    from pyPersistence import Persistent
+    from pyPersistence import GHOST
+    from pyPersistence import UPTODATE
+    from pyPersistence import CHANGED
+    from pyPersistence import STICKY
+else:
+    _HAVE_CPERSISTECE = True
+    import copy_reg
+    copy_reg.constructor(simple_new)
 
-from cPersistence import Persistent, GHOST, UPTODATE, CHANGED, STICKY
-from cPickleCache import PickleCache
-
-from cPersistence import simple_new
-import copy_reg
-copy_reg.constructor(simple_new)
-
-# Make an interface declaration for Persistent,
-# if zope.interface is available.
 try:
-    from zope.interface import classImplements
+    from cPickleCache import PickleCache
 except ImportError:
-    pass
-else:
-    from persistent.interfaces import IPersistent
-    classImplements(Persistent, IPersistent)
+    from picklecache import PickleCache
+
+if _HAVE_CPERSISTECE:
+    # Make an interface declaration for Persistent, if zope.interface
+    # is available.  XXX that the pyPersistent version already does this?
+    try:
+        from zope.interface import classImplements
+    except ImportError:
+        pass
+    else:
+        from persistent.interfaces import IPersistent
+        classImplements(Persistent, IPersistent)

Modified: ZODB/branches/tseaver-python_picklecache/src/persistent/interfaces.py
===================================================================
--- ZODB/branches/tseaver-python_picklecache/src/persistent/interfaces.py	2009-03-19 15:34:54 UTC (rev 98273)
+++ ZODB/branches/tseaver-python_picklecache/src/persistent/interfaces.py	2009-03-19 15:36:43 UTC (rev 98274)
@@ -19,6 +19,11 @@
 from zope.interface import Interface
 from zope.interface import Attribute
 
+try:
+    from cPersistence import GHOST, UPTODATE, CHANGED, STICKY
+except ImportError:
+    GHOST, UPTODATE, CHANGED, STICKY = range(4)
+
 class IPersistent(Interface):
     """Python persistent interface
 
@@ -306,3 +311,138 @@
 ##         is returned.  If non-None, the return value is the kind of
 ##         timestamp supplied by Python's time.time().
 ##         """
+
+
+class IPickleCache(Interface):
+    """ API of the cache for a ZODB connection.
+    """
+    def __getitem__(oid):
+        """ -> the persistent object for OID.
+
+        o Raise KeyError if not found.
+        """
+
+    def __setitem__(oid, value):
+        """ Save the persistent object under OID.
+
+        o 'oid' must be a string, else raise ValueError.
+
+        o Raise KeyError on duplicate
+        """
+
+    def __delitem__(oid):
+        """ Remove the persistent object for OID.
+
+        o 'oid' must be a string, else raise ValueError.
+
+        o Raise KeyError if not found.
+        """
+
+    def get(oid, default=None):
+        """ -> the persistent object for OID.
+
+        o Return 'default' if not found.
+        """
+
+    def mru(oid):
+        """ Move the element corresonding to 'oid' to the head.
+
+        o Raise KeyError if no element is found.
+        """
+
+    def __len__():
+        """ -> the number of OIDs in the cache.
+        """
+
+    def items():
+        """-> a sequence of tuples (oid, value) for cached objects.
+
+        o Only includes items in 'data' (no p-classes).
+        """
+
+    def ringlen():
+        """ -> the number of persistent objects in the ring.
+
+        o Only includes items in the ring (no ghosts or p-classes).
+        """
+
+    def lru_items():
+        """ -> a sequence of tuples (oid, value) for cached objects.
+
+        o Tuples will be in LRU order.
+
+        o Only includes items in the ring (no ghosts or p-classes).
+        """
+
+    def klass_items():
+        """-> a sequence of tuples (oid, value) for cached p-classes.
+
+        o Only includes persistent classes.
+        """
+
+    def incrgc():
+        """ Perform an incremental garbage collection sweep.
+
+        o Reduce number of non-ghosts to 'cache_size', if possible.
+        
+        o Ghostify in LRU order.
+
+        o Skip dirty or sticky objects.
+
+        o Quit once we get down to 'cache_size'.
+        """
+
+    def full_sweep():
+        """ Perform a full garbage collection sweep.
+
+        o Reduce number of non-ghosts to 0, if possible.
+
+        o Ghostify all non-sticky / non-changed objecs.
+        """
+
+    def minimize():
+        """ Alias for 'full_sweep'.
+
+        o XXX?
+        """
+
+    def reify(to_reify):
+        """ Reify the indicated objects.
+
+        o If 'to_reify' is a string, treat it as an OID.
+
+        o Otherwise, iterate over it as a sequence of OIDs.
+
+        o For each OID, if present in 'data' and in GHOST state:
+
+            o Call '_p_unghostify' on the object.
+
+            o Add it to the ring.
+
+        o If any OID is present but not in GHOST state, skip it.
+
+        o Raise KeyErrory if any OID is not present.
+        """
+
+    def invalidate(to_invalidate):
+        """ Invalidate the indicated objects.
+
+        o If 'to_invalidate' is a string, treat it as an OID.
+
+        o Otherwise, iterate over it as a sequence of OIDs.
+
+        o Any OID corresponding to a p-class will cause the corresponding
+            p-class to be removed from the cache.
+
+        o For all other OIDs, ghostify the corrsponding object and 
+            remove it from the ring.
+        """
+
+    cache_size = Attribute(u'Target size of the cache')
+    cache_drain_resistance = Attribute(u'Factor for draining cache below '
+                                        u'target size')
+    cache_non_ghost_count = Attribute(u'Number of non-ghosts in the cache '
+                                        u'(XXX how is it different from '
+                                        u'ringlen?')
+    cache_data = Attribute(u"Property:  copy of our 'data' dict")
+    cache_klass_count = Attribute(u"Property: len of 'persistent_classes'")

Added: ZODB/branches/tseaver-python_picklecache/src/persistent/picklecache.py
===================================================================
--- ZODB/branches/tseaver-python_picklecache/src/persistent/picklecache.py	                        (rev 0)
+++ ZODB/branches/tseaver-python_picklecache/src/persistent/picklecache.py	2009-03-19 15:36:43 UTC (rev 98274)
@@ -0,0 +1,225 @@
+##############################################################################
+#
+# Copyright (c) 2009 Zope Corporation 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.
+# 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.
+#
+##############################################################################
+import weakref
+
+from zope.interface import implements
+
+from persistent.interfaces import CHANGED
+from persistent.interfaces import GHOST
+from persistent.interfaces import IPickleCache
+from persistent.interfaces import STICKY
+
+class RingNode(object):
+    # 32 byte fixed size wrapper.
+    __slots__ = ('object', 'next', 'prev')
+    def __init__(self, object, next=None, prev=None):
+        self.object = object
+        self.next = next
+        self.prev = prev
+
+class PickleCache(object):
+    implements(IPickleCache)
+
+    def __init__(self, jar, target_size):
+        self.jar = jar
+        self.target_size = target_size
+        self.drain_resistance = 0
+        self.non_ghost_count = 0
+        self.persistent_classes = {}
+        self.data = weakref.WeakValueDictionary()
+        self.ring = RingNode(None)
+        self.ring.next = self.ring.prev = self.ring
+
+    # IPickleCache API
+    def __len__(self):
+        """ See IPickleCache.
+        """
+        return (len(self.persistent_classes) +
+                len(self.data))
+
+    def __getitem__(self, oid):
+        """ See IPickleCache.
+        """
+        value = self.data.get(oid)
+        if value is not None:
+            return value
+        return self.persistent_classes[oid]
+
+    def __setitem__(self, oid, value):
+        """ See IPickleCache.
+        """
+        if not isinstance(oid, str):
+            raise ValueError('OID must be string: %s' % oid)
+        # XXX
+        if oid in self.persistent_classes or oid in self.data:
+            raise KeyError('Duplicate OID: %s' % oid)
+        if type(value) is type:
+            self.persistent_classes[oid] = value
+        else:
+            self.data[oid] = value
+            if value._p_state != GHOST:
+                self.non_ghost_count += 1
+                mru = self.ring.prev
+                self.ring.prev = node = RingNode(value, self.ring, mru)
+                mru.next = node
+
+    def __delitem__(self, oid):
+        """ See IPickleCache.
+        """
+        if not isinstance(oid, str):
+            raise ValueError('OID must be string: %s' % oid)
+        if oid in self.persistent_classes:
+            del self.persistent_classes[oid]
+        else:
+            value = self.data.pop(oid)
+            node = self.ring.next
+            if node is None:
+                return
+            while node is not self.ring:
+                if node.object is value:
+                    node.prev.next, node.next.prev = node.next, node.prev
+                    self.non_ghost_count -= 1
+                    break
+                node = node.next
+
+    def get(self, oid, default=None):
+        """ See IPickleCache.
+        """
+        value = self.data.get(oid, self)
+        if value is not self:
+            return value
+        return self.persistent_classes.get(oid, default)
+
+    def mru(self, oid):
+        """ See IPickleCache.
+        """
+        node = self.ring.next
+        while node is not self.ring and node.object._p_oid != oid:
+            node = node.next
+        if node is self.ring:
+            raise KeyError('Unknown OID: %s' % oid)
+        # remove from old location
+        node.prev.next, node.next.prev = node.next, node.prev
+        # splice into new
+        self.ring.prev.next, node.prev = node, self.ring.prev
+        self.ring.prev, node.next = node, self.ring
+        
+    def ringlen(self):
+        """ See IPickleCache.
+        """
+        result = 0
+        node = self.ring.next
+        while node is not self.ring:
+            result += 1
+            node = node.next
+        return result
+
+    def items(self):
+        """ See IPickleCache.
+        """
+        return self.data.items()
+
+    def lru_items(self):
+        """ See IPickleCache.
+        """
+        result = []
+        node = self.ring.next
+        while node is not self.ring:
+            result.append((node.object._p_oid, node.object))
+            node = node.next
+        return result
+
+    def klass_items(self):
+        """ See IPickleCache.
+        """
+        return self.persistent_classes.items()
+
+    def incrgc(self, ignored=None):
+        """ See IPickleCache.
+        """
+        target = self.target_size
+        if self.drain_resistance >= 1:
+            size = self.non_ghost_count
+            target2 = size - 1 - (size / self.drain_resistance)
+            if target2 < target:
+                target = target2
+        self._sweep(target)
+
+    def full_sweep(self, target=None):
+        """ See IPickleCache.
+        """
+        self._sweep(0)
+
+    minimize = full_sweep
+
+    def reify(self, oid):
+        """ See IPickleCache.
+        """
+        pass
+
+    def invalidate(self, to_invalidate):
+        """ See IPickleCache.
+        """
+        if isintance(to_invalidate, str):
+            self._invalidate(to_invalidate)
+        else:
+            for oid in to_invalidate:
+                self._invalidate(oid)
+
+    def debug_info(self):
+        """ See IPickleCache.
+        """
+        result = []
+        for oid, klass in self.persistent_classes.items():
+            result.append((oid,
+                            len(gc.getreferents(value)),
+                            type(value).__name__,
+                            value._p_state,
+                            ))
+        for oid, value in self.data.items():
+            result.append((oid,
+                            len(gc.getreferents(value)),
+                            type(value).__name__,
+                            ))
+
+    cache_size = property(lambda self: self.target_size)
+    cache_drain_resistance = property(lambda self: self.drain_resistance)
+    cache_non_ghost_count = property(lambda self: self.non_ghost_count)
+    cache_data = property(lambda self: dict(self.data.items()))
+    cache_klass_count = property(lambda self: len(self.persistent_classes))
+
+    # Helpers
+    def _sweep(self, target):
+        # lock
+        node = self.ring.next
+        while node is not self.ring and self.non_ghost_count > target:
+            if node.object._p_state not in (STICKY, CHANGED):
+                node.prev.next, node.next.prev = node.next, node.prev
+                node.object = None
+                self.non_ghost_count -= 1
+            node = node.next
+
+    def _invalidate(self, oid):
+        value = self.data.get(oid)
+        if value is not None and value._p_state != GHOST:
+            # value._p_invalidate() # NOOOO, we'll do it ourselves.
+            value._p_ghostify() # TBD
+            node = self.ring.next
+            while node is not self.ring:
+                if node.object is value:
+                    node.prev.next, node.next.prev = node.next, node.prev
+                    break
+        elif oid in self.persistent_classes:
+            del self.persistent_classes[oid]
+

Added: ZODB/branches/tseaver-python_picklecache/src/persistent/tests/test_picklecache.py
===================================================================
--- ZODB/branches/tseaver-python_picklecache/src/persistent/tests/test_picklecache.py	                        (rev 0)
+++ ZODB/branches/tseaver-python_picklecache/src/persistent/tests/test_picklecache.py	2009-03-19 15:36:43 UTC (rev 98274)
@@ -0,0 +1,287 @@
+##############################################################################
+#
+# Copyright (c) 2009 Zope Corporation 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.
+# 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.
+#
+##############################################################################
+import unittest
+
+class PickleCacheTests(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from persistent.picklecache import PickleCache
+        return PickleCache
+
+    def _makeOne(self, jar=None, target_size=10):
+        if jar is None:
+            jar = DummyConnection()
+        return self._getTargetClass()(jar, target_size)
+
+    def _makePersist(self, state=None, oid='foo', jar=None):
+        if state is None:
+            from persistent.interfaces import GHOST
+            state = GHOST
+        if jar is None:
+            jar = DummyConnection()
+        persist = DummyPersistent()
+        persist._p_state = state
+        persist._p_oid = oid
+        persist._p_jar = jar
+        return persist
+
+    def test_class_conforms_to_IPickleCache(self):
+        from zope.interface.verify import verifyClass
+        from persistent.interfaces import IPickleCache
+        verifyClass(IPickleCache, self._getTargetClass())
+
+    def test_instance_conforms_to_IPickleCache(self):
+        from zope.interface.verify import verifyObject
+        from persistent.interfaces import IPickleCache
+        verifyObject(IPickleCache, self._makeOne())
+
+    def test_empty(self):
+        cache = self._makeOne()
+
+        self.assertEqual(len(cache), 0)
+        self.assertEqual(len(cache.items()), 0)
+        self.assertEqual(len(cache.klass_items()), 0)
+        self.assertEqual(cache.ringlen(), 0)
+        self.assertEqual(len(cache.lru_items()), 0)
+        self.assertEqual(cache.cache_size, 10)
+        self.assertEqual(cache.cache_drain_resistance, 0)
+        self.assertEqual(cache.cache_non_ghost_count, 0)
+        self.assertEqual(dict(cache.cache_data), {})
+        self.assertEqual(cache.cache_klass_count, 0)
+
+    def test___getitem___nonesuch_raises_KeyError(self):
+        cache = self._makeOne()
+
+        self.assertRaises(KeyError, lambda: cache['nonesuch'])
+
+    def test_get_nonesuch_no_default(self):
+        cache = self._makeOne()
+
+        self.assertEqual(cache.get('nonesuch'), None)
+
+    def test_get_nonesuch_w_default(self):
+        cache = self._makeOne()
+        default = object
+
+        self.failUnless(cache.get('nonesuch', default) is default)
+
+    def test___setitem___non_string_oid_raises_ValueError(self):
+        cache = self._makeOne()
+
+        try:
+            cache[object()] = self._makePersist()
+        except ValueError:
+            pass
+        else:
+            self.fail("Didn't raise ValueError with non-string OID.")
+
+    def test___setitem___duplicate_oid_raises_KeyError(self):
+        cache = self._makeOne()
+        original = self._makePersist()
+        cache['original'] = original
+        duplicate = self._makePersist()
+
+        try:
+            cache['original'] = duplicate
+        except KeyError:
+            pass
+        else:
+            self.fail("Didn't raise KeyError with duplicate OID.")
+
+    def test___setitem___ghost(self):
+        from persistent.interfaces import GHOST
+        cache = self._makeOne()
+        ghost = self._makePersist(state=GHOST)
+
+        cache['ghost'] = ghost
+
+        self.assertEqual(len(cache), 1)
+        self.assertEqual(len(cache.items()), 1)
+        self.assertEqual(len(cache.klass_items()), 0)
+        self.assertEqual(cache.items()[0][0], 'ghost')
+        self.assertEqual(cache.ringlen(), 0)
+        self.failUnless(cache.items()[0][1] is ghost)
+        self.failUnless(cache['ghost'] is ghost)
+
+    def test___setitem___non_ghost(self):
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        uptodate = self._makePersist(state=UPTODATE)
+
+        cache['uptodate'] = uptodate
+
+        self.assertEqual(len(cache), 1)
+        self.assertEqual(len(cache.items()), 1)
+        self.assertEqual(len(cache.klass_items()), 0)
+        self.assertEqual(cache.items()[0][0], 'uptodate')
+        self.assertEqual(cache.ringlen(), 1)
+        self.failUnless(cache.items()[0][1] is uptodate)
+        self.failUnless(cache['uptodate'] is uptodate)
+        self.failUnless(cache.get('uptodate') is uptodate)
+
+    def test___setitem___persistent_class(self):
+        class pclass(object):
+            pass
+        cache = self._makeOne()
+
+        cache['pclass'] = pclass
+
+        self.assertEqual(len(cache), 1)
+        self.assertEqual(len(cache.items()), 0)
+        self.assertEqual(len(cache.klass_items()), 1)
+        self.assertEqual(cache.klass_items()[0][0], 'pclass')
+        self.failUnless(cache.klass_items()[0][1] is pclass)
+        self.failUnless(cache['pclass'] is pclass)
+        self.failUnless(cache.get('pclass') is pclass)
+
+    def test___delitem___non_string_oid_raises_ValueError(self):
+        cache = self._makeOne()
+
+        try:
+            del cache[object()]
+        except ValueError:
+            pass
+        else:
+            self.fail("Didn't raise ValueError with non-string OID.")
+
+    def test___delitem___nonesuch_raises_KeyError(self):
+        cache = self._makeOne()
+        original = self._makePersist()
+
+        try:
+            del cache['nonesuch']
+        except KeyError:
+            pass
+        else:
+            self.fail("Didn't raise KeyError with nonesuch OID.")
+
+    def test_lruitems(self):
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        cache['one'] = self._makePersist(oid='one', state=UPTODATE)
+        cache['two'] = self._makePersist(oid='two', state=UPTODATE)
+        cache['three'] = self._makePersist(oid='three', state=UPTODATE)
+
+        items = cache.lru_items()
+        self.assertEqual(len(items), 3)
+        self.assertEqual(items[0][0], 'one')
+        self.assertEqual(items[1][0], 'two')
+        self.assertEqual(items[2][0], 'three')
+
+    def test_mru_nonesuch_raises_KeyError(self):
+        cache = self._makeOne()
+
+        try:
+            cache.mru('nonesuch')
+        except KeyError:
+            pass
+        else:
+            self.fail("Didn't raise KeyError with nonesuch OID.")
+
+    def test_mru_normal(self):
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        cache['one'] = self._makePersist(oid='one', state=UPTODATE)
+        cache['two'] = self._makePersist(oid='two', state=UPTODATE)
+        cache['three'] = self._makePersist(oid='three', state=UPTODATE)
+
+        cache.mru('two')
+
+        self.assertEqual(cache.ringlen(), 3)
+        items = cache.lru_items()
+        self.assertEqual(len(items), 3)
+        self.assertEqual(items[0][0], 'one')
+        self.assertEqual(items[1][0], 'three')
+        self.assertEqual(items[2][0], 'two')
+
+    def test_mru_first(self):
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        cache['one'] = self._makePersist(oid='one', state=UPTODATE)
+        cache['two'] = self._makePersist(oid='two', state=UPTODATE)
+        cache['three'] = self._makePersist(oid='three', state=UPTODATE)
+
+        cache.mru('one')
+
+        self.assertEqual(cache.ringlen(), 3)
+        items = cache.lru_items()
+        self.assertEqual(len(items), 3)
+        self.assertEqual(items[0][0], 'two')
+        self.assertEqual(items[1][0], 'three')
+        self.assertEqual(items[2][0], 'one')
+
+    def test_mru_last(self):
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        cache['one'] = self._makePersist(oid='one', state=UPTODATE)
+        cache['two'] = self._makePersist(oid='two', state=UPTODATE)
+        cache['three'] = self._makePersist(oid='three', state=UPTODATE)
+
+        cache.mru('three')
+
+        self.assertEqual(cache.ringlen(), 3)
+        items = cache.lru_items()
+        self.assertEqual(len(items), 3)
+        self.assertEqual(items[0][0], 'one')
+        self.assertEqual(items[1][0], 'two')
+        self.assertEqual(items[2][0], 'three')
+
+    def test_incrgc_simple(self):
+        from persistent.interfaces import UPTODATE
+        cache = self._makeOne()
+        oids = []
+        for i in range(100):
+            oid = 'oid_%04d' % i
+            oids.append(oid)
+            cache[oid] = self._makePersist(oid=oid, state=UPTODATE)
+        self.assertEqual(cache.cache_non_ghost_count, 100)
+
+        cache.incrgc()
+
+        self.assertEqual(cache.cache_non_ghost_count, 10)
+        items = cache.lru_items()
+        self.assertEqual(len(items), 10)
+        self.assertEqual(items[0][0], 'oid_0090')
+        self.assertEqual(items[1][0], 'oid_0091')
+        self.assertEqual(items[2][0], 'oid_0092')
+        self.assertEqual(items[3][0], 'oid_0093')
+        self.assertEqual(items[4][0], 'oid_0094')
+        self.assertEqual(items[5][0], 'oid_0095')
+        self.assertEqual(items[6][0], 'oid_0096')
+        self.assertEqual(items[7][0], 'oid_0097')
+        self.assertEqual(items[8][0], 'oid_0098')
+        self.assertEqual(items[9][0], 'oid_0099')
+
+        for oid in oids[:90]:
+            self.failUnless(cache.get(oid) is None)
+
+        for oid in oids[90:]:
+            self.failIf(cache.get(oid) is None)
+
+
+class DummyPersistent(object):
+    pass
+
+class DummyConnection:
+
+    def setklassstate(self, obj):
+        """Method used by PickleCache."""
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite(),
+        ))
+
+if __name__ == '__main__':
+    unittest.main()



More information about the Zodb-checkins mailing list