Commit 3eaea15c authored by Jeremy Hylton's avatar Jeremy Hylton

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.
parent 8356d18e
===================
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.
......@@ -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)
......@@ -14,7 +14,7 @@
static char cPersistence_doc_string[] =
"Defines Persistent mixin class for persistent objects.\n"
"\n"
"$Id: cPersistence.c,v 1.75 2004/01/08 16:53:15 tim_one Exp $\n";
"$Id: cPersistence.c,v 1.76 2004/02/19 02:59:30 jeremy Exp $\n";
#include "cPersistence.h"
#include "structmember.h"
......@@ -23,9 +23,8 @@ struct ccobject_head_struct {
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__p_changed, *py__p_deactivate;
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 @@ init_strings(void)
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 @@ unghostify(cPersistentObject *self)
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 @@ Per__p_deactivate(cPersistentObject *self)
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 @@ static PyObject *
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 @@ Per_getattro(cPersistentObject *self, PyObject *name)
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 @@ Per_getattro(cPersistentObject *self, PyObject *name)
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.
name = convert_name(name);
if (!name)
goto Done;
s = PyString_AS_STRING(name);
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.
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;
}
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.
/*
XXX we should probably not allow assignment of __class__ and __dict__.
*/
static int
......@@ -420,7 +443,7 @@ Per_setattro(cPersistentObject *self, PyObject *name, PyObject *v)
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 @@ Per_setattro(cPersistentObject *self, PyObject *name, PyObject *v)
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 @@ Per_get_mtime(cPersistentObject *self)
{
PyObject *t, *v;
if (!unghostify(self))
if (unghostify(self) < 0)
return NULL;
accessed(self);
......@@ -607,15 +696,70 @@ Per_get_mtime(cPersistentObject *self)
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 @@ typedef int (*intfunctionwithpythonarg)(PyObject*);
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 @@ initcPersistence(void)
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 */
}
}
......@@ -81,6 +81,8 @@ typedef struct {
percachedelfunc percachedel;
} cPersistenceCAPIstruct;
#define cPersistenceType cPersistenceCAPI->pertype
#ifndef DONT_USE_CPERSISTENCECAPI
static cPersistenceCAPIstruct *cPersistenceCAPI;
#endif
......@@ -95,10 +97,26 @@ static cPersistenceCAPIstruct *cPersistenceCAPI;
#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)) \
......
......@@ -90,7 +90,7 @@ process must skip such objects, rather than deactivating them.
static char cPickleCache_doc_string[] =
"Defines the PickleCache used by ZODB Connection objects.\n"
"\n"
"$Id: cPickleCache.c,v 1.87 2003/11/28 16:44:55 jim Exp $\n";
"$Id: cPickleCache.c,v 1.88 2004/02/19 02:59:30 jeremy Exp $\n";
#define DONT_USE_CPERSISTENCECAPI
#include "cPersistence.h"
......@@ -107,11 +107,11 @@ static cPersistenceCAPIstruct *capi;
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 @@ static int cc_ass_sub(ccobject *self, PyObject *key, PyObject *v);
/* ---------------------------------------------------------------- */
#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 @@ scan_gc_items(ccobject *self,int target)
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 @@ lockgc(ccobject *self, int target_size)
}
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 @@ lockgc(ccobject *self, int target_size)
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 @@ cc_incrgc(ccobject *self, PyObject *args)
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 @@ cc_full_sweep(ccobject *self, PyObject *args)
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 @@ _invalidate(ccobject *self, PyObject *key)
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 @@ cc_klass_items(ccobject *self)
PyObject *l,*k,*v;
int p = 0;
l = PyList_New(PyDict_Size(self->data));
l = PyList_New(0);
if (l == NULL)
return NULL;
......@@ -395,6 +422,45 @@ cc_klass_items(ccobject *self)
return l;
}
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)
{
......@@ -417,7 +483,7 @@ cc_lru_items(ccobject *self)
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 @@ static struct PyMethodDef cc_methods[] = {
{"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 @@ static struct PyMethodDef cc_methods[] = {
"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 @@ cc_clear(ccobject *self)
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 @@ cc_traverse(ccobject *self, visitproc visit, void *arg)
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 @@ cc_add_item(ccobject *self, PyObject *key, PyObject *v)
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 @@ cc_add_item(ccobject *self, PyObject *key, PyObject *v)
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 @@ cc_del_item(ccobject *self, PyObject *key)
/* 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--;
......
##############################################################################
#
# 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: dict.py,v 1.2 2004/02/19 02:59:30 jeremy Exp $
"""
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()
##############################################################################
#
# 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
"""
......@@ -14,9 +14,9 @@
"""Python implementation of persistent list.
$Id: list.py,v 1.5 2003/11/28 16:44:55 jim Exp $"""
$Id: list.py,v 1.6 2004/02/19 02:59:30 jeremy Exp $"""
__version__='$Revision: 1.5 $'[11:-2]
__version__='$Revision: 1.6 $'[11:-2]
import persistent
from UserList import UserList
......@@ -53,12 +53,14 @@ class PersistentList(UserList, persistent.Persistent):
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)
......
......@@ -87,7 +87,7 @@ pickle_copy_dict(PyObject *state)
{
PyObject *copy, *key, *value;
char *ckey;
int pos = 0, nr;
int pos = 0;
copy = PyDict_New();
if (copy == NULL)
......@@ -96,11 +96,8 @@ pickle_copy_dict(PyObject *state)
if (state == NULL)
return copy;
while ((nr = PyDict_Next(state, &pos, &key, &value)))
while (PyDict_Next(state, &pos, &key, &value))
{
if (nr < 0)
goto err;
if (key && PyString_Check(key))
{
ckey = PyString_AS_STRING(key);
......@@ -111,9 +108,7 @@ pickle_copy_dict(PyObject *state)
continue;
}
if (key != NULL && value != NULL &&
(PyObject_SetItem(copy, key, value) < 0)
)
if (PyObject_SetItem(copy, key, value) < 0)
goto err;
}
......@@ -184,6 +179,7 @@ pickle___getstate__(PyObject *self)
continue;
}
/* XXX will this go through our getattr hook? */
value = PyObject_GetAttr(self, name);
if (value == NULL)
PyErr_Clear();
......@@ -191,7 +187,7 @@ pickle___getstate__(PyObject *self)
{
int err = PyDict_SetItem(slots, name, value);
Py_DECREF(value);
if (err)
if (err < 0)
goto end;
n++;
}
......@@ -222,9 +218,7 @@ pickle_setattrs_from_dict(PyObject *self, PyObject *dict)
while (PyDict_Next(dict, &pos, &key, &value))
{
if (key != NULL && value != NULL &&
(PyObject_SetAttr(self, key, value) < 0)
)
if (PyObject_SetAttr(self, key, value) < 0)
return -1;
}
return 0;
......
......@@ -15,7 +15,7 @@ import pickle
import time
import unittest
from persistent import Persistent
from persistent import Persistent, GHOST, UPTODATE, CHANGED, STICKY
from persistent.cPickleCache import PickleCache
from persistent.TimeStamp import TimeStamp
from ZODB.utils import p64
......@@ -111,7 +111,7 @@ class PersistenceTest(unittest.TestCase):
obj._p_jar = 12
self.assertRaises(ValueError, setoid)
def testChanged(self):
def testChangedAndState(self):
obj = P()
self.jar.add(obj)
......@@ -122,24 +122,29 @@ class PersistenceTest(unittest.TestCase):
obj.x = 1
self.assertEqual(obj._p_changed, 1)
self.assertEqual(obj._p_state, CHANGED)
self.assert_(obj in self.jar.registered)
obj._p_changed = 0
self.assertEqual(obj._p_changed, 0)
self.assertEqual(obj._p_state, UPTODATE)
self.jar.registered.clear()
obj._p_changed = 1
self.assertEqual(obj._p_changed, 1)
self.assertEqual(obj._p_state, CHANGED)
self.assert_(obj in self.jar.registered)
# setting obj._p_changed to None ghostifies if the
# object is in the up-to-date state, but not otherwise.
obj._p_changed = None
self.assertEqual(obj._p_changed, 1)
self.assertEqual(obj._p_state, CHANGED)
obj._p_changed = 0
# Now it's a ghost.
obj._p_changed = None
self.assertEqual(obj._p_changed, None)
self.assertEqual(obj._p_state, GHOST)
obj = P()
self.jar.add(obj)
......@@ -148,6 +153,34 @@ class PersistenceTest(unittest.TestCase):
# you delete the _p_changed attribute.
del obj._p_changed
self.assertEqual(obj._p_changed, None)
self.assertEqual(obj._p_state, GHOST)
def testStateReadonly(self):
# make sure we can't write to _p_state; we don't want yet
# another way to change state!
obj = P()
def setstate(value):
obj._p_state = value
self.assertRaises(TypeError, setstate, GHOST)
self.assertRaises(TypeError, setstate, UPTODATE)
self.assertRaises(TypeError, setstate, CHANGED)
self.assertRaises(TypeError, setstate, STICKY)
def testInvalidate(self):
obj = P()
self.jar.add(obj)
self.assertEqual(obj._p_changed, 0)
self.assertEqual(obj._p_state, UPTODATE)
obj._p_invalidate()
self.assertEqual(obj._p_changed, None)
self.assertEqual(obj._p_state, GHOST)
obj._p_activate()
obj.x = 1
obj._p_invalidate()
self.assertEqual(obj._p_changed, None)
self.assertEqual(obj._p_state, GHOST)
def testSerial(self):
noserial = "\000" * 8
......@@ -196,6 +229,7 @@ class PersistenceTest(unittest.TestCase):
# The getattr hook modified the object, so it should now be
# in the changed state.
self.assertEqual(obj._p_changed, 1)
self.assertEqual(obj._p_state, CHANGED)
self.assertEqual(obj.curly, 2)
self.assertEqual(obj.moe, 3)
......@@ -213,6 +247,7 @@ class PersistenceTest(unittest.TestCase):
# The getattr hook modified the object, so it should now be
# in the changed state.
self.assertEqual(obj._p_changed, 1)
self.assertEqual(obj._p_state, CHANGED)
self.assertEqual(obj.curly, 2)
self.assertEqual(obj.moe, 3)
......
##############################################################################
#
# 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.
#
##############################################################################
"""Unit tests for PickleCache
$Id: test_PickleCache.py,v 1.2 2004/02/19 02:59:32 jeremy Exp $
"""
class DummyConnection:
def setklassstate(self, obj):
"""Method used by PickleCache."""
def test_delitem():
"""
>>> from persistent import PickleCache
>>> conn = DummyConnection()
>>> cache = PickleCache(conn)
>>> del cache['']
Traceback (most recent call last):
...
KeyError: ''
>>> from persistent import Persistent
>>> p = Persistent()
>>> p._p_oid = 'foo'
>>> p._p_jar = conn
>>> cache['foo'] = p
>>> del cache['foo']
"""
from doctest import DocTestSuite
import unittest
def test_suite():
return unittest.TestSuite((
DocTestSuite(),
))
if __name__ == '__main__':
unittest.main()
##############################################################################
#
# 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.
#
##############################################################################
"""Test the list interface to PersistentList
"""
import unittest
from persistent.list import PersistentList
l0 = []
l1 = [0]
l2 = [0, 1]
class TestPList(unittest.TestCase):
def testTheWorld(self):
# Test constructors
u = PersistentList()
u0 = PersistentList(l0)
u1 = PersistentList(l1)
u2 = PersistentList(l2)
uu = PersistentList(u)
uu0 = PersistentList(u0)
uu1 = PersistentList(u1)
uu2 = PersistentList(u2)
v = PersistentList(tuple(u))
class OtherList:
def __init__(self, initlist):
self.__data = initlist
def __len__(self):
return len(self.__data)
def __getitem__(self, i):
return self.__data[i]
v0 = PersistentList(OtherList(u0))
vv = PersistentList("this is also a sequence")
# Test __repr__
eq = self.assertEqual
eq(str(u0), str(l0), "str(u0) == str(l0)")
eq(repr(u1), repr(l1), "repr(u1) == repr(l1)")
eq(`u2`, `l2`, "`u2` == `l2`")
# Test __cmp__ and __len__
def mycmp(a, b):
r = cmp(a, b)
if r < 0: return -1
if r > 0: return 1
return r
all = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2]
for a in all:
for b in all:
eq(mycmp(a, b), mycmp(len(a), len(b)),
"mycmp(a, b) == mycmp(len(a), len(b))")
# Test __getitem__
for i in range(len(u2)):
eq(u2[i], i, "u2[i] == i")
# Test __setitem__
uu2[0] = 0
uu2[1] = 100
try:
uu2[2] = 200
except IndexError:
pass
else:
raise TestFailed("uu2[2] shouldn't be assignable")
# Test __delitem__
del uu2[1]
del uu2[0]
try:
del uu2[0]
except IndexError:
pass
else:
raise TestFailed("uu2[0] shouldn't be deletable")
# Test __getslice__
for i in range(-3, 4):
eq(u2[:i], l2[:i], "u2[:i] == l2[:i]")
eq(u2[i:], l2[i:], "u2[i:] == l2[i:]")
for j in range(-3, 4):
eq(u2[i:j], l2[i:j], "u2[i:j] == l2[i:j]")
# Test __setslice__
for i in range(-3, 4):
u2[:i] = l2[:i]
eq(u2, l2, "u2 == l2")
u2[i:] = l2[i:]
eq(u2, l2, "u2 == l2")
for j in range(-3, 4):
u2[i:j] = l2[i:j]
eq(u2, l2, "u2 == l2")
uu2 = u2[:]
uu2[:0] = [-2, -1]
eq(uu2, [-2, -1, 0, 1], "uu2 == [-2, -1, 0, 1]")
uu2[0:] = []
eq(uu2, [], "uu2 == []")
# Test __contains__
for i in u2:
self.failUnless(i in u2, "i in u2")
for i in min(u2)-1, max(u2)+1:
self.failUnless(i not in u2, "i not in u2")
# Test __delslice__
uu2 = u2[:]
del uu2[1:2]
del uu2[0:1]
eq(uu2, [], "uu2 == []")
uu2 = u2[:]
del uu2[1:]
del uu2[:1]
eq(uu2, [], "uu2 == []")
# Test __add__, __radd__, __mul__ and __rmul__
#self.failUnless(u1 + [] == [] + u1 == u1, "u1 + [] == [] + u1 == u1")
self.failUnless(u1 + [1] == u2, "u1 + [1] == u2")
#self.failUnless([-1] + u1 == [-1, 0], "[-1] + u1 == [-1, 0]")
self.failUnless(u2 == u2*1 == 1*u2, "u2 == u2*1 == 1*u2")
self.failUnless(u2+u2 == u2*2 == 2*u2, "u2+u2 == u2*2 == 2*u2")
self.failUnless(u2+u2+u2 == u2*3 == 3*u2, "u2+u2+u2 == u2*3 == 3*u2")
# Test append
u = u1[:]
u.append(1)
eq(u, u2, "u == u2")
# Test insert
u = u2[:]
u.insert(0, -1)
eq(u, [-1, 0, 1], "u == [-1, 0, 1]")
# Test pop
u = PersistentList([0, -1, 1])
u.pop()
eq(u, [0, -1], "u == [0, -1]")
u.pop(0)
eq(u, [-1], "u == [-1]")
# Test remove
u = u2[:]
u.remove(1)
eq(u, u1, "u == u1")
# Test count
u = u2*3
eq(u.count(0), 3, "u.count(0) == 3")
eq(u.count(1), 3, "u.count(1) == 3")
eq(u.count(2), 0, "u.count(2) == 0")
# Test index
eq(u2.index(0), 0, "u2.index(0) == 0")
eq(u2.index(1), 1, "u2.index(1) == 1")
try:
u2.index(2)
except ValueError:
pass
else:
raise TestFailed("expected ValueError")
# Test reverse
u = u2[:]
u.reverse()
eq(u, [1, 0], "u == [1, 0]")
u.reverse()
eq(u, u2, "u == u2")
# Test sort
u = PersistentList([1, 0])
u.sort()
eq(u, u2, "u == u2")
# Test extend
u = u1[:]
u.extend(u2)
eq(u, u1 + u2, "u == u1 + u2")
# Test iadd
u = u1[:]
u += u2
eq(u, u1 + u2, "u == u1 + u2")
# Test imul
u = u1[:]
u *= 3
eq(u, u1 + u1 + u1, "u == u1 + u1 + u1")
def test_suite():
return unittest.makeSuite(TestPList)
if __name__ == "__main__":
loader = unittest.TestLoader()
unittest.main(testLoader=loader)
##############################################################################
#
# Copyright (c) 2004 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.
#
##############################################################################
"""Overriding attr methods
This module tests and documents, through example, overriding attribute
access methods.
$Id: test_overriding_attrs.py,v 1.2 2004/02/19 02:59:32 jeremy Exp $
"""
from persistent import Persistent
try:
from transaction import get_transaction
except ImportError:
pass # else assume ZODB will install it as a builtin
from ZODB.tests.util import DB
class SampleOverridingGetattr(Persistent):
"""Example of overriding __getattr__
"""
def __getattr__(self, name):
"""Get attributes that can't be gotten the usual way
The __getattr__ method works pretty much the same for persistent
classes as it does for other classes. No special handling is
needed. If an object is a ghost, then it will be activated before
__getattr__ is called.
In this example, our objects returns a tuple with the attribute
name, converted to upper case and the value of _p_changed, for any
attribute that isn't handled by the default machinery.
>>> o = SampleOverridingGetattr()
>>> o._p_changed
0
>>> o._p_oid
>>> o._p_jar
>>> o.spam
('SPAM', 0)
>>> o.spam = 1
>>> o.spam
1
We'll save the object, so it can be deactivated:
>>> db = DB()
>>> conn = db.open()
>>> conn.root()['o'] = o
>>> get_transaction().commit()
>>> o._p_deactivate()
>>> o._p_changed
And now, if we ask for an attribute it doesn't have,
>>> o.eggs
('EGGS', 0)
And we see that the object was activated before calling the
__getattr__ method.
We always close databases after we use them:
>>> db.close()
"""
return name.upper(), self._p_changed
class SampleOverridingGetattributeSetattrAndDelattr(Persistent):
"""Example of overriding __getattribute__, __setattr__, and __delattr__
In this example, we'll provide an example that shows how to
override the __getattribute__, __setattr__, and __delattr__
methods. We'll create a class that stores it's attributes in a
secret dictionary within it's instance dictionary.
The class will have the policy that variables with names starting
with 'tmp_' will be volatile.
"""
def __init__(self, **kw):
self.__dict__['__secret__'] = kw.copy()
def __getattribute__(self, name):
"""Get an attribute value
The __getattribute__ method is called for all attribute
accesses. It overrides the attribute access support inherited
from Persistent.
Our sample class let's us provide initial values as keyword
arguments to the constructor:
>>> o = SampleOverridingGetattributeSetattrAndDelattr(x=1)
>>> o._p_changed
0
>>> o._p_oid
>>> o._p_jar
>>> o.x
1
>>> o.y
Traceback (most recent call last):
...
AttributeError: y
Next, we'll save the object in a database so that we can
deactivate it:
>>> db = DB()
>>> conn = db.open()
>>> conn.root()['o'] = o
>>> get_transaction().commit()
>>> o._p_deactivate()
>>> o._p_changed
And we'll get some data:
>>> o.x
1
which activates the object:
>>> o._p_changed
0
It works for missing attribes too:
>>> o._p_deactivate()
>>> o._p_changed
>>> o.y
Traceback (most recent call last):
...
AttributeError: y
>>> o._p_changed
0
See the very important note in the comment below!
We always close databases after we use them:
>>> db.close()
"""
#################################################################
# IMPORTANT! READ THIS! 8->
#
# We *always* give Persistent a chance first.
# Persistent handles certain special attributes, like _p_
# attributes. In particular, the base class handles __dict__
# and __class__.
#
# We call _p_getattr. If it returns True, then we have to
# use Persistent.__getattribute__ to get the value.
#
#################################################################
if Persistent._p_getattr(self, name):
return Persistent.__getattribute__(self, name)
# Data should be in our secret dictionary:
secret = self.__dict__['__secret__']
if name in secret:
return secret[name]
# Maybe it's a method:
meth = getattr(self.__class__, name, None)
if meth is None:
raise AttributeError, name
return meth.__get__(self, self.__class__)
def __setattr__(self, name, value):
"""Set an attribute value
The __setattr__ method is called for all attribute
assignments. It overrides the attribute assignment support
inherited from Persistent.
Implementors of __setattr__ methods:
1. Must call Persistent._p_setattr first to allow it
to handle some attributes and to make sure that the object
is activated if necessary, and
2. Must set _p_changed to mark objects as changed.
See the comments in the source below.
>>> o = SampleOverridingGetattributeSetattrAndDelattr()
>>> o._p_changed
0
>>> o._p_oid
>>> o._p_jar
>>> o.x
Traceback (most recent call last):
...
AttributeError: x
>>> o.x = 1
>>> o.x
1
Because the implementation doesn't store attributes directly
in the instance dictionary, we don't have a key for the attribute:
>>> 'x' in o.__dict__
False
Next, we'll save the object in a database so that we can
deactivate it:
>>> db = DB()
>>> conn = db.open()
>>> conn.root()['o'] = o
>>> get_transaction().commit()
>>> o._p_deactivate()
>>> o._p_changed
We'll modify an attribute
>>> o.y = 2
>>> o.y
2
which reactivates it, and markes it as modified, because our
implementation marked it as modified:
>>> o._p_changed
1
Now, if commit:
>>> get_transaction().commit()
>>> o._p_changed
0
And deactivate the object:
>>> o._p_deactivate()
>>> o._p_changed
and then set a variable with a name starting with 'tmp_',
The object will be activated, but not marked as modified,
because our __setattr__ implementation doesn't mark the
object as changed if the name starts with 'tmp_':
>>> o.tmp_foo = 3
>>> o._p_changed
0
>>> o.tmp_foo
3
We always close databases after we use them:
>>> db.close()
"""
#################################################################
# IMPORTANT! READ THIS! 8->
#
# We *always* give Persistent a chance first.
# Persistent handles certain special attributes, like _p_
# attributes.
#
# We call _p_setattr. If it returns True, then we are done.
# It has already set the attribute.
#
#################################################################
if Persistent._p_setattr(self, name, value):
return
self.__dict__['__secret__'][name] = value
if not name.startswith('tmp_'):
self._p_changed = 1
def __delattr__(self, name):
"""Delete an attribute value
The __delattr__ method is called for all attribute
deletions. It overrides the attribute deletion support
inherited from Persistent.
Implementors of __delattr__ methods:
1. Must call Persistent._p_delattr first to allow it
to handle some attributes and to make sure that the object
is activated if necessary, and
2. Must set _p_changed to mark objects as changed.
See the comments in the source below.
>>> o = SampleOverridingGetattributeSetattrAndDelattr(
... x=1, y=2, tmp_z=3)
>>> o._p_changed
0
>>> o._p_oid
>>> o._p_jar
>>> o.x
1
>>> del o.x
>>> o.x
Traceback (most recent call last):
...
AttributeError: x
Next, we'll save the object in a database so that we can
deactivate it:
>>> db = DB()
>>> conn = db.open()
>>> conn.root()['o'] = o
>>> get_transaction().commit()
>>> o._p_deactivate()
>>> o._p_changed
If we delete an attribute:
>>> del o.y
The object is activated. It is also marked as changed because
our implementation marked it as changed.
>>> o._p_changed
1
>>> o.y
Traceback (most recent call last):
...
AttributeError: y
>>> o.tmp_z
3
Now, if commit:
>>> get_transaction().commit()
>>> o._p_changed
0
And deactivate the object:
>>> o._p_deactivate()
>>> o._p_changed
and then delete a variable with a name starting with 'tmp_',
The object will be activated, but not marked as modified,
because our __delattr__ implementation doesn't mark the
object as changed if the name starts with 'tmp_':
>>> del o.tmp_z
>>> o._p_changed
0
>>> o.tmp_z
Traceback (most recent call last):
...
AttributeError: tmp_z
We always close databases after we use them:
>>> db.close()
"""
#################################################################
# IMPORTANT! READ THIS! 8->
#
# We *always* give Persistent a chance first.
# Persistent handles certain special attributes, like _p_
# attributes.
#
# We call _p_delattr. If it returns True, then we are done.
# It has already deleted the attribute.
#
#################################################################
if Persistent._p_delattr(self, name):
return
del self.__dict__['__secret__'][name]
if not name.startswith('tmp_'):
self._p_changed = 1
def test_suite():
from doctest import DocTestSuite
return DocTestSuite()
##############################################################################
#
# 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.
#
##############################################################################
import unittest
from persistent import Persistent
from persistent.interfaces import IPersistent
try:
import zope.interface
except ImportError:
interfaces = False
else:
interfaces = True
class Test(unittest.TestCase):
klass = None # override in subclass
def testSaved(self):
p = self.klass()
p._p_oid = '\0\0\0\0\0\0hi'
dm = DM()
p._p_jar = dm
self.assertEqual(p._p_changed, 0)
self.assertEqual(dm.called, 0)
p.inc()
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 1)
p.inc()
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 1)
p._p_deactivate()
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 1)
p._p_deactivate()
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 1)
del p._p_changed
# XXX deal with current cPersistence implementation
if p._p_changed != 3:
self.assertEqual(p._p_changed, None)
self.assertEqual(dm.called, 1)
p.inc()
self.assertEqual(p.x, 43)
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 2)
p._p_changed = 0
self.assertEqual(p._p_changed, 0)
self.assertEqual(dm.called, 2)
self.assertEqual(p.x, 43)
p.inc()
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 3)
def testUnsaved(self):
p = self.klass()
self.assertEqual(p.x, 0)
self.assertEqual(p._p_changed, 0)
self.assertEqual(p._p_jar, None)
self.assertEqual(p._p_oid, None)
p.inc()
p.inc()
self.assertEqual(p.x, 2)
self.assertEqual(p._p_changed, 0)
p._p_deactivate()
self.assertEqual(p._p_changed, 0)
p._p_changed = 1
self.assertEqual(p._p_changed, 0)
p._p_deactivate()
self.assertEqual(p._p_changed, 0)
del p._p_changed
self.assertEqual(p._p_changed, 0)
if self.has_dict:
self.failUnless(p.__dict__)
self.assertEqual(p.x, 2)
def testState(self):
p = self.klass()
self.assertEqual(p.__getstate__(), {'x': 0})
self.assertEqual(p._p_changed, 0)
p.__setstate__({'x':5})
self.assertEqual(p._p_changed, 0)
if self.has_dict:
p._v_foo = 2
self.assertEqual(p.__getstate__(), {'x': 5})
self.assertEqual(p._p_changed, 0)
def testSetStateSerial(self):
p = self.klass()
p._p_serial = '00000012'
p.__setstate__(p.__getstate__())
self.assertEqual(p._p_serial, '00000012')
def testDirectChanged(self):
p = self.klass()
p._p_oid = 1
dm = DM()
p._p_jar = dm
self.assertEqual(p._p_changed, 0)
self.assertEqual(dm.called, 0)
p._p_changed = 1
self.assertEqual(dm.called, 1)
def testGhostChanged(self):
# An object is a ghost, and it's _p_changed it set to True.
# This assignment should have no effect.
p = self.klass()
p._p_oid = 1
dm = DM()
p._p_jar = dm
p._p_deactivate()
self.assertEqual(p._p_changed, None)
p._p_changed = True
self.assertEqual(p._p_changed, None)
def testRegistrationFailure(self):
p = self.klass()
p._p_oid = 1
dm = BrokenDM()
p._p_jar = dm
self.assertEqual(p._p_changed, 0)
self.assertEqual(dm.called, 0)
try:
p._p_changed = 1
except NotImplementedError:
pass
else:
raise AssertionError("Exception not propagated")
self.assertEqual(dm.called, 1)
self.assertEqual(p._p_changed, 0)
def testLoadFailure(self):
p = self.klass()
p._p_oid = 1
dm = BrokenDM()
p._p_jar = dm
p._p_deactivate() # make it a ghost
try:
p._p_activate()
except NotImplementedError:
pass
else:
raise AssertionError("Exception not propagated")
self.assertEqual(p._p_changed, None)
def testActivate(self):
p = self.klass()
dm = DM()
p._p_oid = 1
p._p_jar = dm
p._p_changed = 0
p._p_deactivate()
# XXX does this really test the activate method?
p._p_activate()
self.assertEqual(p._p_changed, 0)
self.assertEqual(p.x, 42)
def testDeactivate(self):
p = self.klass()
dm = DM()
p._p_oid = 1
p._p_deactivate() # this deactive has no effect
self.assertEqual(p._p_changed, 0)
p._p_jar = dm
p._p_changed = 0
p._p_deactivate()
self.assertEqual(p._p_changed, None)
p._p_activate()
self.assertEqual(p._p_changed, 0)
self.assertEqual(p.x, 42)
if interfaces:
def testInterface(self):
self.assert_(IPersistent.isImplementedByInstancesOf(Persistent),
"%s does not implement IPersistent" % Persistent)
p = Persistent()
self.assert_(IPersistent.isImplementedBy(p),
"%s does not implement IPersistent" % p)
self.assert_(IPersistent.isImplementedByInstancesOf(P),
"%s does not implement IPersistent" % P)
p = self.klass()
self.assert_(IPersistent.isImplementedBy(p),
"%s does not implement IPersistent" % p)
def testDataManagerAndAttributes(self):
# Test to cover an odd bug where the instance __dict__ was
# set at the same location as the data manager in the C type.
p = P()
p.inc()
p.inc()
self.assert_('x' in p.__dict__)
self.assert_(p._p_jar is None)
def testMultipleInheritance(self):
# make sure it is possible to inherit from two different
# subclasses of persistent.
class A(Persistent):
pass
class B(Persistent):
pass
class C(A, B):
pass
class D(object):
pass
class E(D, B):
pass
def testMultipleMeta(self):
# make sure it's possible to define persistent classes
# with a base whose metaclass is different
class alternateMeta(type):
pass
class alternate(object):
__metaclass__ = alternateMeta
class mixedMeta(alternateMeta, type):
pass
class mixed(alternate,Persistent):
__metaclass__ = mixedMeta
def testSlots(self):
# Verify that Persistent classes behave the same way
# as pure Python objects where '__slots__' and '__dict__'
# are concerned.
class noDict(object):
__slots__ = ['foo']
class shouldHaveDict(noDict):
pass
class p_noDict(Persistent):
__slots__ = ['foo']
class p_shouldHaveDict(p_noDict):
pass
self.assertEqual(noDict.__dictoffset__, 0)
self.assertEqual(p_noDict.__dictoffset__, 0)
self.assert_(shouldHaveDict.__dictoffset__ <> 0)
self.assert_(p_shouldHaveDict.__dictoffset__ <> 0)
def testBasicTypeStructure(self):
# test that a persistent class has a sane C type structure
# use P (defined below) as simplest example
self.assertEqual(Persistent.__dictoffset__, 0)
self.assertEqual(Persistent.__weakrefoffset__, 0)
self.assert_(Persistent.__basicsize__ > object.__basicsize__)
self.assert_(P.__dictoffset__)
self.assert_(P.__weakrefoffset__)
self.assert_(P.__dictoffset__ < P.__weakrefoffset__)
self.assert_(P.__basicsize__ > Persistent.__basicsize__)
def testDeactivateErrors(self):
p = self.klass()
p._p_oid = '\0\0\0\0\0\0hi'
dm = DM()
p._p_jar = dm
def typeerr(*args, **kwargs):
self.assertRaises(TypeError, p, *args, **kwargs)
typeerr(1)
typeerr(1, 2)
typeerr(spam=1)
typeerr(spam=1, force=1)
p._p_changed = True
class Err(object):
def __nonzero__(self):
raise RuntimeError
typeerr(force=Err())
class P(Persistent):
def __init__(self):
self.x = 0
def inc(self):
self.x += 1
class P2(P):
def __getstate__(self):
return 42
def __setstate__(self, v):
self.v = v
class B(Persistent):
__slots__ = ["x", "_p_serial"]
def __init__(self):
self.x = 0
def inc(self):
self.x += 1
def __getstate__(self):
return {'x': self.x}
def __setstate__(self, state):
self.x = state['x']
class DM:
def __init__(self):
self.called = 0
def register(self, ob):
self.called += 1
def setstate(self, ob):
ob.__setstate__({'x': 42})
class BrokenDM(DM):
def register(self,ob):
self.called += 1
raise NotImplementedError
def setstate(self,ob):
raise NotImplementedError
class PersistentTest(Test):
klass = P
has_dict = 1
def testPicklable(self):
import pickle
p = self.klass()
p.inc()
p2 = pickle.loads(pickle.dumps(p))
self.assertEqual(p2.__class__, self.klass)
# verify that the inc is reflected:
self.assertEqual(p2.x, p.x)
# This assertion would be invalid. Interfaces
# are compared by identity and copying doesn't
# preserve identity. We would get false negatives due
# to the differing identities of the original and copied
# PersistentInterface:
# self.assertEqual(p2.__dict__, p.__dict__)
def testPicklableWCustomState(self):
import pickle
p = P2()
p2 = pickle.loads(pickle.dumps(p))
self.assertEqual(p2.__class__, P2);
self.assertEqual(p2.__dict__, {'v': 42})
class BasePersistentTest(Test):
klass = B
has_dict = 0
def test_suite():
s = unittest.TestSuite()
for klass in PersistentTest, BasePersistentTest:
s.addTest(unittest.makeSuite(klass))
return s
......@@ -13,7 +13,7 @@
##############################################################################
"""Basic pickling tests
$Id: test_pickle.py,v 1.3 2003/12/29 22:40:50 tim_one Exp $
$Id: test_pickle.py,v 1.4 2004/02/19 02:59:32 jeremy Exp $
"""
from persistent import Persistent
......@@ -73,7 +73,8 @@ def test_basic_pickling():
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.__setstate__({'z': 1})
......@@ -123,7 +124,8 @@ def test_pickling_w_overrides():
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
......@@ -164,7 +166,8 @@ def test_pickling_w_slots_only():
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.s4 = 'spam'
......@@ -180,7 +183,8 @@ def test_pickling_w_slots_only():
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
......@@ -217,7 +221,8 @@ def test_pickling_w_slots():
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.s4 = 'spam'
......@@ -234,7 +239,8 @@ def test_pickling_w_slots():
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
......@@ -258,7 +264,8 @@ def test_pickling_w_slots_w_empty_dict():
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.s4 = 'spam'
......@@ -275,7 +282,8 @@ def test_pickling_w_slots_w_empty_dict():
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
......
##############################################################################
#
# 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.
#
##############################################################################
"""XXX short summary goes here.
$Id: test_wref.py,v 1.2 2004/02/19 02:59:32 jeremy Exp $
"""
import unittest
from doctest import DocTestSuite
def test_suite():
return DocTestSuite('persistent.wref')
if __name__ == '__main__':
unittest.main()
##############################################################################
#
# 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: wref.py,v 1.2 2004/02/19 02:59:30 jeremy Exp $
"""
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. :)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment