[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 Checkins mailing list