[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