[Zodb-checkins] SVN: ZODB/branches/elro-python_persistent/src/persistent/ pure python persistence work from black forest sprint
Laurence Rowe
l at lrowe.co.uk
Thu Mar 19 12:45:51 EDT 2009
Log message for revision 98277:
pure python persistence work from black forest sprint
Changed:
U ZODB/branches/elro-python_persistent/src/persistent/__init__.py
A ZODB/branches/elro-python_persistent/src/persistent/_cache.py
A ZODB/branches/elro-python_persistent/src/persistent/_persistence.py
U ZODB/branches/elro-python_persistent/src/persistent/interfaces.py
U ZODB/branches/elro-python_persistent/src/persistent/tests/persistent.txt
U ZODB/branches/elro-python_persistent/src/persistent/tests/test_persistent.py
-=-
Modified: ZODB/branches/elro-python_persistent/src/persistent/__init__.py
===================================================================
--- ZODB/branches/elro-python_persistent/src/persistent/__init__.py 2009-03-19 16:43:32 UTC (rev 98276)
+++ ZODB/branches/elro-python_persistent/src/persistent/__init__.py 2009-03-19 16:45:51 UTC (rev 98277)
@@ -18,6 +18,7 @@
from cPersistence import Persistent, GHOST, UPTODATE, CHANGED, STICKY
from cPickleCache import PickleCache
+#from persistent._persistence import Persistent
from cPersistence import simple_new
import copy_reg
Added: ZODB/branches/elro-python_persistent/src/persistent/_cache.py
===================================================================
--- ZODB/branches/elro-python_persistent/src/persistent/_cache.py (rev 0)
+++ ZODB/branches/elro-python_persistent/src/persistent/_cache.py 2009-03-19 16:45:51 UTC (rev 98277)
@@ -0,0 +1,129 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# 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.
+#
+##############################################################################
+
+from _persistence import observer, Changed, Used, Unused, GhostState, SavedState, ChangedState
+from _persistence import PersistentObserver as PersistentObserverBase
+from UserDict import UserDict
+
+class PersistentCache(UserDict):
+ """
+ Maintains a collection of weak references to persistent objects.
+ """
+ class PersistentObserver(PersistentObserverBase):
+ pass
+
+class DM:
+ # A very basic dm
+ def __init__(self, serial='00000000', oid=1):
+ self.serial = serial
+ self.oid = oid
+ self.cache = PersistentCache()
+ self.storage = {} # a mapping of oid -> (klass, data)
+ self.registered = set() # objects which have changed
+
+ def __setitem__(self, event, obsv):
+ """Events sent through the observer
+ """
+
+ if event & Changed:
+ if obsv.state is GhostState:
+ self.setstate(obsv())
+ if obsv.state is SavedState:
+ self.register(obsv())
+ obsv.state = ChangedState
+
+ elif event & Used:
+ if obsv.state is GhostState:
+ self.setstate(obsv())
+ obsv.state = SavedState
+
+ ######
+ # These methods are called by users of Persistent
+
+ def activate(self, pobj):
+ obsv = observer(pobj)
+ if obsv and obsv.state is GhostState: # unattached objects cannot be ghost
+ self.setstate(pobj)
+
+ def deactivate(self, pobj):
+ obsv = observer(pobj)
+ if obsv and obsv.state is SavedState and not obsv.nonghostifiable:
+ obsv()._p_release_state()
+ obsv.state = GhostState
+
+ def invalidate(self, pobj):
+ # ignore nonghostifiable for now
+ obsv = observer(pobj)
+ if obsv and not obsv.nonghostifiable:
+ self.deregister(pobj)
+ pobj._p_release_state()
+ obsv.state = GhostState
+
+ def unchanged(self, pobj):
+ obsv = observer(pobj)
+ if obsv and obsv.state == ChangedState:
+ self.deregister(pobj)
+ obsv.state = SavedState
+ #
+ ######
+
+ def setstate(self, pobj):
+ obsv = observer(pobj)
+ klass, data = self.storage[obsv.oid]
+ pobj.__setstate__(data)
+ obsv.state = SavedState
+
+ def register(self, pobj):
+ """Register a Persistent object with the transaction
+
+ As a side effect this ensures that changed objects cannot be gc'ed
+ """
+ self.registered.add(pobj)
+
+ def deregister(self, pobj):
+ self.registered.discard(pobj)
+
+ def add(self, pobj, oid=None):
+ """oid is there only for the tests
+ """
+ assert observer(pobj) is None
+ if oid is None:
+ oid = self.oid
+ self.oid += 1
+ obsv = self.cache.PersistentObserver(pobj)
+ obsv.state = SavedState
+ obsv.oid = oid
+ obsv.serial = self.serial
+ obsv.manager = self
+ obsv.nonghostifiable = getattr(type(pobj), '_p_nonghostifiable', False)
+ self.cache[oid] = obsv
+
+ def get(self, oid):
+ obsv = self.cache.get(oid, None)
+ if obsv is not None:
+ pobj = obsv()
+ if pobj is not None:
+ return pobj
+
+ klass, data = self.storage[oid] # this might raise a keyerror
+ nonghostifiable = getattr(klass, '_p_nonghostifiable', False)
+ pobj = object.__new__(klass)
+ obsv = self.cache.PersistentObserver(pobj)
+ obsv.state = GhostState
+ obsv.serial = self.serial
+ obsv.oid = oid
+ obsv.manager = self
+ obsv.nonghostifiable = nonghostifiable
+ self.cache[obsv.oid] = obsv
+ return pobj
Added: ZODB/branches/elro-python_persistent/src/persistent/_persistence.py
===================================================================
--- ZODB/branches/elro-python_persistent/src/persistent/_persistence.py (rev 0)
+++ ZODB/branches/elro-python_persistent/src/persistent/_persistence.py 2009-03-19 16:45:51 UTC (rev 98277)
@@ -0,0 +1,200 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# 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.
+#
+##############################################################################
+
+from weakref import ref, getweakrefs
+
+GhostState = None # The object is a ghost
+ChangedState = True # The object has been modified
+SavedState = False # The object is not a ghost and has not been modified.
+
+Used = 1
+Unused = 2
+Changed = 4
+
+class PersistentObserver(ref):
+ """
+ one entry in a cache
+
+ There is one observer but multiple events, for persistence.
+ """
+
+ def __init__(self, obj):
+ ref.__init__(self, obj, self.cb)
+ self.used = 0 # should be read-only
+
+ # assigned by the data manager
+ # self.oid = persistent object id
+ # self.serial = object serial or None for a ghost
+ # self.manager = ? (r/o attribute)
+ # self.manager_data = dm specific data
+ # self.nonghostifiable
+ self.state = SavedState
+
+ # self.mtime ???
+
+ def cb(self, wref):
+ pass
+
+ def __setitem__(self, event, ignored):
+
+ if event & Used:
+ self.used += 1
+
+ elif event & Unused:
+ self.used -= 1
+
+ self.manager[event] = self
+
+
+def observer(pobj):
+ """Return an object's persistent observer, if any.
+ """
+ for r in getweakrefs(pobj):
+ if isinstance(r, PersistentObserver):
+ return r
+ return None
+
+def oid(pobj):
+ obsv = observer(pobj)
+ if obsv:
+ return obsv.oid
+
+def manager(pobj):
+ obsv = observer(pobj)
+ if obsv:
+ return obsv.manager
+
+def state(pobj):
+ obsv = observer(pobj)
+ if obsv:
+ return obsv.state
+ else:
+ return SavedState
+
+def changed(pobj):
+ """
+ If the object is in the saved or read state, move it to the modified
+ state. Else, do nothing.
+
+ This function is the preferred way to tell the persistence system that
+ an object has changed in cases where the persistence system cannot
+ detect a change automatically.
+ """
+ obsv = observer(pobj)
+ if obsv:
+ obsv[Changed] = 1
+
+def unchanged(pobj):
+ """
+ If the object is in the changed state, move it to the saved state.
+ Else, do nothing.
+
+ This function is used in those very rare situations in which the
+ persistence system would determine that an object has changed when it
+ should not.
+ """
+ dm = manager(pobj)
+ if dm:
+ dm.unchanged(pobj)
+
+def invalidate(pobj):
+ dm = manager(pobj)
+ if dm:
+ dm.invalidate(pobj)
+
+def activate(pobj):
+ dm = manager(pobj)
+ if dm:
+ dm.activate(pobj)
+
+def deactivate(pobj):
+ dm = manager(pobj)
+ if dm:
+ dm.deactivate(pobj)
+
+def mtime(pobj):
+ dm = manager(pobj)
+ if dm:
+ dm.mtime(pobj)
+
+def _get_p_state(pobj):
+ s = state(pobj)
+ if s is GhostState:
+ return -1
+ if s is SavedState:
+ return 0
+ if s is ChangedState:
+ return 1
+
+def _set_p_changed(pobj, value):
+ if value:
+ changed(pobj)
+ else:
+ unchanged(pobj)
+
+class Persistent(object):
+ """Mix-in class providing IPersistent support
+ """
+
+ # Deprecated
+ _p_jar = property(manager)
+ _p_oid = property(oid)
+ _p_changed = property(state, _set_p_changed, invalidate)
+ _p_state = property(_get_p_state)
+ _p_serial = property(lambda self: observer(self).serial)
+ _p_mtime = property(mtime)
+ _p_invalidate = invalidate
+ _p_activate = activate
+ _p_deactivate = deactivate
+
+ # New interface
+
+ _p_nonghostifiable = False # the default
+
+ def _p_release_state(self):
+ # unconditionally release state. Called by the data manager
+ del self.__dict__
+
+ def __getstate__(self):
+ return dict((k, v) for k, v in self.__dict__.iteritems() if not k[:3] == '_v_')
+
+ def __setstate__(self, state):
+ del self.__dict__
+ self.__dict__.update(state)
+
+ def __getattribute__(self, name):
+ if name[:3] != '_p_' and name not in ('__dict__', '__setstate__'):
+ obsv = observer(self)
+ if obsv:
+ obsv[Used] = 1
+ try:
+ return object.__getattribute__(self, name)
+ finally:
+ if obsv:
+ obsv[Unused] = 1
+ else:
+ return object.__getattribute__(self, name)
+
+ def __setattr__(self, name, v):
+ if name[:3] not in ('_p_', '_v_') and name != '__dict__':
+ obsv = observer(self)
+ if obsv:
+ obsv[Changed | Used] = 1
+ try:
+ return object.__setattr__(self, name, v)
+ finally:
+ if obsv:
+ obsv[Unused] = 1
+ else:
+ return object.__setattr__(self, name, v)
Modified: ZODB/branches/elro-python_persistent/src/persistent/interfaces.py
===================================================================
--- ZODB/branches/elro-python_persistent/src/persistent/interfaces.py 2009-03-19 16:43:32 UTC (rev 98276)
+++ ZODB/branches/elro-python_persistent/src/persistent/interfaces.py 2009-03-19 16:45:51 UTC (rev 98277)
@@ -156,14 +156,14 @@
"""
_p_jar = Attribute(
- """The data manager for the object.
+ """The data manager for the object. (Deprecated)
The data manager implements the IPersistentDataManager interface.
If there is no data manager, then this is None.
""")
_p_oid = Attribute(
- """The object id.
+ """The object id. (Deprecated)
It is up to the data manager to assign this.
The special value None is reserved to indicate that an object
@@ -173,7 +173,7 @@
""")
_p_changed = Attribute(
- """The persistent state of the object.
+ """The persistent state of the object. (Deprecated)
This is one of:
@@ -203,7 +203,7 @@
""")
_p_serial = Attribute(
- """The object serial number.
+ """The object serial number. (Deprecated)
This member is used by the data manager to distiguish distinct
revisions of a given persistent object.
@@ -221,9 +221,13 @@
def __setstate__(state):
"""Set the object data.
"""
+
+ def _p_release_state():
+ """Release the object's state.
+ """
def _p_activate():
- """Activate the object.
+ """Activate the object. (Deprecated)
Change the object to the saved state if it is a ghost.
"""
@@ -238,7 +242,7 @@
"""
def _p_invalidate():
- """Invalidate the object.
+ """Invalidate the object. (Deprecated)
Invalidate the object. This causes any data to be thrown
away, even if the object is in the changed state. The object
@@ -255,6 +259,39 @@
conflicts for this object.
"""
+class IPersistentObserver(Interface):
+
+ def __setitem__(event, ignore):
+ """Efficiently notify observer or persistent object usage.
+
+ Event is one of Used, Unused or Changed.
+ """
+
+ def activate():
+ """Activate the referenced object.
+
+ Change the object to the saved state if it is a ghost.
+ """
+
+ def invalidate():
+ """Invalidate the referenced object.
+
+ Invalidate the object. This causes any data to be thrown
+ away, even if the object is in the changed state. The object
+ is moved to the ghost state; further accesses will cause
+ object data to be reloaded.
+ """
+
+ def ghostify():
+ """Move the referenced object to the ghost state.
+
+ Called by _p_deactivate
+ """
+
+ def unchanged():
+ """
+ """
+
# TODO: document conflict resolution.
class IPersistentDataManager(Interface):
Modified: ZODB/branches/elro-python_persistent/src/persistent/tests/persistent.txt
===================================================================
--- ZODB/branches/elro-python_persistent/src/persistent/tests/persistent.txt 2009-03-19 16:43:32 UTC (rev 98276)
+++ ZODB/branches/elro-python_persistent/src/persistent/tests/persistent.txt 2009-03-19 16:45:51 UTC (rev 98277)
@@ -24,13 +24,19 @@
loading and storing the state of a persistent object. It's stored in
the ``_p_jar`` attribute of a persistent object.
- >>> class DM:
- ... def __init__(self):
+ >>> from persistent._cache import DM as BaseDM
+ >>> from persistent._persistence import observer, SavedState, Persistent
+
+ >>> class DM(BaseDM):
+ ... def __init__(self, *args, **kwargs):
... self.called = 0
+ ... BaseDM.__init__(self, *args, **kwargs)
... def register(self, ob):
... self.called += 1
+ ... BaseDM.register(self, ob)
... def setstate(self, ob):
- ... ob.__setstate__({'x': 42})
+ ... ob.__setstate__({'x':42})
+ ... observer(ob).state = SavedState
>>> class BrokenDM(DM):
... def register(self,ob):
@@ -39,9 +45,7 @@
... def setstate(self,ob):
... raise NotImplementedError
- >>> from persistent import Persistent
-
Test Persistent without Data Manager
------------------------------------
@@ -92,10 +96,13 @@
Next try some tests of an object with a data manager. The `DM` class is
a simple testing stub.
+ >>> dm = DM(serial="00000007")
>>> p = P()
- >>> dm = DM()
- >>> p._p_oid = "00000012"
- >>> p._p_jar = dm
+ >>> dm.add(p, oid="00000012")
+ >>> p._p_oid
+ '00000012'
+ >>> p._p_jar is dm
+ True
>>> p._p_changed
0
>>> dm.called
@@ -163,6 +170,7 @@
implementations.
>>> p = P()
+ >>> dm.add(p)
>>> state = p.__getstate__()
>>> isinstance(state, dict)
True
@@ -188,10 +196,9 @@
The ``_p_serial`` attribute is not affected by calling setstate.
- >>> p._p_serial = "00000012"
>>> p.__setstate__(p.__getstate__())
>>> p._p_serial
- '00000012'
+ '00000007'
Change Ghost test
@@ -203,8 +210,7 @@
ignored.
>>> p = P()
- >>> p._p_jar = DM()
- >>> p._p_oid = 1
+ >>> dm.add(p)
>>> p._p_deactivate()
>>> p._p_changed # None
>>> p._p_state # ghost state
@@ -226,8 +232,7 @@
``_p_invalidate()``.
>>> p = P()
- >>> p._p_oid = 1
- >>> p._p_jar = DM()
+ >>> dm.add(p)
>>> p._p_deactivate()
>>> p._p_state
-1
@@ -258,9 +263,9 @@
up-to-date state. It shouldn't change to the modified state, because it won't
be saved when the transaction commits.
+ >>> dm = BrokenDM()
>>> p = P()
- >>> p._p_oid = 1
- >>> p._p_jar = BrokenDM()
+ >>> dm.add(p)
>>> p._p_state
0
>>> p._p_jar.called
@@ -277,9 +282,9 @@
Make sure that exceptions that occur inside the data manager's ``setstate()``
method propagate out to the caller.
+ >>> dm = BrokenDM()
>>> p = P()
- >>> p._p_oid = 1
- >>> p._p_jar = BrokenDM()
+ >>> dm.add(p)
>>> p._p_deactivate()
>>> p._p_state
-1
Modified: ZODB/branches/elro-python_persistent/src/persistent/tests/test_persistent.py
===================================================================
--- ZODB/branches/elro-python_persistent/src/persistent/tests/test_persistent.py 2009-03-19 16:43:32 UTC (rev 98276)
+++ ZODB/branches/elro-python_persistent/src/persistent/tests/test_persistent.py 2009-03-19 16:45:51 UTC (rev 98277)
@@ -12,7 +12,7 @@
#
##############################################################################
from zope.testing import doctest
-from persistent import Persistent
+from persistent._persistence import Persistent
class P(Persistent):
def __init__(self):
More information about the Zodb-checkins
mailing list