[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