[Zodb-checkins] CVS: Zope/lib/python/persistent - README.txt:1.2
dict.py:1.2 interfaces.py:1.2 wref.py:1.2 __init__.py:1.7
cPersistence.c:1.76 cPersistence.h:1.29 cPickleCache.c:1.88
list.py:1.6
Jeremy Hylton
jeremy at zope.com
Wed Feb 18 22:00:07 EST 2004
Update of /cvs-repository/Zope/lib/python/persistent
In directory cvs.zope.org:/tmp/cvs-serv12124/lib/python/persistent
Modified Files:
__init__.py cPersistence.c cPersistence.h cPickleCache.c
list.py
Added Files:
README.txt dict.py interfaces.py wref.py
Log Message:
Merge zope3-zodb3-devel-branch to the Zope head (Zope 2 head).
Added support for persistent weak references and
PersistentWeakKeyDictionary.
Add _p_invalidate() method to persistence API. _p_deactivate() is
advisory from the cache. _p_invalidate() is called when database
invalidates object.
Port support for getattr/setattr hacks from ZODB4. The doctest tests
describe the new technique pretty well.
Remove unused arguments from some cPickleCache calls.
=== Zope/lib/python/persistent/README.txt 1.1 => 1.2 ===
--- /dev/null Wed Feb 18 22:00:07 2004
+++ Zope/lib/python/persistent/README.txt Wed Feb 18 21:59:30 2004
@@ -0,0 +1,24 @@
+===================
+Persistence support
+==================
+
+(This document is under construction. More basic documentation will
+ eventually appear here.)
+
+
+Overriding __getattr__, __getattribute__, __setattr__, and __delattr__
+-----------------------------------------------------------------------
+
+Subclasses can override the attribute-management methods. For the
+__getattr__ method, the behavior is like that for regular Python
+classes and for earlier versions of ZODB 3.
+
+For __getattribute__, __setattr__, and __delattr__, it is necessary to
+cal certain methods defined by persistent.Persistent. Detailed
+examples and documentation is provided in the test module,
+persistent.tests.test_overriding_attrs.
+
+
+
+
+
=== Zope/lib/python/persistent/dict.py 1.1 => 1.2 ===
--- /dev/null Wed Feb 18 22:00:07 2004
+++ Zope/lib/python/persistent/dict.py Wed Feb 18 21:59:30 2004
@@ -0,0 +1,77 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 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.
+#
+##############################################################################
+"""Python implementation of persistent container type
+
+$Id$
+"""
+
+import persistent
+from UserDict import IterableUserDict
+
+__metaclass__ = type
+
+class PersistentDict(persistent.Persistent, IterableUserDict):
+ """A persistent wrapper for mapping objects.
+
+ This class allows wrapping of mapping objects so that object
+ changes are registered. As a side effect, mapping objects may be
+ subclassed.
+ """
+
+ # IterableUserDict provides all of the mapping behavior. The
+ # PersistentDict class is responsible marking the persistent
+ # state as changed when a method actually changes the state. At
+ # the mapping API evolves, we may need to add more methods here.
+
+ __super_delitem = IterableUserDict.__delitem__
+ __super_setitem = IterableUserDict.__setitem__
+ __super_clear = IterableUserDict.clear
+ __super_update = IterableUserDict.update
+ __super_setdefault = IterableUserDict.setdefault
+ __super_popitem = IterableUserDict.popitem
+
+ __super_p_init = persistent.Persistent.__init__
+ __super_init = IterableUserDict.__init__
+
+ def __init__(self, dict=None):
+ self.__super_init(dict)
+ self.__super_p_init()
+
+ def __delitem__(self, key):
+ self.__super_delitem(key)
+ self._p_changed = True
+
+ def __setitem__(self, key, v):
+ self.__super_setitem(key, v)
+ self._p_changed = True
+
+ def clear(self):
+ self.__super_clear()
+ self._p_changed = True
+
+ def update(self, b):
+ self.__super_update(b)
+ self._p_changed = True
+
+ def setdefault(self, key, failobj=None):
+ # We could inline all of UserDict's implementation into the
+ # method here, but I'd rather not depend at all on the
+ # implementation in UserDict (simple as it is).
+ if not self.has_key(key):
+ self._p_changed = True
+ return self.__super_setdefault(key, failobj)
+
+ def popitem(self):
+ self._p_changed = True
+ return self.__super_popitem()
=== Zope/lib/python/persistent/interfaces.py 1.1 => 1.2 ===
--- /dev/null Wed Feb 18 22:00:07 2004
+++ Zope/lib/python/persistent/interfaces.py Wed Feb 18 21:59:30 2004
@@ -0,0 +1,334 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 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.
+#
+##############################################################################
+
+try:
+ from zope.interface import Interface
+ from zope.interface import Attribute
+except ImportError:
+
+ # just allow the module to compile if zope isn't available
+
+ class Interface(object):
+ pass
+
+ def Attribute(s):
+ return s
+
+class IPersistent(Interface):
+ """Python persistent interface
+
+ A persistent object can be in one of several states:
+
+ - Unsaved
+
+ The object has been created but not saved in a data manager.
+
+ In this state, the _p_changed attribute is non-None and false
+ and the _p_jar attribute is None.
+
+ - Saved
+
+ The object has been saved and has not been changed since it was saved.
+
+ In this state, the _p_changed attribute is non-None and false
+ and the _p_jar attribute is set to a data manager.
+
+ - Sticky
+
+ This state is identical to the up-to-date state except that the
+ object cannot transition to the ghost state. This is a special
+ state used by C methods of persistent objects to make sure that
+ state is not unloaded in the middle of computation.
+
+ In this state, the _p_changed attribute is non-None and false
+ and the _p_jar attribute is set to a data manager.
+
+ There is, currently, no official way to detect whether an object
+ is in the sticky state.
+
+ - Changed
+
+ The object has been changed.
+
+ In this state, the _p_changed attribute is true
+ and the _p_jar attribute is set to a data manager.
+
+ - Ghost
+
+ the object is in memory but its state has not been loaded from
+ the database (or has been unloaded). In this state, the object
+ doesn't contain any data.
+
+ The following state transactions are possible:
+
+ - Unsaved -> Saved
+
+ This transition occurs when an object is saved in the
+ database. This usually happens when an unsaved object is added
+ to (e.g. as an attribute or item of) a saved (or changed) object
+ and the transaction is committed.
+
+ - Saved -> Changed
+ Sticky -> Changed
+
+ This transition occurs when someone sets an attribute or sets
+ _p_changed to a true value on an up-to-date or sticky
+ object. When the transition occurs, the persistent object is
+ required to call the register method on its data manager,
+ passing itself as the only argument.
+
+ - Saved -> Sticky
+
+ This transition occurs when C code marks the object as sticky to
+ prevent its deactivation and transition to the ghost state.
+
+ - Saved -> Ghost
+
+ This transition occurs when an saved object is deactivated, by:
+ calling _p_deactivate, setting _p_changed to None, or deleting
+ _p_changed.
+
+ - Sticky -> Saved
+
+ This transition occurs when C code unmarks the object as sticky to
+ allow its deactivation and transition to the ghost state.
+
+ - Changed -> Saved
+
+ This transition occurs when a transaction is committed.
+ The data manager affects the transaction by setting _p_changed
+ to a true value.
+
+ - Changed -> Ghost
+
+ This transition occurs when a transaction is aborted.
+ The data manager affects the transaction by deleting _p_changed.
+
+ - Ghost -> Saved
+
+ This transition occurs when an attribute or operation of a ghost
+ is accessed and the object's state is loaded from the database.
+
+ Note that there is a separate C API that is not included here.
+ The C API requires a specific data layout and defines the sticky
+ state that is used to prevent object deactivation while in C
+ routines.
+
+ """
+
+ _p_jar=Attribute(
+ """The data manager for the object
+
+ The data manager implements the IPersistentDataManager interface.
+ If there is no data manager, then this is None.
+ """)
+
+ _p_oid=Attribute(
+ """The object id
+
+ It is up to the data manager to assign this.
+ The special value None is reserved to indicate that an object
+ id has not been assigned.
+ """)
+
+ _p_changed=Attribute(
+ """The persistent state of the object
+
+ This is one of:
+
+ None -- The object is a ghost. It is not active.
+
+ false -- The object is saved (or has never been saved).
+
+ true -- The object has been modified.
+
+ The object state may be changed by assigning this attribute,
+ however, assigning None is ignored if the object is not in the
+ up-to-date state.
+
+ Note that an object can change to the modified state only if
+ it has a data manager. When such a state change occurs, the
+ 'register' method of the data manager is called, passing the
+ persistent object.
+
+ Deleting this attribute forces deactivation independent of
+ existing state.
+
+ Note that an attribute is used for this to allow optimized
+ cache implementations.
+ """)
+
+ _p_serial=Attribute(
+ """The object serial number
+
+ This is an arbitrary object.
+ """)
+
+ _p_atime=Attribute(
+ """The integer object access time, in seconds, modulus one day
+
+ XXX When does a day start, the current implementation appears
+ to use gmtime, but this hasn't be explicitly specified.
+
+ XXX Why just one day?
+ """)
+
+ def __getstate__():
+ """Get the object state data
+
+ The state should not include persistent attributes ("_p_name")
+ """
+
+ def __setstate__(state):
+ """Set the object state data
+
+ Note that this does not affect the object's persistent state.
+ """
+
+ def _p_activate():
+ """Activate the object
+
+ Change the object to the up-to-date state if it is a ghost.
+ """
+
+ def _p_deactivate():
+ """Deactivate the object
+
+ If possible, change an object in the up-to-date state to the
+ ghost state. It may not be possible to make some persistent
+ objects ghosts.
+ """
+
+class IPersistentNoReadConflicts(IPersistent):
+ def _p_independent():
+ """Hook for subclasses to prevent read conflict errors
+
+ A specific persistent object type can define this method and
+ have it return true if the data manager should ignore read
+ conflicts for this object.
+ """
+class IPersistentDataManager(Interface):
+ """Provide services for managing persistent state.
+
+ This interface is used by a persistent object to interact with its
+ data manager in the context of a transaction.
+ """
+
+ def setstate(object):
+ """Load the state for the given object.
+
+ The object should be in the deactivated (ghost) state.
+ The object's state will be set and the object will end up
+ in the up-to-date state.
+
+ The object must implement the IPersistent interface.
+ """
+
+ def register(object):
+ """Register a IPersistent with the current transaction.
+
+ This method provides some insulation of the persistent object
+ from details of transaction management. For example, it allows
+ the use of per-database-connection rather than per-thread
+ transaction managers.
+
+ A persistent object should not register with its data manager
+ more than once during a single transaction. XXX should is too
+ wishy-washy; we should probably guarantee that this is true,
+ and it might be.
+ """
+
+ def mtime(object):
+ """Return the modification time of the object.
+
+ The modification time may not be known, in which case None
+ is returned.
+ """
+
+class ICache(Interface):
+ """In-memory object cache
+
+ The cache serves two purposes. It peforms pointer swizzling, and
+ it keeps a bounded set of recently used but otherwise unreferenced
+ in objects to avoid the cost of re-loading them.
+
+ Pointer swizzling is the process of converting between persistent
+ object ids and Python object ids. When a persistent object is
+ serialized, its references to other persistent objects are
+ represented as persitent object ids (oids). When the object is
+ unserialized, the oids are converted into references to Python
+ objects. If several different serialized objects refer to the
+ same object, they must all refer to the same object when they are
+ unserialized.
+
+ A cache stores persistent objects, but it treats ghost objects and
+ non-ghost or active objects differently. It has weak references
+ to ghost objects, because ghost objects are only stored in the
+ cache to satisfy the pointer swizzling requirement. It has strong
+ references to active objects, because it caches some number of
+ them even if they are unreferenced.
+
+ The cache keeps some number of recently used but otherwise
+ unreferenced objects in memory. We assume that there is a good
+ chance the object will be used again soon, so keeping it memory
+ avoids the cost of recreating the object.
+
+ An ICache implementation is intended for use by an
+ IPersistentDataManager.
+ """
+
+ def get(oid):
+ """Return the object from the cache or None."""
+
+ def set(oid, obj):
+ """Store obj in the cache under oid.
+
+ obj must implement IPersistent
+ """
+
+ def remove(oid):
+ """Remove oid from the cache if it exists."""
+
+ def invalidate(oids):
+ """Make all of the objects in oids ghosts.
+
+ `oids` is an iterable object that yields oids.
+
+ The cache must attempt to change each object to a ghost by
+ calling _p_deactivate().
+
+ If an oid is not in the cache, ignore it.
+ """
+
+ def clear():
+ """Invalidate all the active objects."""
+
+ def activate(oid):
+ """Notification that object oid is now active.
+
+ The caller is notifying the cache of a state change.
+
+ Raises LookupError if oid is not in cache.
+ """
+
+ def shrink():
+ """Remove excess active objects from the cache."""
+
+ def statistics():
+ """Return dictionary of statistics about cache size.
+
+ Contains at least the following keys:
+ active -- number of active objects
+ ghosts -- number of ghost objects
+ """
=== Zope/lib/python/persistent/wref.py 1.1 => 1.2 ===
--- /dev/null Wed Feb 18 22:00:07 2004
+++ Zope/lib/python/persistent/wref.py Wed Feb 18 21:59:30 2004
@@ -0,0 +1,302 @@
+##############################################################################
+#
+# Copyright (c) 2003 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.
+#
+##############################################################################
+"""ZODB-based persistent weakrefs
+
+$Id$
+"""
+
+from persistent import Persistent
+
+WeakRefMarker = object()
+
+class WeakRef(object):
+ """Persistent weak references
+
+ Persistent weak references are used much like Python weak
+ references. The major difference is that you can't specify an
+ object to be called when the object is removed from the database.
+
+ Here's an example. We'll start by creating a persistent object and
+ a refernce to it:
+
+ >>> import persistent.list
+ >>> import ZODB.tests.util
+ >>> ob = persistent.list.PersistentList()
+ >>> ref = WeakRef(ob)
+ >>> ref() is ob
+ True
+
+ The hash of the ref if the same as the hash of the referenced object:
+
+ >>> hash(ref) == hash(ob)
+ True
+
+ Two refs to the same object are equal:
+
+ >>> WeakRef(ob) == ref
+ True
+
+ >>> ob2 = persistent.list.PersistentList([1])
+ >>> WeakRef(ob2) == ref
+ False
+
+ Lets save the reference and the referenced object in a database:
+
+ >>> db = ZODB.tests.util.DB()
+
+ >>> conn1 = db.open()
+ >>> conn1.root()['ob'] = ob
+ >>> conn1.root()['ref'] = ref
+ >>> ZODB.tests.util.commit()
+
+ If we open a new connection, we can use the reference:
+
+ >>> conn2 = db.open()
+ >>> conn2.root()['ref']() is conn2.root()['ob']
+ True
+ >>> hash(conn2.root()['ref']) == hash(conn2.root()['ob'])
+ True
+
+ But if we delete the referenced object and pack:
+
+ >>> del conn2.root()['ob']
+ >>> ZODB.tests.util.commit()
+ >>> ZODB.tests.util.pack(db)
+
+ And then look in a new connection:
+
+ >>> conn3 = db.open()
+ >>> conn3.root()['ob']
+ Traceback (most recent call last):
+ ...
+ KeyError: 'ob'
+
+ Trying to dereference the reference returns None:
+
+ >>> conn3.root()['ref']()
+
+ Trying to get a hash, raises a type error:
+
+ >>> hash(conn3.root()['ref'])
+ Traceback (most recent call last):
+ ...
+ TypeError: Weakly-referenced object has gone away
+
+ Always explicitly close databases: :)
+
+ >>> db.close()
+
+ """
+
+ # We set _p_oid to a marker so that the serialization system can
+ # provide special handling of weakrefs.
+ _p_oid = WeakRefMarker
+
+ def __init__(self, ob):
+ self._v_ob = ob
+ self.oid = ob._p_oid
+ self.dm = ob._p_jar
+
+ def __call__(self):
+ try:
+ return self._v_ob
+ except AttributeError:
+ try:
+ self._v_ob = self.dm[self.oid]
+ except KeyError:
+ return None
+ return self._v_ob
+
+ def __hash__(self):
+ self = self()
+ if self is None:
+ raise TypeError('Weakly-referenced object has gone away')
+ return hash(self)
+
+ def __eq__(self, other):
+ self = self()
+ if self is None:
+ raise TypeError('Weakly-referenced object has gone away')
+ other = other()
+ if other is None:
+ raise TypeError('Weakly-referenced object has gone away')
+
+ return self == other
+
+
+class PersistentWeakKeyDictionary(Persistent):
+ """Persistent weak key dictionary
+
+ This is akin to WeakKeyDictionaries. Note, however, that removal
+ of items is extremely lazy. See below.
+
+ We'll start by creating a PersistentWeakKeyDictionary and adding
+ some persistent objects to it.
+
+ >>> d = PersistentWeakKeyDictionary()
+ >>> import ZODB.tests.util
+ >>> p1 = ZODB.tests.util.P('p1')
+ >>> p2 = ZODB.tests.util.P('p2')
+ >>> p3 = ZODB.tests.util.P('p3')
+ >>> d[p1] = 1
+ >>> d[p2] = 2
+ >>> d[p3] = 3
+
+ We'll create an extra persistent object that's not in the dict:
+
+ >>> p4 = ZODB.tests.util.P('p4')
+
+ Now we'll excercise iteration and item access:
+
+ >>> l = [(str(k), d[k], d.get(k)) for k in d]
+ >>> l.sort()
+ >>> l
+ [('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)]
+
+ And the containment operator:
+
+ >>> [p in d for p in [p1, p2, p3, p4]]
+ [True, True, True, False]
+
+ We can add the dict and the referenced objects to a database:
+
+ >>> db = ZODB.tests.util.DB()
+
+ >>> conn1 = db.open()
+ >>> conn1.root()['p1'] = p1
+ >>> conn1.root()['d'] = d
+ >>> conn1.root()['p2'] = p2
+ >>> conn1.root()['p3'] = p3
+ >>> ZODB.tests.util.commit()
+
+ And things still work, as before:
+
+ >>> l = [(str(k), d[k], d.get(k)) for k in d]
+ >>> l.sort()
+ >>> l
+ [('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)]
+ >>> [p in d for p in [p1, p2, p3, p4]]
+ [True, True, True, False]
+
+ Likewise, we can read the objects from another connection and
+ things still work.
+
+ >>> conn2 = db.open()
+ >>> d = conn2.root()['d']
+ >>> p1 = conn2.root()['p1']
+ >>> p2 = conn2.root()['p2']
+ >>> p3 = conn2.root()['p3']
+ >>> l = [(str(k), d[k], d.get(k)) for k in d]
+ >>> l.sort()
+ >>> l
+ [('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)]
+ >>> [p in d for p in [p1, p2, p3, p4]]
+ [True, True, True, False]
+
+ Now, we'll delete one of the objects from the database, but *not*
+ from the dictionary:
+
+ >>> del conn2.root()['p2']
+ >>> ZODB.tests.util.commit()
+
+ And pack the database, so that the no-longer referenced p2 is
+ actually removed from the database.
+
+ >>> ZODB.tests.util.pack(db)
+
+ Now if we access the dictionary in a new connection, it no longer
+ has p2:
+
+ >>> conn3 = db.open()
+ >>> d = conn3.root()['d']
+ >>> l = [(str(k), d[k], d.get(k)) for k in d]
+ >>> l.sort()
+ >>> l
+ [('P(p1)', 1, 1), ('P(p3)', 3, 3)]
+
+ It's worth nothing that that the versions of the dictionary in
+ conn1 and conn2 still have p2, because p2 is still in the caches
+ for those connections.
+
+ Always explicitly close databases: :)
+
+ >>> db.close()
+
+ """
+ # XXX it is expensive trying to load dead objects from the database.
+ # It would be helpful if the data manager/connection cached these.
+
+
+ def __init__(self, adict=None, **kwargs):
+ self.data = {}
+ if adict is not None:
+ keys = getattr(adict, "keys", None)
+ if keys is None:
+ adict = dict(adict)
+ self.update(adict)
+ if kwargs:
+ self.update(kwargs)
+
+ def __getstate__(self):
+ state = Persistent.__getstate__(self)
+ state['data'] = state['data'].items()
+ return state
+
+ def __setstate__(self, state):
+ state['data'] = dict([
+ (k, v) for (k, v) in state['data']
+ if k() is not None
+ ])
+ Persistent.__setstate__(self, state)
+
+ def __setitem__(self, key, value):
+ self.data[WeakRef(key)] = value
+
+ def __getitem__(self, key):
+ return self.data[WeakRef(key)]
+
+ def __delitem__(self, key):
+ del self.data[WeakRef(key)]
+
+ def get(self, key, default=None):
+ """D.get(k[, d]) -> D[k] if k in D, else d.
+
+ >>> import ZODB.tests.util
+ >>> key = ZODB.tests.util.P("key")
+ >>> missing = ZODB.tests.util.P("missing")
+ >>> d = PersistentWeakKeyDictionary([(key, 1)])
+ >>> d.get(key)
+ 1
+ >>> d.get(missing)
+ >>> d.get(missing, 12)
+ 12
+ """
+ return self.data.get(WeakRef(key), default)
+
+ def __contains__(self, key):
+ return WeakRef(key) in self.data
+
+ def __iter__(self):
+ for k in self.data:
+ yield k()
+
+ def update(self, adict):
+ if isinstance(adict, PersistentWeakKeyDictionary):
+ self.data.update(adict.update)
+ else:
+ for k, v in adict.items():
+ self.data[WeakRef(k)] = v
+
+ # XXX Someone else can fill out the rest of the methods, with tests. :)
+
=== Zope/lib/python/persistent/__init__.py 1.6 => 1.7 ===
--- Zope/lib/python/persistent/__init__.py:1.6 Fri Nov 28 11:44:55 2003
+++ Zope/lib/python/persistent/__init__.py Wed Feb 18 21:59:30 2004
@@ -13,5 +13,19 @@
##############################################################################
"""Provide access to Persistent and PersistentMapping."""
-from cPersistence import Persistent
+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
+except ImportError:
+ pass
+else:
+ from persistent.interfaces import IPersistent
+ classImplements(Persistent, IPersistent)
=== Zope/lib/python/persistent/cPersistence.c 1.75 => 1.76 ===
--- Zope/lib/python/persistent/cPersistence.c:1.75 Thu Jan 8 11:53:15 2004
+++ Zope/lib/python/persistent/cPersistence.c Wed Feb 18 21:59:30 2004
@@ -23,9 +23,8 @@
CACHE_HEAD
};
-#define ASSIGN(V,E) {PyObject *__e; __e=(E); Py_XDECREF(V); (V)=__e;}
-#define UNLESS(E) if(!(E))
-#define UNLESS_ASSIGN(V,E) ASSIGN(V,E) UNLESS(V)
+/* These two objects are initialized when the module is loaded */
+static PyObject *TimeStamp, *py_simple_new;
/* Strings initialized by init_strings() below. */
static PyObject *py_keys, *py_setstate, *py___dict__, *py_timeTime;
@@ -33,9 +32,6 @@
static PyObject *py___getattr__, *py___setattr__, *py___delattr__;
static PyObject *py___getstate__;
-/* These two objects are initialized when the module is loaded */
-static PyObject *TimeStamp, *py_simple_new;
-
static int
init_strings(void)
{
@@ -59,7 +55,7 @@
static void ghostify(cPersistentObject*);
/* Load the state of the object, unghostifying it. Upon success, return 1.
- * If an error occurred, re-ghostify the object and return 0.
+ * If an error occurred, re-ghostify the object and return -1.
*/
static int
unghostify(cPersistentObject *self)
@@ -82,7 +78,7 @@
r = PyObject_CallMethod(self->jar, "setstate", "O", (PyObject *)self);
if (r == NULL) {
ghostify(self);
- return 0;
+ return -1;
}
self->state = cPersistent_UPTODATE_STATE;
Py_DECREF(r);
@@ -209,6 +205,33 @@
return Py_None;
}
+static PyObject *
+Per__p_activate(cPersistentObject *self)
+{
+ if (unghostify(self) < 0)
+ return NULL;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static int Per_set_changed(cPersistentObject *self, PyObject *v);
+
+static PyObject *
+Per__p_invalidate(cPersistentObject *self)
+{
+ signed char old_state = self->state;
+
+ if (old_state != cPersistent_GHOST_STATE) {
+ if (Per_set_changed(self, NULL) < 0)
+ return NULL;
+ ghostify(self);
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+
#include "pickle/pickle.c"
@@ -230,27 +253,13 @@
Per__getstate__(cPersistentObject *self)
{
/* XXX Should it be an error to call __getstate__() on a ghost? */
- if (!unghostify(self))
+ if (unghostify(self) < 0)
return NULL;
/* XXX shouldn't we increment stickyness? */
return pickle___getstate__((PyObject*)self);
}
-
-static struct PyMethodDef Per_methods[] = {
- {"_p_deactivate", (PyCFunction)Per__p_deactivate, METH_NOARGS,
- "_p_deactivate() -- Deactivate the object"},
- {"__getstate__", (PyCFunction)Per__getstate__, METH_NOARGS,
- pickle___getstate__doc },
-
- PICKLE_SETSTATE_DEF
- PICKLE_GETNEWARGS_DEF
- PICKLE_REDUCE_DEF
-
- {NULL, NULL} /* sentinel */
-};
-
/* The Persistent base type provides a traverse function, but not a
clear function. An instance of a Persistent subclass will have
its dict cleared through subtype_clear().
@@ -378,7 +387,7 @@
s = PyString_AS_STRING(name);
if (*s != '_' || unghost_getattr(s)) {
- if (!unghostify(self))
+ if (unghostify(self) < 0)
goto Done;
accessed(self);
}
@@ -389,23 +398,37 @@
return result;
}
-/* We need to decide on a reasonable way for a programmer to write
- an __setattr__() or __delattr__() hook.
+/* Exposed as _p_getattr method. Test whether base getattr should be used */
+static PyObject *
+Per__p_getattr(cPersistentObject *self, PyObject *name)
+{
+ PyObject *result = NULL; /* guilty until proved innocent */
+ char *s;
- The ZODB3 has been that if you write a hook, it will be called if
- the attribute is not an _p_ attribute and after doing any necessary
- unghostifying. AMK's guide says modification will not be tracked
- automatically, so the hook must explicitly set _p_changed; I'm not
- sure if I believe that.
-
- This approach won't work with new-style classes, because type will
- install a slot wrapper that calls the derived class's __setattr__().
- That means Persistent's tp_setattro doesn't get a chance to be called.
- Changing this behavior would require a metaclass.
-
- One option for ZODB 3.3 is to require setattr hooks to know about
- _p_ and to call a prep function before modifying the object's state.
- That's the solution I like best at the moment.
+ name = convert_name(name);
+ if (!name)
+ goto Done;
+ s = PyString_AS_STRING(name);
+
+ if (*s != '_' || unghost_getattr(s))
+ {
+ if (unghostify(self) < 0)
+ goto Done;
+ accessed(self);
+ result = Py_False;
+ }
+ else
+ result = Py_True;
+
+ Py_INCREF(result);
+
+ Done:
+ Py_XDECREF(name);
+ return result;
+}
+
+/*
+ XXX we should probably not allow assignment of __class__ and __dict__.
*/
static int
@@ -420,7 +443,7 @@
s = PyString_AS_STRING(name);
if (strncmp(s, "_p_", 3) != 0) {
- if (!unghostify(self))
+ if (unghostify(self) < 0)
goto Done;
accessed(self);
if (strncmp(s, "_v_", 3) != 0
@@ -436,6 +459,72 @@
return result;
}
+
+static int
+Per_p_set_or_delattro(cPersistentObject *self, PyObject *name, PyObject *v)
+{
+ int result = -1; /* guilty until proved innocent */
+ char *s;
+
+ name = convert_name(name);
+ if (!name)
+ goto Done;
+ s = PyString_AS_STRING(name);
+
+ if (strncmp(s, "_p_", 3) != 0)
+ {
+ if (unghostify(self) < 0)
+ goto Done;
+ accessed(self);
+
+ result = 0;
+ }
+ else
+ {
+ if (PyObject_GenericSetAttr((PyObject *)self, name, v) < 0)
+ goto Done;
+ result = 1;
+ }
+
+ Done:
+ Py_XDECREF(name);
+ return result;
+}
+
+static PyObject *
+Per__p_setattr(cPersistentObject *self, PyObject *args)
+{
+ PyObject *name, *v, *result;
+ int r;
+
+ if (! PyArg_ParseTuple(args, "OO:_p_setattr", &name, &v))
+ return NULL;
+
+ r = Per_p_set_or_delattro(self, name, v);
+ if (r < 0)
+ return NULL;
+
+ result = r ? Py_True : Py_False;
+ Py_INCREF(result);
+ return result;
+}
+
+static PyObject *
+Per__p_delattr(cPersistentObject *self, PyObject *name)
+{
+ int r;
+ PyObject *result;
+
+ r = Per_p_set_or_delattro(self, name, NULL);
+ if (r < 0)
+ return NULL;
+
+ result = r ? Py_True : Py_False;
+ Py_INCREF(result);
+ return result;
+}
+
+
static PyObject *
Per_get_changed(cPersistentObject *self)
{
@@ -589,7 +678,7 @@
{
PyObject *t, *v;
- if (!unghostify(self))
+ if (unghostify(self) < 0)
return NULL;
accessed(self);
@@ -607,15 +696,70 @@
return v;
}
+static PyObject *
+Per_get_state(cPersistentObject *self)
+{
+ return PyInt_FromLong(self->state);
+}
+
static PyGetSetDef Per_getsets[] = {
{"_p_changed", (getter)Per_get_changed, (setter)Per_set_changed},
{"_p_jar", (getter)Per_get_jar, (setter)Per_set_jar},
{"_p_mtime", (getter)Per_get_mtime},
{"_p_oid", (getter)Per_get_oid, (setter)Per_set_oid},
{"_p_serial", (getter)Per_get_serial, (setter)Per_set_serial},
+ {"_p_state", (getter)Per_get_state},
{NULL}
};
+static struct PyMethodDef Per_methods[] = {
+ {"_p_deactivate", (PyCFunction)Per__p_deactivate, METH_NOARGS,
+ "_p_deactivate() -- Deactivate the object"},
+ {"_p_activate", (PyCFunction)Per__p_activate, METH_NOARGS,
+ "_p_activate() -- Activate the object"},
+ {"_p_invalidate", (PyCFunction)Per__p_invalidate, METH_NOARGS,
+ "_p_invalidate() -- Invalidate the object"},
+ {"_p_getattr", (PyCFunction)Per__p_getattr, METH_O,
+ "_p_getattr(name) -- Test whether the base class must handle the name\n"
+ "\n"
+ "The method unghostifies the object, if necessary.\n"
+ "The method records the object access, if necessary.\n"
+ "\n"
+ "This method should be called by subclass __getattribute__\n"
+ "implementations before doing anything else. If the method\n"
+ "returns True, then __getattribute__ implementations must delegate\n"
+ "to the base class, Persistent.\n"
+ },
+ {"_p_setattr", (PyCFunction)Per__p_setattr, METH_VARARGS,
+ "_p_setattr(name, value) -- Save persistent meta data\n"
+ "\n"
+ "This method should be called by subclass __setattr__ implementations\n"
+ "before doing anything else. If it returns true, then the attribute\n"
+ "was handled by the base class.\n"
+ "\n"
+ "The method unghostifies the object, if necessary.\n"
+ "The method records the object access, if necessary.\n"
+ },
+ {"_p_delattr", (PyCFunction)Per__p_delattr, METH_O,
+ "_p_delattr(name) -- Delete persistent meta data\n"
+ "\n"
+ "This method should be called by subclass __delattr__ implementations\n"
+ "before doing anything else. If it returns true, then the attribute\n"
+ "was handled by the base class.\n"
+ "\n"
+ "The method unghostifies the object, if necessary.\n"
+ "The method records the object access, if necessary.\n"
+ },
+ {"__getstate__", (PyCFunction)Per__getstate__, METH_NOARGS,
+ pickle___getstate__doc },
+
+ PICKLE_SETSTATE_DEF
+ PICKLE_GETNEWARGS_DEF
+ PICKLE_REDUCE_DEF
+
+ {NULL, NULL} /* sentinel */
+};
+
/* This module is compiled as a shared library. Some compilers don't
allow addresses of Python objects defined in other libraries to be
used in static initializers here. The DEFERRED_ADDRESS macro is
@@ -669,7 +813,7 @@
static int
Per_setstate(cPersistentObject *self)
{
- if (!unghostify(self))
+ if (unghostify(self) < 0)
return -1;
self->state = cPersistent_STICKY_STATE;
return 0;
@@ -731,15 +875,28 @@
if (PyModule_AddObject(m, "CAPI", s) < 0)
return;
+ if (PyModule_AddIntConstant(m, "GHOST", cPersistent_GHOST_STATE) < 0)
+ return;
+
+ if (PyModule_AddIntConstant(m, "UPTODATE", cPersistent_UPTODATE_STATE) < 0)
+ return;
+
+ if (PyModule_AddIntConstant(m, "CHANGED", cPersistent_CHANGED_STATE) < 0)
+ return;
+
+ if (PyModule_AddIntConstant(m, "STICKY", cPersistent_STICKY_STATE) < 0)
+ return;
+
py_simple_new = PyObject_GetAttrString(m, "simple_new");
if (!py_simple_new)
return;
- m = PyImport_ImportModule("persistent.TimeStamp");
- if (!m)
- return;
- TimeStamp = PyObject_GetAttrString(m, "TimeStamp");
- if (!TimeStamp)
- return;
- Py_DECREF(m);
+ if (TimeStamp == NULL) {
+ m = PyImport_ImportModule("persistent.TimeStamp");
+ if (!m)
+ return;
+ TimeStamp = PyObject_GetAttrString(m, "TimeStamp");
+ Py_DECREF(m);
+ /* fall through to immediate return on error */
+ }
}
=== Zope/lib/python/persistent/cPersistence.h 1.28 => 1.29 ===
--- Zope/lib/python/persistent/cPersistence.h:1.28 Fri Feb 13 23:28:13 2004
+++ Zope/lib/python/persistent/cPersistence.h Wed Feb 18 21:59:30 2004
@@ -81,6 +81,8 @@
percachedelfunc percachedel;
} cPersistenceCAPIstruct;
+#define cPersistenceType cPersistenceCAPI->pertype
+
#ifndef DONT_USE_CPERSISTENCECAPI
static cPersistenceCAPIstruct *cPersistenceCAPI;
#endif
@@ -95,10 +97,26 @@
#define PER_GHOSTIFY(O) (cPersistenceCAPI->ghostify((cPersistentObject*)(O)))
+/* If the object is sticky, make it non-sticky, so that it can be ghostified.
+ The value is not meaningful
+ */
#define PER_ALLOW_DEACTIVATION(O) ((O)->state==cPersistent_STICKY_STATE && ((O)->state=cPersistent_UPTODATE_STATE))
#define PER_PREVENT_DEACTIVATION(O) ((O)->state==cPersistent_UPTODATE_STATE && ((O)->state=cPersistent_STICKY_STATE))
+/*
+ Make a persistent object usable from C by:
+
+ - Making sure it is not a ghost
+
+ - Making it sticky.
+
+ IMPORTANT: If you call this and don't call PER_ALLOW_DEACTIVATION,
+ your object will not be ghostified.
+
+ PER_USE returns a 1 on success and 0 failure, where failure means
+ error.
+ */
#define PER_USE(O) \
(((O)->state != cPersistent_GHOST_STATE \
|| (cPersistenceCAPI->setstate((PyObject*)(O)) >= 0)) \
=== Zope/lib/python/persistent/cPickleCache.c 1.87 => 1.88 ===
--- Zope/lib/python/persistent/cPickleCache.c:1.87 Fri Nov 28 11:44:55 2003
+++ Zope/lib/python/persistent/cPickleCache.c Wed Feb 18 21:59:30 2004
@@ -107,11 +107,11 @@
ccobject_head in cPersistence.c */
typedef struct {
CACHE_HEAD
- int klass_count; /* count of persistent classes */
- PyObject *data; /* oid -> object dict */
- PyObject *jar; /* Connection object */
- PyObject *setklassstate; /* ??? */
- int cache_size; /* target number of items in cache */
+ int klass_count; /* count of persistent classes */
+ PyObject *data; /* oid -> object dict */
+ PyObject *jar; /* Connection object */
+ PyObject *setklassstate; /* ??? */
+ int cache_size; /* target number of items in cache */
/* Most of the time the ring contains only:
* many nodes corresponding to persistent objects
@@ -138,7 +138,7 @@
/* ---------------------------------------------------------------- */
-#define OBJECT_FROM_RING(SELF, HERE, CTX) \
+#define OBJECT_FROM_RING(SELF, HERE) \
((cPersistentObject *)(((char *)here) - offsetof(cPersistentObject, ring)))
static int
@@ -167,7 +167,7 @@
this because the ring lock is held. We can safely assume
the current ring node is a persistent object now we know it
is not the home */
- object = OBJECT_FROM_RING(self, here, "scan_gc_items");
+ object = OBJECT_FROM_RING(self, here);
if (!object)
return -1;
@@ -223,7 +223,7 @@
}
self->ring_lock = 1;
- if (scan_gc_items(self, target_size)) {
+ if (scan_gc_items(self, target_size) < 0) {
self->ring_lock = 0;
return NULL;
}
@@ -236,7 +236,7 @@
static PyObject *
cc_incrgc(ccobject *self, PyObject *args)
{
- int n = 1;
+ int obsolete_arg = -999;
int starting_size = self->non_ghost_count;
int target_size = self->cache_size;
@@ -250,19 +250,30 @@
target_size = target_size_2;
}
- if (!PyArg_ParseTuple(args, "|i:incrgc", &n))
+
+ if (!PyArg_ParseTuple(args, "|i:incrgc", &obsolete_arg))
return NULL;
+ if (obsolete_arg != -999
+ &&
+ (PyErr_Warn(PyExc_DeprecationWarning,
+ "No argument expected")
+ < 0))
+ return NULL;
+
return lockgc(self, target_size);
}
static PyObject *
cc_full_sweep(ccobject *self, PyObject *args)
{
- int dt = 0;
+ int dt = -999;
+
+ /* XXX This should be deprecated */
+
if (!PyArg_ParseTuple(args, "|i:full_sweep", &dt))
return NULL;
- if (dt == 0)
+ if (dt == -999)
return lockgc(self, 0);
else
return cc_incrgc(self, args);
@@ -271,9 +282,18 @@
static PyObject *
cc_minimize(ccobject *self, PyObject *args)
{
- int ignored;
+ int ignored = -999;
+
if (!PyArg_ParseTuple(args, "|i:minimize", &ignored))
return NULL;
+
+ if (ignored != -999
+ &&
+ (PyErr_Warn(PyExc_DeprecationWarning,
+ "No argument expected")
+ < 0))
+ return NULL;
+
return lockgc(self, 0);
}
@@ -285,6 +305,13 @@
if (!v)
return;
if (PyType_Check(v)) {
+ /* This looks wrong, but it isn't. We use strong references to types
+ because they don't have the ring members.
+
+ XXX the result is that we *never* remove classes unless
+ they are modified.
+
+ */
if (v->ob_refcnt <= 1) {
self->klass_count--;
if (PyDict_DelItem(self->data, key) < 0)
@@ -372,7 +399,7 @@
PyObject *l,*k,*v;
int p = 0;
- l = PyList_New(PyDict_Size(self->data));
+ l = PyList_New(0);
if (l == NULL)
return NULL;
@@ -396,6 +423,45 @@
}
static PyObject *
+cc_debug_info(ccobject *self)
+{
+ PyObject *l,*k,*v;
+ int p = 0;
+
+ l = PyList_New(0);
+ if (l == NULL)
+ return NULL;
+
+ while (PyDict_Next(self->data, &p, &k, &v))
+ {
+ if (v->ob_refcnt <= 0)
+ v = Py_BuildValue("Oi", k, v->ob_refcnt);
+
+ else if (! PyType_Check(v) &&
+ (v->ob_type->tp_basicsize >= sizeof(cPersistentObject))
+ )
+ v = Py_BuildValue("Oisi",
+ k, v->ob_refcnt, v->ob_type->tp_name,
+ ((cPersistentObject*)v)->state);
+ else
+ v = Py_BuildValue("Ois", k, v->ob_refcnt, v->ob_type->tp_name);
+
+ if (v == NULL)
+ goto err;
+
+ if (PyList_Append(l, v) < 0)
+ goto err;
+ }
+
+ return l;
+
+ err:
+ Py_DECREF(l);
+ return NULL;
+
+}
+
+static PyObject *
cc_lru_items(ccobject *self)
{
PyObject *l;
@@ -417,7 +483,7 @@
here = self->ring_home.r_next;
while (here != &self->ring_home) {
PyObject *v;
- cPersistentObject *object = OBJECT_FROM_RING(self, here, "cc_items");
+ cPersistentObject *object = OBJECT_FROM_RING(self, here);
if (object == NULL) {
Py_DECREF(l);
@@ -520,15 +586,14 @@
{"klass_items", (PyCFunction)cc_klass_items, METH_NOARGS,
"List (oid, object) pairs of cached persistent classes."},
{"full_sweep", (PyCFunction)cc_full_sweep, METH_VARARGS,
- "full_sweep([age]) -- Perform a full sweep of the cache\n\n"
- "Supported for backwards compatibility. If the age argument is 0,\n"
- "behaves like minimize(). Otherwise, behaves like incrgc()."},
+ "full_sweep() -- Perform a full sweep of the cache."},
{"minimize", (PyCFunction)cc_minimize, METH_VARARGS,
"minimize([ignored]) -- Remove as many objects as possible\n\n"
"Ghostify all objects that are not modified. Takes an optional\n"
"argument, but ignores it."},
{"incrgc", (PyCFunction)cc_incrgc, METH_VARARGS,
- "incrgc([n]) -- Perform incremental garbage collection\n\n"
+ "incrgc() -- Perform incremental garbage collection\n\n"
+ "This method had been depricated!"
"Some other implementations support an optional parameter 'n' which\n"
"indicates a repetition count; this value is ignored."},
{"invalidate", (PyCFunction)cc_invalidate, METH_O,
@@ -537,6 +602,8 @@
"get(key [, default]) -- get an item, or a default"},
{"ringlen", (PyCFunction)cc_ringlen, METH_NOARGS,
"ringlen() -- Returns number of non-ghost items in cache."},
+ {"debug_info", (PyCFunction)cc_debug_info, METH_NOARGS,
+ "debug_info() -- Returns debugging data about objects in the cache."},
{NULL, NULL} /* sentinel */
};
@@ -618,7 +685,7 @@
while (self->ring_home.r_next != &self->ring_home) {
CPersistentRing *here = self->ring_home.r_next;
- cPersistentObject *o = OBJECT_FROM_RING(self, here, "cc_clear");
+ cPersistentObject *o = OBJECT_FROM_RING(self, here);
if (o->cache) {
Py_INCREF(o); /* account for uncounted reference */
@@ -685,7 +752,7 @@
return 0;
while (here != &self->ring_home) {
- cPersistentObject *o = OBJECT_FROM_RING(self, here, "foo");
+ cPersistentObject *o = OBJECT_FROM_RING(self, here);
VISIT(o);
here = here->r_next;
}
@@ -722,6 +789,7 @@
PyObject *oid, *object_again, *jar;
cPersistentObject *p;
+ /* Sanity check the value given to make sure it is allowed in the cache */
if (PyType_Check(v)) {
/* Its a persistent class, such as a ZClass. Thats ok. */
}
@@ -743,12 +811,13 @@
oid = PyObject_GetAttr(v, py__p_oid);
if (oid == NULL)
return -1;
- if (!PyString_Check(oid)) {
+ if (! PyString_Check(oid)) {
PyErr_Format(PyExc_TypeError,
"Cached object oid must be a string, not a %s",
oid->ob_type->tp_name);
return -1;
}
+
/* we know they are both strings.
* now check if they are the same string.
*/
@@ -836,8 +905,10 @@
/* unlink this item from the ring */
v = PyDict_GetItem(self->data, key);
- if (v == NULL)
+ if (v == NULL) {
+ PyErr_SetObject(PyExc_KeyError, key);
return -1;
+ }
if (PyType_Check(v)) {
self->klass_count--;
=== Zope/lib/python/persistent/list.py 1.5 => 1.6 ===
--- Zope/lib/python/persistent/list.py:1.5 Fri Nov 28 11:44:55 2003
+++ Zope/lib/python/persistent/list.py Wed Feb 18 21:59:30 2004
@@ -53,12 +53,14 @@
self._p_changed = 1
def __iadd__(self, other):
- self.__super_iadd(other)
+ L = self.__super_iadd(other)
self._p_changed = 1
+ return L
def __imul__(self, n):
- self.__super_imul(n)
+ L = self.__super_imul(n)
self._p_changed = 1
+ return L
def append(self, item):
self.__super_append(item)
More information about the Zodb-checkins
mailing list