[Zodb-checkins] SVN: ZODB/branches/tseaver-python_picklecache-2/src/persistent/ Add attribute access / mutation protocol.

Tres Seaver tseaver at palladion.com
Tue Feb 15 19:00:51 EST 2011


Log message for revision 120361:
  Add attribute access / mutation protocol.

Changed:
  U   ZODB/branches/tseaver-python_picklecache-2/src/persistent/interfaces.py
  U   ZODB/branches/tseaver-python_picklecache-2/src/persistent/pypersistent.py
  U   ZODB/branches/tseaver-python_picklecache-2/src/persistent/tests/test_pypersistent.py

-=-
Modified: ZODB/branches/tseaver-python_picklecache-2/src/persistent/interfaces.py
===================================================================
--- ZODB/branches/tseaver-python_picklecache-2/src/persistent/interfaces.py	2011-02-16 00:00:50 UTC (rev 120360)
+++ ZODB/branches/tseaver-python_picklecache-2/src/persistent/interfaces.py	2011-02-16 00:00:51 UTC (rev 120361)
@@ -247,6 +247,26 @@
         May be set by the data manager.
         """)
 
+    # Attribute access protocol
+    def __getattribute__(name):
+        """ Handle activating ghosts before returning an attribute value.
+
+        "Special" attributes and '_p_*' attributes don't require activation.
+        """
+
+    def __setattr__(name, value):
+        """ Handle activating ghosts before setting an attribute value.
+
+        "Special" attributes and '_p_*' attributes don't require activation.
+        """
+
+    def __delattr__(name):
+        """ Handle activating ghosts before deleting an attribute value.
+
+        "Special" attributes and '_p_*' attributes don't require activation.
+        """
+
+    # Pickling protocol.
     def __getstate__():
         """Get the object data.
 
@@ -262,6 +282,7 @@
         """Reduce an object to contituent parts for serialization.
         """
 
+    # Custom methods
     def _p_activate():
         """Activate the object.
 

Modified: ZODB/branches/tseaver-python_picklecache-2/src/persistent/pypersistent.py
===================================================================
--- ZODB/branches/tseaver-python_picklecache-2/src/persistent/pypersistent.py	2011-02-16 00:00:50 UTC (rev 120360)
+++ ZODB/branches/tseaver-python_picklecache-2/src/persistent/pypersistent.py	2011-02-16 00:00:51 UTC (rev 120361)
@@ -30,6 +30,8 @@
 _CHANGED = 0x0001
 _STICKY = 0x0002
 
+_OGA = object.__getattribute__
+
 # Allowed values for _p_state
 GHOST = -1
 UPTODATE = 0
@@ -128,12 +130,12 @@
         if self.__flags is None:
             if value is not None:
                 self._p_activate()
-                self._set_changed_flag(value)
+                self._p_set_changed_flag(value)
         else:
             if value is None: # -> ghost
                 self._p_deactivate()
             else:
-                self._set_changed_flag(value)
+                self._p_set_changed_flag(value)
 
     def _del_changed(self):
         self._p_invalidate()
@@ -210,6 +212,39 @@
     _p_status = property(_get_status)
 
     # Methods from IPersistent.
+    def __getattribute__(self, name):
+        """ See IPersistent.
+        """
+        if (not name.startswith('_Persistent__') and
+            not name.startswith('_p_') and
+            name not in SPECIAL_NAMES):
+            if _OGA(self, '_Persistent__flags') is None:
+                _OGA(self, '_p_activate')()
+            _OGA(self, '_p_accessed')()
+        return _OGA(self, name)
+
+    def __setattr__(self, name, value):
+        if (not name.startswith('_Persistent__') and
+            not name.startswith('_p_')):
+            if _OGA(self, '_Persistent__flags') is None:
+                _OGA(self, '_p_activate')()
+            _OGA(self, '_p_accessed')()
+            if (_OGA(self, '_Persistent__jar') is not None and
+                _OGA(self, '_Persistent__oid') is not None):
+                _OGA(self, '_p_register')()
+        object.__setattr__(self, name, value)
+
+    def __delattr__(self, name):
+        if (not name.startswith('_Persistent__') and
+            not name.startswith('_p_')):
+            if _OGA(self, '_Persistent__flags') is None:
+                _OGA(self, '_p_activate')()
+            _OGA(self, '_p_accessed')()
+            if (_OGA(self, '_Persistent__jar') is not None and
+                _OGA(self, '_Persistent__oid') is not None):
+                _OGA(self, '_p_register')()
+        object.__delattr__(self, name)
+
     def __getstate__(self):
         """ See IPersistent.
         """
@@ -254,7 +289,7 @@
         if name.startswith('_p_') or name in SPECIAL_NAMES:
             return True
         self._p_activate()
-        # TODO set the object as acceessed with the jar's cache.
+        self._p_accessed()
         return False
 
     def _p_setattr(self, name, value):
@@ -264,7 +299,7 @@
             setattr(self, name, value)
             return True
         self._p_activate()
-        # TODO set the object as acceessed with the jar's cache.
+        self._p_accessed()
         return False
 
     def _p_delattr(self, name):
@@ -274,19 +309,29 @@
             delattr(self, name)
             return True
         self._p_activate()
-        # TODO set the object as acceessed with the jar's cache.
+        self._p_accessed()
         return False
 
-    # Helper methods:  not APIs
-    def _register(self):
+    # Helper methods:  not APIs:  we name them with '_p_' to bypass
+    # the __getattribute__ bit which bumps the cache.
+    def _p_register(self):
         if self.__jar is not None and self.__oid is not None:
             self.__jar.register(self)
 
-    def _set_changed_flag(self, value):
+    def _p_set_changed_flag(self, value):
         if value:
             before = self.__flags
             self.__flags |= _CHANGED
             if before != self.__flags:
-                self._register()
+                self._p_register()
         else:
             self.__flags &= ~_CHANGED
+
+    def _p_accessed(self):
+        # Notify the jar's pickle cache that we have been accessed.
+        # This relies on what has been (until now) an implementation
+        # detail, the '_cache' attribute of the jar.  We made it a
+        # private API to avoid the cycle of keeping a reference to
+        # the cache on the persistent object.
+        if self.__jar is not None and self.__oid is not None:
+            self.__jar._cache.mru(self.__oid)

Modified: ZODB/branches/tseaver-python_picklecache-2/src/persistent/tests/test_pypersistent.py
===================================================================
--- ZODB/branches/tseaver-python_picklecache-2/src/persistent/tests/test_pypersistent.py	2011-02-16 00:00:50 UTC (rev 120360)
+++ ZODB/branches/tseaver-python_picklecache-2/src/persistent/tests/test_pypersistent.py	2011-02-16 00:00:51 UTC (rev 120361)
@@ -25,20 +25,32 @@
     def _makeJar(self):
         from zope.interface import implements
         from persistent.interfaces import IPersistentDataManager
+
+        class _Cache(object):
+            def __init__(self):
+                self._mru = []
+            def mru(self, oid):
+                self._mru.append(oid)
+
         class _Jar(object):
             implements(IPersistentDataManager)
             def __init__(self):
                 self._loaded = []
                 self._registered = []
+                self._cache = _Cache()
             def setstate(self, obj):
                 self._loaded.append(obj._p_oid)
             def register(self, obj):
                 self._registered.append(obj._p_oid)
+
         return _Jar()
 
-    def _makeOneWithJar(self):
-        OID = '1' * 8
-        inst = self._makeOne()
+    def _makeOneWithJar(self, klass=None):
+        OID = '\x01' * 8
+        if klass is not None:
+            inst = klass()
+        else:
+            inst = self._makeOne()
         jar = self._makeJar()
         inst._p_jar = jar
         inst._p_oid = OID
@@ -419,6 +431,195 @@
         inst._p_estimated_size = 123
         self.assertEqual(inst._p_estimated_size, 0)
 
+    def test___getattribute___p__names(self):
+        NAMES = ['_p_jar',
+                 '_p_oid',
+                 '_p_changed',
+                 '_p_serial',
+                 '_p_mtime',
+                 '_p_state',
+                 '_p_estimated_size',
+                 '_p_sticky',
+                 '_p_status',
+                ]
+        inst, jar, OID = self._makeOneWithJar()
+        jar._cache._mru = []
+        for name in NAMES:
+            getattr(inst, name)
+        self.assertEqual(jar._cache._mru, [])
+
+    def test___getattribute__special_name(self):
+        from persistent.pypersistent import SPECIAL_NAMES
+        inst, jar, OID = self._makeOneWithJar()
+        jar._cache._mru = []
+        for name in SPECIAL_NAMES:
+            getattr(inst, name, None)
+        self.assertEqual(jar._cache._mru, [])
+
+    def test___getattribute__normal_name_from_new(self):
+        class Derived(self._getTargetClass()):
+            normal = 'value'
+        inst = Derived()
+        self.assertEqual(getattr(inst, 'normal', None), 'value')
+
+    def test___getattribute__normal_name_from_unsaved(self):
+        class Derived(self._getTargetClass()):
+            normal = 'value'
+        inst = Derived()
+        inst._p_changed = True
+        self.assertEqual(getattr(inst, 'normal', None), 'value')
+
+    def test___getattribute__normal_name_from_ghost(self):
+        class Derived(self._getTargetClass()):
+            normal = 'value'
+        inst, jar, OID = self._makeOneWithJar(Derived)
+        jar._cache._mru = []
+        self.assertEqual(getattr(inst, 'normal', None), 'value')
+        self.assertEqual(jar._cache._mru, [OID])
+
+    def test___getattribute__normal_name_from_saved(self):
+        class Derived(self._getTargetClass()):
+            normal = 'value'
+        inst, jar, OID = self._makeOneWithJar(Derived)
+        inst._p_changed = False
+        jar._cache._mru = []
+        self.assertEqual(getattr(inst, 'normal', None), 'value')
+        self.assertEqual(jar._cache._mru, [OID])
+
+    def test___getattribute__normal_name_from_changed(self):
+        class Derived(self._getTargetClass()):
+            normal = 'value'
+        inst, jar, OID = self._makeOneWithJar(Derived)
+        inst._p_changed = True
+        jar._cache._mru = []
+        self.assertEqual(getattr(inst, 'normal', None), 'value')
+        self.assertEqual(jar._cache._mru, [OID])
+
+    def test___setattr___p__names(self):
+        inst, jar, OID = self._makeOneWithJar()
+        NAMES = [('_p_jar', jar),
+                 ('_p_oid', OID),
+                 ('_p_changed', False),
+                 ('_p_serial', '\x01' * 8),
+                 ('_p_estimated_size', 0),
+                 ('_p_sticky', False),
+                ]
+        jar._cache._mru = []
+        for name, value in NAMES:
+            setattr(inst, name, value)
+        self.assertEqual(jar._cache._mru, [])
+
+    def test___setattr__normal_name_from_new(self):
+        class Derived(self._getTargetClass()):
+            normal = 'before'
+        inst = Derived()
+        setattr(inst, 'normal', 'after')
+        self.assertEqual(getattr(inst, 'normal', None), 'after')
+
+    def test___setattr__normal_name_from_unsaved(self):
+        class Derived(self._getTargetClass()):
+            normal = 'before'
+        inst = Derived()
+        inst._p_changed = True
+        setattr(inst, 'normal', 'after')
+        self.assertEqual(getattr(inst, 'normal', None), 'after')
+
+    def test___setattr__normal_name_from_ghost(self):
+        class Derived(self._getTargetClass()):
+            normal = 'before'
+        inst, jar, OID = self._makeOneWithJar(Derived)
+        jar._cache._mru = []
+        setattr(inst, 'normal', 'after')
+        self.assertEqual(jar._cache._mru, [OID])
+        self.assertEqual(jar._registered, [OID])
+        self.assertEqual(getattr(inst, 'normal', None), 'after')
+
+    def test___setattr__normal_name_from_saved(self):
+        class Derived(self._getTargetClass()):
+            normal = 'before'
+        inst, jar, OID = self._makeOneWithJar(Derived)
+        inst._p_changed = False
+        jar._cache._mru = []
+        setattr(inst, 'normal', 'after')
+        self.assertEqual(jar._cache._mru, [OID])
+        self.assertEqual(jar._registered, [OID])
+        self.assertEqual(getattr(inst, 'normal', None), 'after')
+
+    def test___setattr__normal_name_from_changed(self):
+        class Derived(self._getTargetClass()):
+            normal = 'before'
+        inst, jar, OID = self._makeOneWithJar(Derived)
+        inst._p_changed = True
+        jar._cache._mru = []
+        jar._registered = []
+        setattr(inst, 'normal', 'after')
+        self.assertEqual(jar._cache._mru, [OID])
+        self.assertEqual(jar._registered, [OID])
+        self.assertEqual(getattr(inst, 'normal', None), 'after')
+
+    def test___delattr___p__names(self):
+        inst, jar, OID = self._makeOneWithJar()
+        jar._cache._mru = []
+        jar._registered = []
+        delattr(inst, '_p_changed') #only del-able _p_ attribute.
+        self.assertEqual(jar._cache._mru, [])
+        self.assertEqual(jar._registered, [])
+
+    def test___delattr__normal_name_from_new(self):
+        class Derived(self._getTargetClass()):
+            normal = 'before'
+        inst = Derived()
+        setattr(inst, 'normal', 'after')
+        delattr(inst, 'normal')
+        self.assertEqual(getattr(inst, 'normal', None), 'before')
+
+    def test___delattr__normal_name_from_unsaved(self):
+        class Derived(self._getTargetClass()):
+            normal = 'before'
+        inst = Derived()
+        inst._p_changed = True
+        setattr(inst, 'normal', 'after')
+        delattr(inst, 'normal')
+        self.assertEqual(getattr(inst, 'normal', None), 'before')
+
+    def test___delattr__normal_name_from_ghost(self):
+        class Derived(self._getTargetClass()):
+            normal = 'before'
+        inst, jar, OID = self._makeOneWithJar(Derived)
+        setattr(inst, 'normal', 'after')
+        jar._cache._mru = []
+        jar._registered = []
+        delattr(inst, 'normal')
+        self.assertEqual(jar._cache._mru, [OID])
+        self.assertEqual(jar._registered, [OID])
+        self.assertEqual(getattr(inst, 'normal', None), 'before')
+
+    def test___delattr__normal_name_from_saved(self):
+        class Derived(self._getTargetClass()):
+            normal = 'before'
+        inst, jar, OID = self._makeOneWithJar(Derived)
+        setattr(inst, 'normal', 'after')
+        inst._p_changed = False
+        jar._cache._mru = []
+        jar._registered = []
+        delattr(inst, 'normal')
+        self.assertEqual(jar._cache._mru, [OID])
+        self.assertEqual(jar._registered, [OID])
+        self.assertEqual(getattr(inst, 'normal', None), 'before')
+
+    def test___delattr__normal_name_from_changed(self):
+        class Derived(self._getTargetClass()):
+            normal = 'before'
+        inst, jar, OID = self._makeOneWithJar(Derived)
+        setattr(inst, 'normal', 'after')
+        inst._p_changed = True
+        jar._cache._mru = []
+        jar._registered = []
+        delattr(inst, 'normal')
+        self.assertEqual(jar._cache._mru, [OID])
+        self.assertEqual(jar._registered, [OID])
+        self.assertEqual(getattr(inst, 'normal', None), 'before')
+
     def test___getstate__(self):
         inst = self._makeOne()
         self.assertEqual(inst.__getstate__(), ())
@@ -436,6 +637,32 @@
         self.assertEqual(inst._p_changed, None)
         self.assertEqual(inst._p_sticky, False)
 
+    def test___reduce__(self):
+        inst = self._makeOne()
+        first, second = inst.__reduce__()
+        self.assertEqual(first, (self._getTargetClass(),))
+        self.assertEqual(second, ())
+
+    def test___reduce__w_subclass_having_getstate(self):
+        class Derived(self._getTargetClass()):
+            def __getstate__(self):
+                return {}
+        inst = Derived()
+        first, second = inst.__reduce__()
+        self.assertEqual(first, (Derived,))
+        self.assertEqual(second, {})
+
+    def test___reduce__w_subclass_having_gna_and_getstate(self):
+        class Derived(self._getTargetClass()):
+            def __getnewargs__(self):
+                return ('a', 'b')
+            def __getstate__(self):
+                return {'foo': 'bar'}
+        inst = Derived()
+        first, second = inst.__reduce__()
+        self.assertEqual(first, (Derived, 'a', 'b'))
+        self.assertEqual(second, {'foo': 'bar'})
+
     def test__p_activate_from_new(self):
         inst = self._makeOne()
         inst._p_activate()
@@ -544,11 +771,23 @@
         inst._p_sticky = True
         self.assertRaises(ValueError, inst._p_invalidate)
 
-    def test__p_getattr_w__p__name(self):
+    def test__p_getattr_w__p__names(self):
+        NAMES = ['_p_jar',
+                 '_p_oid',
+                 '_p_changed',
+                 '_p_serial',
+                 '_p_mtime',
+                 '_p_state',
+                 '_p_estimated_size',
+                 '_p_sticky',
+                 '_p_status',
+                ]
         inst, jar, OID = self._makeOneWithJar()
-        self.failUnless(inst._p_getattr('_p_foo'))
+        for name in NAMES:
+            self.failUnless(inst._p_getattr(name))
         self.assertEqual(inst._p_status, 'ghost')
         self.assertEqual(list(jar._loaded), [])
+        self.assertEqual(list(jar._cache._mru), [])
 
     def test__p_getattr_w_special_names(self):
         from persistent.pypersistent import SPECIAL_NAMES
@@ -557,12 +796,14 @@
             self.failUnless(inst._p_getattr(name))
             self.assertEqual(inst._p_status, 'ghost')
         self.assertEqual(list(jar._loaded), [])
+        self.assertEqual(list(jar._cache._mru), [])
 
     def test__p_getattr_w_normal_name(self):
         inst, jar, OID = self._makeOneWithJar()
         self.failIf(inst._p_getattr('normal'))
         self.assertEqual(inst._p_status, 'saved')
         self.assertEqual(list(jar._loaded), [OID])
+        self.assertEqual(list(jar._cache._mru), [OID])
 
     def test__p_setattr_w__p__name(self):
         inst, jar, OID = self._makeOneWithJar()
@@ -570,12 +811,14 @@
         self.assertEqual(inst._p_status, 'ghost')
         self.assertEqual(inst._p_serial, '1' * 8)
         self.assertEqual(list(jar._loaded), [])
+        self.assertEqual(list(jar._cache._mru), [])
 
     def test__p_setattr_w_normal_name(self):
         inst, jar, OID = self._makeOneWithJar()
         self.failIf(inst._p_setattr('normal', 'value'))
         self.assertEqual(inst._p_status, 'saved')
         self.assertEqual(list(jar._loaded), [OID])
+        self.assertEqual(list(jar._cache._mru), [OID])
 
     def test__p_delattr_w__p__name(self):
         inst, jar, OID = self._makeOneWithJar()
@@ -585,35 +828,11 @@
         self.assertEqual(inst._p_status, 'ghost')
         self.assertEqual(inst._p_changed, None)
         self.assertEqual(list(jar._loaded), [])
+        self.assertEqual(list(jar._cache._mru), [])
 
     def test__p_delattr_w_normal_name(self):
         inst, jar, OID = self._makeOneWithJar()
         self.failIf(inst._p_delattr('normal'))
         self.assertEqual(inst._p_status, 'saved')
         self.assertEqual(list(jar._loaded), [OID])
-
-    def test___reduce__(self):
-        inst = self._makeOne()
-        first, second = inst.__reduce__()
-        self.assertEqual(first, (self._getTargetClass(),))
-        self.assertEqual(second, ())
-
-    def test___reduce__w_subclass_having_getstate(self):
-        class Derived(self._getTargetClass()):
-            def __getstate__(self):
-                return {}
-        inst = Derived()
-        first, second = inst.__reduce__()
-        self.assertEqual(first, (Derived,))
-        self.assertEqual(second, {})
-
-    def test___reduce__w_subclass_having_gna_and_getstate(self):
-        class Derived(self._getTargetClass()):
-            def __getnewargs__(self):
-                return ('a', 'b')
-            def __getstate__(self):
-                return {'foo': 'bar'}
-        inst = Derived()
-        first, second = inst.__reduce__()
-        self.assertEqual(first, (Derived, 'a', 'b'))
-        self.assertEqual(second, {'foo': 'bar'})
+        self.assertEqual(list(jar._cache._mru), [OID])



More information about the Zodb-checkins mailing list