Commit 13d745d5 authored by root's avatar root

Use tagged version of src/persistent

parent 9866e019
zope.interface
# the following are needed by the tests
transaction
ZODB
===================
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.
# Extension information for zpkg.
# Mark an "exported" header for use from other packages.
# This is not needed for headers only used within the package.
#
header cPersistence.h
# This is included by cPersistence.h, so all users of cPersistence.h
# have to be able to include this indirectly.
#
header ring.h
<extension cPersistence>
source cPersistence.c
source ring.c
depends-on cPersistence.h
depends-on ring.h
</extension>
<extension cPickleCache>
source cPickleCache.c
source ring.c
depends-on cPersistence.h
depends-on ring.h
</extension>
<extension TimeStamp>
source TimeStamp.c
</extension>
/*****************************************************************************
Copyright (c) 2001, 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
****************************************************************************/
#include "Python.h"
#include <time.h>
PyObject *TimeStamp_FromDate(int, int, int, int, int, double);
PyObject *TimeStamp_FromString(const char *);
static char TimeStampModule_doc[] =
"A 64-bit TimeStamp used as a ZODB serial number.\n"
"\n"
"$Id: TimeStamp.c,v 1.5 2004/05/03 20:17:57 spascoe Exp $\n";
typedef struct {
PyObject_HEAD
unsigned char data[8];
} TimeStamp;
/* The first dimension of the arrays below is non-leapyear / leapyear */
static char month_len[2][12]={
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
static short joff[2][12] = {
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
};
static double gmoff=0;
/* XXX should this be stored in sconv? */
#define SCONV ((double)60) / ((double)(1<<16)) / ((double)(1<<16))
static int
leap(int year)
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
static int
days_in_month(int year, int month)
{
return month_len[leap(year)][month];
}
static double
TimeStamp_yad(int y)
{
double d, s;
y -= 1900;
d = (y - 1) * 365;
if (y > 0) {
s = 1.0;
y -= 1;
} else {
s = -1.0;
y = -y;
}
return d + s * (y / 4 - y / 100 + (y + 300) / 400);
}
static double
TimeStamp_abst(int y, int mo, int d, int m, int s)
{
return (TimeStamp_yad(y) + joff[leap(y)][mo] + d) * 86400 + m * 60 + s;
}
static int
TimeStamp_init_gmoff(void)
{
struct tm *t;
time_t z=0;
t = gmtime(&z);
if (t == NULL) {
PyErr_SetString(PyExc_SystemError, "gmtime failed");
return -1;
}
gmoff = TimeStamp_abst(t->tm_year+1900, t->tm_mon, t->tm_mday - 1,
t->tm_hour * 60 + t->tm_min, t->tm_sec);
return 0;
}
static void
TimeStamp_dealloc(TimeStamp *ts)
{
PyObject_Del(ts);
}
static int
TimeStamp_compare(TimeStamp *v, TimeStamp *w)
{
int cmp = memcmp(v->data, w->data, 8);
if (cmp < 0) return -1;
if (cmp > 0) return 1;
return 0;
}
static long
TimeStamp_hash(TimeStamp *self)
{
register unsigned char *p = (unsigned char *)self->data;
register int len = 8;
register long x = *p << 7;
/* XXX unroll loop? */
while (--len >= 0)
x = (1000003*x) ^ *p++;
x ^= 8;
if (x == -1)
x = -2;
return x;
}
typedef struct {
/* XXX reverse-engineer what's in these things and comment them */
int y;
int m;
int d;
int mi;
} TimeStampParts;
static void
TimeStamp_unpack(TimeStamp *self, TimeStampParts *p)
{
unsigned long v;
v = (self->data[0] * 16777216 + self->data[1] * 65536
+ self->data[2] * 256 + self->data[3]);
p->y = v / 535680 + 1900;
p->m = (v % 535680) / 44640 + 1;
p->d = (v % 44640) / 1440 + 1;
p->mi = v % 1440;
}
static double
TimeStamp_sec(TimeStamp *self)
{
unsigned int v;
v = (self->data[4] * 16777216 + self->data[5] * 65536
+ self->data[6] * 256 + self->data[7]);
return SCONV * v;
}
static PyObject *
TimeStamp_year(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyInt_FromLong(p.y);
}
static PyObject *
TimeStamp_month(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyInt_FromLong(p.m);
}
static PyObject *
TimeStamp_day(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyInt_FromLong(p.d);
}
static PyObject *
TimeStamp_hour(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyInt_FromLong(p.mi / 60);
}
static PyObject *
TimeStamp_minute(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyInt_FromLong(p.mi % 60);
}
static PyObject *
TimeStamp_second(TimeStamp *self)
{
return PyFloat_FromDouble(TimeStamp_sec(self));
}
static PyObject *
TimeStamp_timeTime(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyFloat_FromDouble(TimeStamp_abst(p.y, p.m - 1, p.d - 1, p.mi, 0)
+ TimeStamp_sec(self) - gmoff);
}
static PyObject *
TimeStamp_raw(TimeStamp *self)
{
return PyString_FromStringAndSize(self->data, 8);
}
static PyObject *
TimeStamp_str(TimeStamp *self)
{
char buf[128];
TimeStampParts p;
int len;
TimeStamp_unpack(self, &p);
len =sprintf(buf, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%09.6f",
p.y, p.m, p.d, p.mi / 60, p.mi % 60,
TimeStamp_sec(self));
return PyString_FromStringAndSize(buf, len);
}
static PyObject *
TimeStamp_laterThan(TimeStamp *self, PyObject *obj)
{
TimeStamp *o = NULL;
TimeStampParts p;
unsigned char new[8];
int i;
if (obj->ob_type != self->ob_type) {
PyErr_SetString(PyExc_TypeError, "expected TimeStamp object");
return NULL;
}
o = (TimeStamp *)obj;
if (memcmp(self->data, o->data, 8) > 0) {
Py_INCREF(self);
return (PyObject *)self;
}
memcpy(new, o->data, 8);
for (i = 7; i > 3; i--) {
if (new[i] == 255)
new[i] = 0;
else {
new[i]++;
return TimeStamp_FromString(new);
}
}
/* All but the first two bytes are the same. Need to increment
the year, month, and day explicitly. */
TimeStamp_unpack(o, &p);
if (p.mi >= 1439) {
p.mi = 0;
if (p.d == month_len[leap(p.y)][p.m - 1]) {
p.d = 1;
if (p.m == 12) {
p.m = 1;
p.y++;
} else
p.m++;
} else
p.d++;
} else
p.mi++;
return TimeStamp_FromDate(p.y, p.m, p.d, p.mi / 60, p.mi % 60, 0);
}
static struct PyMethodDef TimeStamp_methods[] = {
{"year", (PyCFunction)TimeStamp_year, METH_NOARGS},
{"minute", (PyCFunction)TimeStamp_minute, METH_NOARGS},
{"month", (PyCFunction)TimeStamp_month, METH_NOARGS},
{"day", (PyCFunction)TimeStamp_day, METH_NOARGS},
{"hour", (PyCFunction)TimeStamp_hour, METH_NOARGS},
{"second", (PyCFunction)TimeStamp_second, METH_NOARGS},
{"timeTime",(PyCFunction)TimeStamp_timeTime, METH_NOARGS},
{"laterThan", (PyCFunction)TimeStamp_laterThan, METH_O},
{"raw", (PyCFunction)TimeStamp_raw, METH_NOARGS},
{NULL, NULL},
};
static PyTypeObject TimeStamp_type = {
PyObject_HEAD_INIT(NULL)
0,
"persistent.TimeStamp",
sizeof(TimeStamp),
0,
(destructor)TimeStamp_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
(cmpfunc)TimeStamp_compare, /* tp_compare */
(reprfunc)TimeStamp_raw, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
(hashfunc)TimeStamp_hash, /* tp_hash */
0, /* tp_call */
(reprfunc)TimeStamp_str, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
TimeStamp_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
};
PyObject *
TimeStamp_FromString(const char *buf)
{
/* buf must be exactly 8 characters */
TimeStamp *ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type);
memcpy(ts->data, buf, 8);
return (PyObject *)ts;
}
#define CHECK_RANGE(VAR, LO, HI) if ((VAR) < (LO) || (VAR) > (HI)) { \
return PyErr_Format(PyExc_ValueError, \
# VAR " must be between %d and %d: %d", \
(LO), (HI), (VAR)); \
}
PyObject *
TimeStamp_FromDate(int year, int month, int day, int hour, int min,
double sec)
{
TimeStamp *ts = NULL;
int d;
unsigned int v;
if (year < 1900)
return PyErr_Format(PyExc_ValueError,
"year must be greater than 1900: %d", year);
CHECK_RANGE(month, 1, 12);
d = days_in_month(year, month - 1);
if (day < 1 || day > d)
return PyErr_Format(PyExc_ValueError,
"day must be between 1 and %d: %d", d, day);
CHECK_RANGE(hour, 0, 23);
CHECK_RANGE(min, 0, 59);
/* Seconds are allowed to be anything, so chill
If we did want to be pickly, 60 would be a better choice.
if (sec < 0 || sec > 59)
return PyErr_Format(PyExc_ValueError,
"second must be between 0 and 59: %f", sec);
*/
ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type);
v = (((year - 1900) * 12 + month - 1) * 31 + day - 1);
v = (v * 24 + hour) * 60 + min;
ts->data[0] = v / 16777216;
ts->data[1] = (v % 16777216) / 65536;
ts->data[2] = (v % 65536) / 256;
ts->data[3] = v % 256;
sec /= SCONV;
v = (unsigned int)sec;
ts->data[4] = v / 16777216;
ts->data[5] = (v % 16777216) / 65536;
ts->data[6] = (v % 65536) / 256;
ts->data[7] = v % 256;
return (PyObject *)ts;
}
PyObject *
TimeStamp_TimeStamp(PyObject *obj, PyObject *args)
{
char *buf = NULL;
int len = 0, y, mo, d, h = 0, m = 0;
double sec = 0;
if (PyArg_ParseTuple(args, "s#:TimeStamp", &buf, &len)) {
if (len != 8) {
PyErr_SetString(PyExc_ValueError, "8-character string expected");
return NULL;
}
return TimeStamp_FromString(buf);
}
PyErr_Clear();
if (!PyArg_ParseTuple(args, "iii|iid", &y, &mo, &d, &h, &m, &sec))
return NULL;
return TimeStamp_FromDate(y, mo, d, h, m, sec);
}
static PyMethodDef TimeStampModule_functions[] = {
{"TimeStamp", TimeStamp_TimeStamp, METH_VARARGS},
{NULL, NULL},
};
void
initTimeStamp(void)
{
PyObject *m;
if (TimeStamp_init_gmoff() < 0)
return;
m = Py_InitModule4("TimeStamp", TimeStampModule_functions,
TimeStampModule_doc, NULL, PYTHON_API_VERSION);
if (m == NULL)
return;
TimeStamp_type.ob_type = &PyType_Type;
TimeStamp_type.tp_getattro = PyObject_GenericGetAttr;
}
##############################################################################
#
# 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
#
##############################################################################
"""Provide access to Persistent and PersistentMapping.
$Id: __init__.py,v 1.8 2004/02/24 13:54:05 srichter Exp $
"""
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)
/*****************************************************************************
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
****************************************************************************/
static char cPersistence_doc_string[] =
"Defines Persistent mixin class for persistent objects.\n"
"\n"
"$Id: cPersistence.c,v 1.80 2004/03/13 07:42:06 jeremy Exp $\n";
#include "cPersistence.h"
#include "structmember.h"
struct ccobject_head_struct {
CACHE_HEAD
};
/* 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;
static PyObject *py__p_changed, *py__p_deactivate;
static PyObject *py___getattr__, *py___setattr__, *py___delattr__;
static PyObject *py___getstate__;
static PyObject *py___slotnames__, *copy_reg_slotnames, *__newobj__;
static PyObject *py___getnewargs__, *py___getstate__;
static int
init_strings(void)
{
#define INIT_STRING(S) \
if (!(py_ ## S = PyString_InternFromString(#S))) \
return -1;
INIT_STRING(keys);
INIT_STRING(setstate);
INIT_STRING(timeTime);
INIT_STRING(__dict__);
INIT_STRING(_p_changed);
INIT_STRING(_p_deactivate);
INIT_STRING(__getattr__);
INIT_STRING(__setattr__);
INIT_STRING(__delattr__);
INIT_STRING(__getstate__);
INIT_STRING(__slotnames__);
INIT_STRING(__getnewargs__);
INIT_STRING(__getstate__);
#undef INIT_STRING
return 0;
}
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 -1.
*/
static int
unghostify(cPersistentObject *self)
{
if (self->state < 0 && self->jar) {
PyObject *r;
/* XXX Is it ever possibly to not have a cache? */
if (self->cache) {
/* Create a node in the ring for this unghostified object. */
self->cache->non_ghost_count++;
ring_add(&self->cache->ring_home, &self->ring);
Py_INCREF(self);
}
/* set state to CHANGED while setstate() call is in progress
to prevent a recursive call to _PyPersist_Load().
*/
self->state = cPersistent_CHANGED_STATE;
/* Call the object's __setstate__() */
r = PyObject_CallMethod(self->jar, "setstate", "O", (PyObject *)self);
if (r == NULL) {
ghostify(self);
return -1;
}
self->state = cPersistent_UPTODATE_STATE;
Py_DECREF(r);
}
return 1;
}
/****************************************************************************/
static PyTypeObject Pertype;
static void
accessed(cPersistentObject *self)
{
/* Do nothing unless the object is in a cache and not a ghost. */
if (self->cache && self->state >= 0 && self->ring.r_next)
ring_move_to_head(&self->cache->ring_home, &self->ring);
}
static void
unlink_from_ring(cPersistentObject *self)
{
/* If the cache has been cleared, then a non-ghost object
isn't in the ring any longer.
*/
if (self->ring.r_next == NULL)
return;
/* if we're ghostifying an object, we better have some non-ghosts */
assert(self->cache->non_ghost_count > 0);
self->cache->non_ghost_count--;
ring_del(&self->ring);
}
static void
ghostify(cPersistentObject *self)
{
PyObject **dictptr;
/* are we already a ghost? */
if (self->state == cPersistent_GHOST_STATE)
return;
/* XXX is it ever possible to not have a cache? */
if (self->cache == NULL) {
self->state = cPersistent_GHOST_STATE;
return;
}
/* If the cache is still active, we must unlink the object. */
if (self->ring.r_next) {
/* if we're ghostifying an object, we better have some non-ghosts */
assert(self->cache->non_ghost_count > 0);
self->cache->non_ghost_count--;
ring_del(&self->ring);
}
self->state = cPersistent_GHOST_STATE;
dictptr = _PyObject_GetDictPtr((PyObject *)self);
if (dictptr && *dictptr) {
Py_DECREF(*dictptr);
*dictptr = NULL;
}
/* We remove the reference to the just ghosted object that the ring
* holds. Note that the dictionary of oids->objects has an uncounted
* reference, so if the ring's reference was the only one, this frees
* the ghost object. Note further that the object's dealloc knows to
* inform the dictionary that it is going away.
*/
Py_DECREF(self);
}
static int
changed(cPersistentObject *self)
{
if ((self->state == cPersistent_UPTODATE_STATE ||
self->state == cPersistent_STICKY_STATE)
&& self->jar)
{
PyObject *meth, *arg, *result;
static PyObject *s_register;
if (s_register == NULL)
s_register = PyString_InternFromString("register");
meth = PyObject_GetAttr((PyObject *)self->jar, s_register);
if (meth == NULL)
return -1;
arg = PyTuple_New(1);
if (arg == NULL) {
Py_DECREF(meth);
return -1;
}
Py_INCREF(self);
PyTuple_SET_ITEM(arg, 0, (PyObject *)self);
result = PyEval_CallObject(meth, arg);
Py_DECREF(arg);
Py_DECREF(meth);
if (result == NULL)
return -1;
Py_DECREF(result);
self->state = cPersistent_CHANGED_STATE;
}
return 0;
}
static PyObject *
Per__p_deactivate(cPersistentObject *self)
{
if (self->state == cPersistent_UPTODATE_STATE && self->jar) {
PyObject **dictptr = _PyObject_GetDictPtr((PyObject *)self);
if (dictptr && *dictptr) {
Py_DECREF(*dictptr);
*dictptr = NULL;
}
/* Note that we need to set to ghost state unless we are
called directly. Methods that override this need to
do the same! */
ghostify(self);
}
Py_INCREF(Py_None);
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;
}
static PyObject *
pickle_slotnames(PyTypeObject *cls)
{
PyObject *slotnames;
slotnames = PyDict_GetItem(cls->tp_dict, py___slotnames__);
if (slotnames) {
Py_INCREF(slotnames);
return slotnames;
}
slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames,
(PyObject*)cls, NULL);
if (slotnames && !(slotnames == Py_None || PyList_Check(slotnames))) {
PyErr_SetString(PyExc_TypeError,
"copy_reg._slotnames didn't return a list or None");
Py_DECREF(slotnames);
return NULL;
}
return slotnames;
}
static PyObject *
pickle_copy_dict(PyObject *state)
{
PyObject *copy, *key, *value;
char *ckey;
int pos = 0;
copy = PyDict_New();
if (!copy)
return NULL;
if (!state)
return copy;
while (PyDict_Next(state, &pos, &key, &value)) {
if (key && PyString_Check(key)) {
ckey = PyString_AS_STRING(key);
if (*ckey == '_' &&
(ckey[1] == 'v' || ckey[1] == 'p') &&
ckey[2] == '_')
/* skip volatile and persistent */
continue;
}
if (PyObject_SetItem(copy, key, value) < 0)
goto err;
}
return copy;
err:
Py_DECREF(copy);
return NULL;
}
static char pickle___getstate__doc[] =
"Get the object serialization state\n"
"\n"
"If the object has no assigned slots and has no instance dictionary, then \n"
"None is returned.\n"
"\n"
"If the object has no assigned slots and has an instance dictionary, then \n"
"the a copy of the instance dictionary is returned. The copy has any items \n"
"with names starting with '_v_' or '_p_' ommitted.\n"
"\n"
"If the object has assigned slots, then a two-element tuple is returned. \n"
"The first element is either None or a copy of the instance dictionary, \n"
"as described above. The second element is a dictionary with items \n"
"for each of the assigned slots.\n"
;
static PyObject *
pickle___getstate__(PyObject *self)
{
PyObject *slotnames=NULL, *slots=NULL, *state=NULL;
PyObject **dictp;
int n=0;
slotnames = pickle_slotnames(self->ob_type);
if (!slotnames)
return NULL;
dictp = _PyObject_GetDictPtr(self);
if (dictp)
state = pickle_copy_dict(*dictp);
else {
state = Py_None;
Py_INCREF(state);
}
if (slotnames != Py_None) {
int i;
slots = PyDict_New();
if (!slots)
goto end;
for (i = 0; i < PyList_GET_SIZE(slotnames); i++) {
PyObject *name, *value;
char *cname;
name = PyList_GET_ITEM(slotnames, i);
if (PyString_Check(name)) {
cname = PyString_AS_STRING(name);
if (*cname == '_' &&
(cname[1] == 'v' || cname[1] == 'p') &&
cname[2] == '_')
/* skip volatile and persistent */
continue;
}
/* XXX will this go through our getattr hook? */
value = PyObject_GetAttr(self, name);
if (value == NULL)
PyErr_Clear();
else {
int err = PyDict_SetItem(slots, name, value);
Py_DECREF(value);
if (err < 0)
goto end;
n++;
}
}
}
if (n)
state = Py_BuildValue("(NO)", state, slots);
end:
Py_XDECREF(slotnames);
Py_XDECREF(slots);
return state;
}
static int
pickle_setattrs_from_dict(PyObject *self, PyObject *dict)
{
PyObject *key, *value;
int pos = 0;
if (!PyDict_Check(dict)) {
PyErr_SetString(PyExc_TypeError, "Expected dictionary");
return -1;
}
while (PyDict_Next(dict, &pos, &key, &value)) {
if (PyObject_SetAttr(self, key, value) < 0)
return -1;
}
return 0;
}
static char pickle___setstate__doc[] =
"Set the object serialization state\n\n"
"The state should be in one of 3 forms:\n\n"
"- None\n\n"
" Ignored\n\n"
"- A dictionary\n\n"
" In this case, the object's instance dictionary will be cleared and \n"
" updated with the new state.\n\n"
"- A two-tuple with a string as the first element. \n\n"
" In this case, the method named by the string in the first element will be\n"
" called with the second element.\n\n"
" This form supports migration of data formats.\n\n"
"- A two-tuple with None or a Dictionary as the first element and\n"
" with a dictionary as the second element.\n\n"
" If the first element is not None, then the object's instance dictionary \n"
" will be cleared and updated with the value.\n\n"
" The items in the second element will be assigned as attributes.\n"
;
static PyObject *
pickle___setstate__(PyObject *self, PyObject *state)
{
PyObject *slots=NULL;
if (PyTuple_Check(state)) {
if (!PyArg_ParseTuple(state, "OO:__setstate__", &state, &slots))
return NULL;
}
if (state != Py_None) {
PyObject **dict;
dict = _PyObject_GetDictPtr(self);
if (dict) {
if (!*dict) {
*dict = PyDict_New();
if (!*dict)
return NULL;
}
}
if (*dict) {
PyDict_Clear(*dict);
if (PyDict_Update(*dict, state) < 0)
return NULL;
}
else if (pickle_setattrs_from_dict(self, state) < 0)
return NULL;
}
if (slots && pickle_setattrs_from_dict(self, slots) < 0)
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static char pickle___reduce__doc[] =
"Reduce an object to contituent parts for serialization\n"
;
static PyObject *
pickle___reduce__(PyObject *self)
{
PyObject *args=NULL, *bargs=NULL, *state=NULL, *getnewargs=NULL;
int l, i;
getnewargs = PyObject_GetAttr(self, py___getnewargs__);
if (getnewargs) {
bargs = PyObject_CallFunctionObjArgs(getnewargs, NULL);
Py_DECREF(getnewargs);
if (!bargs)
return NULL;
l = PyTuple_Size(bargs);
if (l < 0)
goto end;
}
else {
PyErr_Clear();
l = 0;
}
args = PyTuple_New(l+1);
if (args == NULL)
goto end;
Py_INCREF(self->ob_type);
PyTuple_SET_ITEM(args, 0, (PyObject*)(self->ob_type));
for (i = 0; i < l; i++) {
Py_INCREF(PyTuple_GET_ITEM(bargs, i));
PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i));
}
state = PyObject_CallMethodObjArgs(self, py___getstate__, NULL);
if (!state)
goto end;
state = Py_BuildValue("(OON)", __newobj__, args, state);
end:
Py_XDECREF(bargs);
Py_XDECREF(args);
return state;
}
/* Return the object's state, a dict or None.
If the object has no dict, it's state is None.
Otherwise, return a dict containing all the attributes that
don't start with "_v_".
The caller should not modify this dict, as it may be a reference to
the object's __dict__.
*/
static PyObject *
Per__getstate__(cPersistentObject *self)
{
/* XXX Should it be an error to call __getstate__() on a ghost? */
if (unghostify(self) < 0)
return NULL;
/* XXX shouldn't we increment stickyness? */
return pickle___getstate__((PyObject*)self);
}
/* 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().
There is always a cycle between a persistent object and its cache.
When the cycle becomes unreachable, the clear function for the
cache will break the cycle. Thus, the persistent object need not
have a clear function. It would be complex to write a clear function
for the objects, if we needed one, because of the reference count
tricks done by the cache.
*/
static void
Per_dealloc(cPersistentObject *self)
{
if (self->state >= 0)
unlink_from_ring(self);
if (self->cache)
cPersistenceCAPI->percachedel(self->cache, self->oid);
Py_XDECREF(self->cache);
Py_XDECREF(self->jar);
Py_XDECREF(self->oid);
self->ob_type->tp_free(self);
}
static int
Per_traverse(cPersistentObject *self, visitproc visit, void *arg)
{
int err;
#define VISIT(SLOT) \
if (SLOT) { \
err = visit((PyObject *)(SLOT), arg); \
if (err) \
return err; \
}
VISIT(self->jar);
VISIT(self->oid);
VISIT(self->cache);
#undef VISIT
return 0;
}
/* convert_name() returns a new reference to a string name
or sets an exception and returns NULL.
*/
static PyObject *
convert_name(PyObject *name)
{
#ifdef Py_USING_UNICODE
/* The Unicode to string conversion is done here because the
existing tp_setattro slots expect a string object as name
and we wouldn't want to break those. */
if (PyUnicode_Check(name)) {
name = PyUnicode_AsEncodedString(name, NULL, NULL);
}
else
#endif
if (!PyString_Check(name)) {
PyErr_SetString(PyExc_TypeError, "attribute name must be a string");
return NULL;
} else
Py_INCREF(name);
return name;
}
/* Returns true if the object requires unghostification.
There are several special attributes that we allow access to without
requiring that the object be unghostified:
__class__
__del__
__dict__
__of__
__setstate__
*/
static int
unghost_getattr(const char *s)
{
if (*s++ != '_')
return 1;
if (*s == 'p') {
s++;
if (*s == '_')
return 0; /* _p_ */
else
return 1;
}
else if (*s == '_') {
s++;
switch (*s) {
case 'c':
return strcmp(s, "class__");
case 'd':
s++;
if (!strcmp(s, "el__"))
return 0; /* __del__ */
if (!strcmp(s, "ict__"))
return 0; /* __dict__ */
return 1;
case 'o':
return strcmp(s, "of__");
case 's':
return strcmp(s, "setstate__");
default:
return 1;
}
}
return 1;
}
static PyObject*
Per_getattro(cPersistentObject *self, PyObject *name)
{
PyObject *result = NULL; /* guilty until proved innocent */
char *s;
name = convert_name(name);
if (!name)
goto Done;
s = PyString_AS_STRING(name);
if (unghost_getattr(s)) {
if (unghostify(self) < 0)
goto Done;
accessed(self);
}
result = PyObject_GenericGetAttr((PyObject *)self, name);
Done:
Py_XDECREF(name);
return result;
}
/* 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;
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
Per_setattro(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);
if (strncmp(s, "_v_", 3) != 0
&& self->state != cPersistent_CHANGED_STATE) {
if (changed(self) < 0)
goto Done;
}
}
result = PyObject_GenericSetAttr((PyObject *)self, name, v);
Done:
Py_XDECREF(name);
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)) {
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)
{
if (self->state < 0) {
Py_INCREF(Py_None);
return Py_None;
}
return PyBool_FromLong(self->state == cPersistent_CHANGED_STATE);
}
static int
Per_set_changed(cPersistentObject *self, PyObject *v)
{
int deactivate = 0, true;
if (!v) {
/* delattr is used to invalidate an object even if it has changed. */
if (self->state != cPersistent_GHOST_STATE)
self->state = cPersistent_UPTODATE_STATE;
deactivate = 1;
}
else if (v == Py_None)
deactivate = 1;
if (deactivate) {
PyObject *res, *meth;
meth = PyObject_GetAttr((PyObject *)self, py__p_deactivate);
if (meth == NULL)
return -1;
res = PyObject_CallObject(meth, NULL);
if (res)
Py_DECREF(res);
else {
/* an error occured in _p_deactivate().
It's not clear what we should do here. The code is
obviously ignoring the exception, but it shouldn't return
0 for a getattr and set an exception. The simplest change
is to clear the exception, but that simply masks the
error.
XXX We'll print an error to stderr just like exceptions in
__del__(). It would probably be better to log it but that
would be painful from C.
*/
PyErr_WriteUnraisable(meth);
}
Py_DECREF(meth);
return 0;
}
true = PyObject_IsTrue(v);
if (true == -1)
return -1;
else if (true)
return changed(self);
if (self->state >= 0)
self->state = cPersistent_UPTODATE_STATE;
return 0;
}
static PyObject *
Per_get_oid(cPersistentObject *self)
{
PyObject *oid = self->oid ? self->oid : Py_None;
Py_INCREF(oid);
return oid;
}
static int
Per_set_oid(cPersistentObject *self, PyObject *v)
{
if (self->cache) {
int result;
if (v == NULL) {
PyErr_SetString(PyExc_ValueError,
"can't delete _p_oid of cached object");
return -1;
}
if (PyObject_Cmp(self->oid, v, &result) < 0)
return -1;
if (result) {
PyErr_SetString(PyExc_ValueError,
"can not change _p_oid of cached object");
return -1;
}
}
Py_XDECREF(self->oid);
Py_XINCREF(v);
self->oid = v;
return 0;
}
static PyObject *
Per_get_jar(cPersistentObject *self)
{
PyObject *jar = self->jar ? self->jar : Py_None;
Py_INCREF(jar);
return jar;
}
static int
Per_set_jar(cPersistentObject *self, PyObject *v)
{
if (self->cache) {
int result;
if (v == NULL) {
PyErr_SetString(PyExc_ValueError,
"can't delete _p_jar of cached object");
return -1;
}
if (PyObject_Cmp(self->jar, v, &result) < 0)
return -1;
if (result) {
PyErr_SetString(PyExc_ValueError,
"can not change _p_jar of cached object");
return -1;
}
}
Py_XDECREF(self->jar);
Py_XINCREF(v);
self->jar = v;
return 0;
}
static PyObject *
Per_get_serial(cPersistentObject *self)
{
return PyString_FromStringAndSize(self->serial, 8);
}
static int
Per_set_serial(cPersistentObject *self, PyObject *v)
{
if (v) {
if (PyString_Check(v) && PyString_GET_SIZE(v) == 8)
memcpy(self->serial, PyString_AS_STRING(v), 8);
else {
PyErr_SetString(PyExc_ValueError,
"_p_serial must be an 8-character string");
return -1;
}
} else
memset(self->serial, 0, 8);
return 0;
}
static PyObject *
Per_get_mtime(cPersistentObject *self)
{
PyObject *t, *v;
if (unghostify(self) < 0)
return NULL;
accessed(self);
if (memcmp(self->serial, "\0\0\0\0\0\0\0\0", 8) == 0) {
Py_INCREF(Py_None);
return Py_None;
}
t = PyObject_CallFunction(TimeStamp, "s#", self->serial, 8);
if (!t)
return NULL;
v = PyObject_CallMethod(t, "timeTime", "");
Py_DECREF(t);
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 },
{"__setstate__", (PyCFunction)pickle___setstate__, METH_O,
pickle___setstate__doc},
{"__reduce__", (PyCFunction)pickle___reduce__, METH_NOARGS,
pickle___reduce__doc},
{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
used to tag the slots where such addresses appear; the module init
function must fill in the tagged slots at runtime. The argument is
for documentation -- the macro ignores it.
*/
#define DEFERRED_ADDRESS(ADDR) 0
static PyTypeObject Pertype = {
PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyPersist_MetaType))
0, /* ob_size */
"persistent.Persistent", /* tp_name */
sizeof(cPersistentObject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)Per_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
(getattrofunc)Per_getattro, /* tp_getattro */
(setattrofunc)Per_setattro, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
/* tp_flags */
0, /* tp_doc */
(traverseproc)Per_traverse, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Per_methods, /* tp_methods */
0, /* tp_members */
Per_getsets, /* tp_getset */
};
/* End of code for Persistent objects */
/* -------------------------------------------------------- */
typedef int (*intfunctionwithpythonarg)(PyObject*);
/* Load the object's state if necessary and become sticky */
static int
Per_setstate(cPersistentObject *self)
{
if (unghostify(self) < 0)
return -1;
self->state = cPersistent_STICKY_STATE;
return 0;
}
static PyObject *
simple_new(PyObject *self, PyObject *type_object)
{
return PyType_GenericNew((PyTypeObject *)type_object, NULL, NULL);
}
static PyMethodDef cPersistence_methods[] = {
{"simple_new", simple_new, METH_O,
"Create an object by simply calling a class's __new__ method without "
"arguments."},
{NULL, NULL}
};
static cPersistenceCAPIstruct
truecPersistenceCAPI = {
&Pertype,
(getattrofunc)Per_getattro, /*tp_getattr with object key*/
(setattrofunc)Per_setattro, /*tp_setattr with object key*/
changed,
accessed,
ghostify,
(intfunctionwithpythonarg)Per_setstate,
NULL /* The percachedel slot is initialized in cPickleCache.c when
the module is loaded. It uses a function in a different
shared library. */
};
void
initcPersistence(void)
{
PyObject *m, *s;
PyObject *copy_reg;
if (init_strings() < 0)
return;
m = Py_InitModule3("cPersistence", cPersistence_methods,
cPersistence_doc_string);
Pertype.ob_type = &PyType_Type;
Pertype.tp_new = PyType_GenericNew;
if (PyType_Ready(&Pertype) < 0)
return;
if (PyModule_AddObject(m, "Persistent", (PyObject *)&Pertype) < 0)
return;
cPersistenceCAPI = &truecPersistenceCAPI;
s = PyCObject_FromVoidPtr(cPersistenceCAPI, NULL);
if (!s)
return;
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;
copy_reg = PyImport_ImportModule("copy_reg");
if (!copy_reg)
return;
copy_reg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames");
if (!copy_reg_slotnames) {
Py_DECREF(copy_reg);
return;
}
__newobj__ = PyObject_GetAttrString(copy_reg, "__newobj__");
if (!__newobj__) {
Py_DECREF(copy_reg);
return;
}
if (!TimeStamp) {
m = PyImport_ImportModule("persistent.TimeStamp");
if (!m)
return;
TimeStamp = PyObject_GetAttrString(m, "TimeStamp");
Py_DECREF(m);
/* fall through to immediate return on error */
}
}
/*****************************************************************************
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
****************************************************************************/
#ifndef CPERSISTENCE_H
#define CPERSISTENCE_H
#include "Python.h"
#include "ring.h"
#define CACHE_HEAD \
PyObject_HEAD \
CPersistentRing ring_home; \
int non_ghost_count;
struct ccobject_head_struct;
typedef struct ccobject_head_struct PerCache;
/* How big is a persistent object?
12 PyGC_Head is two pointers and an int
8 PyObject_HEAD is an int and a pointer
12 jar, oid, cache pointers
8 ring struct
8 serialno
4 state + extra
(52) so far
4 dict ptr
4 weaklist ptr
-------------------------
64 only need 62, but obmalloc rounds up to multiple of eight
Even a ghost requires 64 bytes. It's possible to make a persistent
instance with slots and no dict, which changes the storage needed.
*/
#define cPersistent_HEAD \
PyObject_HEAD \
PyObject *jar; \
PyObject *oid; \
PerCache *cache; \
CPersistentRing ring; \
char serial[8]; \
signed char state; \
unsigned char reserved[3];
#define cPersistent_GHOST_STATE -1
#define cPersistent_UPTODATE_STATE 0
#define cPersistent_CHANGED_STATE 1
#define cPersistent_STICKY_STATE 2
typedef struct {
cPersistent_HEAD
} cPersistentObject;
typedef void (*percachedelfunc)(PerCache *, PyObject *);
typedef struct {
PyTypeObject *pertype;
getattrofunc getattro;
setattrofunc setattro;
int (*changed)(cPersistentObject*);
void (*accessed)(cPersistentObject*);
void (*ghostify)(cPersistentObject*);
int (*setstate)(PyObject*);
percachedelfunc percachedel;
} cPersistenceCAPIstruct;
#define cPersistenceType cPersistenceCAPI->pertype
#ifndef DONT_USE_CPERSISTENCECAPI
static cPersistenceCAPIstruct *cPersistenceCAPI;
#endif
#define cPersistanceModuleName "cPersistence"
#define PER_TypeCheck(O) PyObject_TypeCheck((O), cPersistenceCAPI->pertype)
#define PER_USE_OR_RETURN(O,R) {if((O)->state==cPersistent_GHOST_STATE && cPersistenceCAPI->setstate((PyObject*)(O)) < 0) return (R); else if ((O)->state==cPersistent_UPTODATE_STATE) (O)->state=cPersistent_STICKY_STATE;}
#define PER_CHANGED(O) (cPersistenceCAPI->changed((cPersistentObject*)(O)))
#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)) \
? (((O)->state==cPersistent_UPTODATE_STATE) \
? ((O)->state=cPersistent_STICKY_STATE) : 1) : 0)
#define PER_ACCESSED(O) (cPersistenceCAPI->accessed((cPersistentObject*)(O)))
#endif
/*****************************************************************************
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
****************************************************************************/
/*
Objects are stored under three different regimes:
Regime 1: Persistent Classes
Persistent Classes are part of ZClasses. They are stored in the
self->data dictionary, and are never garbage collected.
The klass_items() method returns a sequence of (oid,object) tuples for
every Persistent Class, which should make it possible to implement
garbage collection in Python if necessary.
Regime 2: Ghost Objects
There is no benefit to keeping a ghost object which has no external
references, therefore a weak reference scheme is used to ensure that
ghost objects are removed from memory as soon as possible, when the
last external reference is lost.
Ghost objects are stored in the self->data dictionary. Normally a
dictionary keeps a strong reference on its values, however this
reference count is 'stolen'.
This weak reference scheme leaves a dangling reference, in the
dictionary, when the last external reference is lost. To clean up this
dangling reference the persistent object dealloc function calls
self->cache->_oid_unreferenced(self->oid). The cache looks up the oid
in the dictionary, ensures it points to an object whose reference
count is zero, then removes it from the dictionary. Before removing
the object from the dictionary it must temporarily resurrect the
object in much the same way that class instances are resurrected
before their __del__ is called.
Since ghost objects are stored under a different regime to non-ghost
objects, an extra ghostify function in cPersistenceAPI replaces
self->state=GHOST_STATE assignments that were common in other
persistent classes (such as BTrees).
Regime 3: Non-Ghost Objects
Non-ghost objects are stored in two data structures: the dictionary
mapping oids to objects and a doubly-linked list that encodes the
order in which the objects were accessed. The dictionary reference is
borrowed, as it is for ghosts. The list reference is a new reference;
the list stores recently used objects, even if they are otherwise
unreferenced, to avoid loading the object from the database again.
The doubly-link-list nodes contain next and previous pointers linking
together the cache and all non-ghost persistent objects.
The node embedded in the cache is the home position. On every
attribute access a non-ghost object will relink itself just behind the
home position in the ring. Objects accessed least recently will
eventually find themselves positioned after the home position.
Occasionally other nodes are temporarily inserted in the ring as
position markers. The cache contains a ring_lock flag which must be
set and unset before and after doing so. Only if the flag is unset can
the cache assume that all nodes are either his own home node, or nodes
from persistent objects. This assumption is useful during the garbage
collection process.
The number of non-ghost objects is counted in self->non_ghost_count.
The garbage collection process consists of traversing the ring, and
deactivating (that is, turning into a ghost) every object until
self->non_ghost_count is down to the target size, or until it
reaches the home position again.
Note that objects in the sticky or changed states are still kept in
the ring, however they can not be deactivated. The garbage collection
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.92 2004/03/13 07:48:12 jeremy Exp $\n";
#define DONT_USE_CPERSISTENCECAPI
#include "cPersistence.h"
#include "structmember.h"
#include <time.h>
#include <stddef.h>
#undef Py_FindMethod
static PyObject *py__p_oid, *py_reload, *py__p_jar, *py__p_changed;
static cPersistenceCAPIstruct *capi;
/* This object is the pickle cache. The CACHE_HEAD macro guarantees
that layout of this struct is the same as the start of
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 */
/* Most of the time the ring contains only:
* many nodes corresponding to persistent objects
* one 'home' node from the cache.
In some cases it is handy to temporarily add other types
of node into the ring as placeholders. 'ring_lock' is a boolean
indicating that someone has already done this. Currently this
is only used by the garbage collection code. */
int ring_lock;
/* 'cache_drain_resistance' controls how quickly the cache size will drop
when it is smaller than the configured size. A value of zero means it will
not drop below the configured size (suitable for most caches). Otherwise,
it will remove cache_non_ghost_count/cache_drain_resistance items from
the cache every time (suitable for rarely used caches, such as those
associated with Zope versions. */
int cache_drain_resistance;
} ccobject;
static int cc_ass_sub(ccobject *self, PyObject *key, PyObject *v);
/* ---------------------------------------------------------------- */
#define OBJECT_FROM_RING(SELF, HERE) \
((cPersistentObject *)(((char *)here) - offsetof(cPersistentObject, ring)))
static int
scan_gc_items(ccobject *self, int target)
{
/* This function must only be called with the ring lock held,
because it places a non-object placeholder in the ring.
*/
cPersistentObject *object;
CPersistentRing placeholder;
CPersistentRing *here = self->ring_home.r_next;
static PyObject *_p_deactivate;
if (!_p_deactivate) {
_p_deactivate = PyString_InternFromString("_p_deactivate");
if (!_p_deactivate)
return -1;
}
/* Scan through the ring until we either find the ring_home (i.e. start
* of the ring, or we've ghosted enough objects to reach the target
* size.
*/
while (1) {
/* back to the home position. stop looking */
if (here == &self->ring_home)
return 0;
/* At this point we know that the ring only contains nodes
from persistent objects, plus our own home node. We know
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);
if (!object)
return -1;
/* we are small enough */
if (self->non_ghost_count <= target)
return 0;
else if (object->state == cPersistent_UPTODATE_STATE) {
PyObject *meth, *error;
/* deactivate it. This is the main memory saver. */
/* Add a placeholder; a dummy node in the ring. We need
to do this to mark our position in the ring. It is
possible that the PyObject_SetAttr() call below will
invoke an __setattr__() hook in Python. If it does,
another thread might run; if that thread accesses a
persistent object and moves it to the head of the ring,
it might cause the gc scan to start working from the
head of the list.
*/
placeholder.r_next = here->r_next;
placeholder.r_prev = here;
here->r_next->r_prev = &placeholder;
here->r_next = &placeholder;
/* Call _p_deactivate(), which may be overridden. */
meth = PyObject_GetAttr((PyObject *)object, _p_deactivate);
if (!meth)
return -1;
error = PyObject_CallObject(meth, NULL);
Py_DECREF(meth);
/* unlink the placeholder */
placeholder.r_next->r_prev = placeholder.r_prev;
placeholder.r_prev->r_next = placeholder.r_next;
here = placeholder.r_next;
if (!error)
return -1; /* problem */
Py_DECREF(error);
}
else
here = here->r_next;
}
}
static PyObject *
lockgc(ccobject *self, int target_size)
{
/* This is thread-safe because of the GIL, and there's nothing
* in between checking the ring_lock and acquiring it that calls back
* into Python.
*/
if (self->ring_lock) {
Py_INCREF(Py_None);
return Py_None;
}
self->ring_lock = 1;
if (scan_gc_items(self, target_size) < 0) {
self->ring_lock = 0;
return NULL;
}
self->ring_lock = 0;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
cc_incrgc(ccobject *self, PyObject *args)
{
int obsolete_arg = -999;
int starting_size = self->non_ghost_count;
int target_size = self->cache_size;
if (self->cache_drain_resistance >= 1) {
/* This cache will gradually drain down to a small size. Check
a (small) number of objects proportional to the current size */
int target_size_2 = (starting_size - 1
- starting_size / self->cache_drain_resistance);
if (target_size_2 < target_size)
target_size = target_size_2;
}
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 = -999;
/* XXX This should be deprecated */
if (!PyArg_ParseTuple(args, "|i:full_sweep", &dt))
return NULL;
if (dt == -999)
return lockgc(self, 0);
else
return cc_incrgc(self, args);
}
static PyObject *
cc_minimize(ccobject *self, PyObject *args)
{
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);
}
static void
_invalidate(ccobject *self, PyObject *key)
{
static PyObject *_p_invalidate;
PyObject *v = PyDict_GetItem(self->data, key);
if (!_p_invalidate) {
_p_invalidate = PyString_InternFromString("_p_invalidate");
if (!_p_invalidate) {
/* It doesn't make any sense to ignore this error, but
the caller ignores all errors.
*/
PyErr_Clear();
return;
}
}
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)
PyErr_Clear();
}
else {
v = PyObject_CallFunction(self->setklassstate, "O", v);
if (v)
Py_DECREF(v);
else
PyErr_Clear();
}
} else {
PyObject *meth, *err;
meth = PyObject_GetAttr(v, _p_invalidate);
if (!meth) {
PyErr_Clear();
return;
}
err = PyObject_CallObject(meth, NULL);
Py_DECREF(meth);
if (!err)
PyErr_Clear();
}
}
static PyObject *
cc_invalidate(ccobject *self, PyObject *inv)
{
PyObject *key, *v;
int i = 0;
if (PyDict_Check(inv)) {
while (PyDict_Next(inv, &i, &key, &v))
_invalidate(self, key);
PyDict_Clear(inv);
}
else {
if (PyString_Check(inv))
_invalidate(self, inv);
else {
int l;
l = PyObject_Length(inv);
if (l < 0)
return NULL;
for (i=l; --i >= 0; ) {
key = PySequence_GetItem(inv, i);
if (!key)
return NULL;
_invalidate(self, key);
Py_DECREF(key);
}
/* XXX Do we really want to modify the input? */
PySequence_DelSlice(inv, 0, l);
}
}
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
cc_get(ccobject *self, PyObject *args)
{
PyObject *r, *key, *d = NULL;
if (!PyArg_ParseTuple(args, "O|O:get", &key, &d))
return NULL;
r = PyDict_GetItem(self->data, key);
if (!r) {
if (d)
r = d;
else
r = Py_None;
}
Py_INCREF(r);
return r;
}
static PyObject *
cc_items(ccobject *self)
{
return PyObject_CallMethod(self->data, "items", "");
}
static PyObject *
cc_klass_items(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(PyType_Check(v)) {
v = Py_BuildValue("OO", k, v);
if (v == NULL) {
Py_DECREF(l);
return NULL;
}
if (PyList_Append(l, v) < 0) {
Py_DECREF(v);
Py_DECREF(l);
return NULL;
}
Py_DECREF(v);
}
}
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)
{
PyObject *l;
CPersistentRing *here;
if (self->ring_lock) {
/* When the ring lock is held, we have no way of know which
ring nodes belong to persistent objects, and which a
placeholders. */
PyErr_SetString(PyExc_ValueError,
".lru_items() is unavailable during garbage collection");
return NULL;
}
l = PyList_New(0);
if (l == NULL)
return NULL;
here = self->ring_home.r_next;
while (here != &self->ring_home) {
PyObject *v;
cPersistentObject *object = OBJECT_FROM_RING(self, here);
if (object == NULL) {
Py_DECREF(l);
return NULL;
}
v = Py_BuildValue("OO", object->oid, object);
if (v == NULL) {
Py_DECREF(l);
return NULL;
}
if (PyList_Append(l, v) < 0) {
Py_DECREF(v);
Py_DECREF(l);
return NULL;
}
Py_DECREF(v);
here = here->r_next;
}
return l;
}
static void
cc_oid_unreferenced(ccobject *self, PyObject *oid)
{
/* This is called by the persistent object deallocation function
when the reference count on a persistent object reaches
zero. We need to fix up our dictionary; its reference is now
dangling because we stole its reference count. Be careful to
not release the global interpreter lock until this is
complete. */
PyObject *v;
/* If the cache has been cleared by GC, data will be NULL. */
if (!self->data)
return;
v = PyDict_GetItem(self->data, oid);
assert(v);
assert(v->ob_refcnt == 0);
/* Need to be very hairy here because a dictionary is about
to decref an already deleted object.
*/
#ifdef Py_TRACE_REFS
/* This is called from the deallocation function after the
interpreter has untracked the reference. Track it again.
*/
_Py_NewReference(v);
/* Don't increment total refcount as a result of the
shenanigans played in this function. The _Py_NewReference()
call above creates artificial references to v.
*/
_Py_RefTotal--;
assert(v->ob_type);
#else
Py_INCREF(v);
#endif
assert(v->ob_refcnt == 1);
/* Incremement the refcount again, because delitem is going to
DECREF it. If it's refcount reached zero again, we'd call back to
the dealloc function that called us.
*/
Py_INCREF(v);
/* XXX Should we call _Py_ForgetReference() on error exit? */
if (PyDict_DelItem(self->data, oid) < 0)
return;
Py_DECREF((ccobject *)((cPersistentObject *)v)->cache);
((cPersistentObject *)v)->cache = NULL;
assert(v->ob_refcnt == 1);
/* Undo the temporary resurrection.
Don't DECREF the object, because this function is called from
the object's dealloc function. If the refcnt reaches zero, it
will all be invoked recursively.
*/
_Py_ForgetReference(v);
}
static PyObject *
cc_ringlen(ccobject *self)
{
CPersistentRing *here;
int c = 0;
for (here = self->ring_home.r_next; here != &self->ring_home;
here = here->r_next)
c++;
return PyInt_FromLong(c);
}
static struct PyMethodDef cc_methods[] = {
{"items", (PyCFunction)cc_items, METH_NOARGS,
"Return list of oid, object pairs for all items in cache."},
{"lru_items", (PyCFunction)cc_lru_items, METH_NOARGS,
"List (oid, object) pairs from the lru list, as 2-tuples."},
{"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() -- 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() -- 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,
"invalidate(oids) -- invalidate one, many, or all ids"},
{"get", (PyCFunction)cc_get, METH_VARARGS,
"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 */
};
static int
cc_init(ccobject *self, PyObject *args, PyObject *kwds)
{
int cache_size = 100;
PyObject *jar;
if (!PyArg_ParseTuple(args, "O|i", &jar, &cache_size))
return -1;
self->setklassstate = self->jar = NULL;
self->data = PyDict_New();
if (self->data == NULL) {
Py_DECREF(self);
return -1;
}
/* Untrack the dict mapping oids to objects.
The dict contains uncounted references to ghost objects, so it
isn't safe for GC to visit it. If GC finds an object with more
referents that refcounts, it will die with an assertion failure.
When the cache participates in GC, it will need to traverse the
objects in the doubly-linked list, which will account for all the
non-ghost objects.
*/
PyObject_GC_UnTrack((void *)self->data);
self->setklassstate = PyObject_GetAttrString(jar, "setklassstate");
if (self->setklassstate == NULL) {
Py_DECREF(self);
return -1;
}
self->jar = jar;
Py_INCREF(jar);
self->cache_size = cache_size;
self->non_ghost_count = 0;
self->klass_count = 0;
self->cache_drain_resistance = 0;
self->ring_lock = 0;
self->ring_home.r_next = &self->ring_home;
self->ring_home.r_prev = &self->ring_home;
return 0;
}
static void
cc_dealloc(ccobject *self)
{
Py_XDECREF(self->data);
Py_XDECREF(self->jar);
Py_XDECREF(self->setklassstate);
PyObject_GC_Del(self);
}
static int
cc_clear(ccobject *self)
{
int pos = 0;
PyObject *k, *v;
/* Clearing the cache is delicate.
A non-ghost object will show up in the ring and in the dict. If
we deallocating the dict before clearing the ring, the GC will
decref each object in the dict. Since the dict references are
uncounted, this will lead to objects having negative refcounts.
Freeing the non-ghost objects should eliminate many objects from
the cache, but there may still be ghost objects left. It's
not safe to decref the dict until it's empty, so we need to manually
clear those out of the dict, too. We accomplish that by replacing
all the ghost objects with None.
*/
/* We don't need to lock the ring, because the cache is unreachable.
It should be impossible for anyone to be modifying the cache.
*/
assert(! self->ring_lock);
while (self->ring_home.r_next != &self->ring_home) {
CPersistentRing *here = self->ring_home.r_next;
cPersistentObject *o = OBJECT_FROM_RING(self, here);
if (o->cache) {
Py_INCREF(o); /* account for uncounted reference */
if (PyDict_DelItem(self->data, o->oid) < 0)
return -1;
}
o->cache = NULL;
Py_DECREF(self);
self->ring_home.r_next = here->r_next;
o->ring.r_prev = NULL;
o->ring.r_next = NULL;
Py_DECREF(o);
here = here->r_next;
}
Py_XDECREF(self->jar);
Py_XDECREF(self->setklassstate);
while (PyDict_Next(self->data, &pos, &k, &v)) {
Py_INCREF(v);
if (PyDict_SetItem(self->data, k, Py_None) < 0)
return -1;
}
Py_XDECREF(self->data);
self->data = NULL;
self->jar = NULL;
self->setklassstate = NULL;
return 0;
}
static int
cc_traverse(ccobject *self, visitproc visit, void *arg)
{
int err;
CPersistentRing *here;
/* If we're in the midst of cleaning up old objects, the ring contains
* assorted junk we must not pass on to the visit() callback. This
* should be rare (our cleanup code would need to have called back
* into Python, which in turn triggered Python's gc). When it happens,
* simply don't chase any pointers. The cache will appear to be a
* source of external references then, and at worst we miss cleaning
* up a dead cycle until the next time Python's gc runs.
*/
if (self->ring_lock)
return 0;
#define VISIT(SLOT) \
if (SLOT) { \
err = visit((PyObject *)(SLOT), arg); \
if (err) \
return err; \
}
VISIT(self->jar);
VISIT(self->setklassstate);
here = self->ring_home.r_next;
/* It is possible that an object is traversed after it is cleared.
In that case, there is no ring.
*/
if (!here)
return 0;
while (here != &self->ring_home) {
cPersistentObject *o = OBJECT_FROM_RING(self, here);
VISIT(o);
here = here->r_next;
}
#undef VISIT
return 0;
}
static int
cc_length(ccobject *self)
{
return PyObject_Length(self->data);
}
static PyObject *
cc_subscript(ccobject *self, PyObject *key)
{
PyObject *r;
r = PyDict_GetItem(self->data, key);
if (r == NULL) {
PyErr_SetObject(PyExc_KeyError, key);
return NULL;
}
Py_INCREF(r);
return r;
}
static int
cc_add_item(ccobject *self, PyObject *key, PyObject *v)
{
int result;
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. */
}
else if (v->ob_type->tp_basicsize < sizeof(cPersistentObject)) {
/* If it's not an instance of a persistent class, (ie Python
classes that derive from persistent.Persistent, BTrees,
etc), report an error.
XXX Need a better test.
*/
PyErr_SetString(PyExc_TypeError,
"Cache values must be persistent objects.");
return -1;
}
/* Can't access v->oid directly because the object might be a
* persistent class.
*/
oid = PyObject_GetAttr(v, py__p_oid);
if (oid == NULL)
return -1;
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.
*/
result = PyObject_Compare(key, oid);
if (PyErr_Occurred()) {
Py_DECREF(oid);
return -1;
}
Py_DECREF(oid);
if (result) {
PyErr_SetString(PyExc_ValueError, "Cache key does not match oid");
return -1;
}
/* useful sanity check, but not strictly an invariant of this class */
jar = PyObject_GetAttr(v, py__p_jar);
if (jar == NULL)
return -1;
if (jar==Py_None) {
Py_DECREF(jar);
PyErr_SetString(PyExc_ValueError,
"Cached object jar missing");
return -1;
}
Py_DECREF(jar);
object_again = PyDict_GetItem(self->data, key);
if (object_again) {
if (object_again != v) {
PyErr_SetString(PyExc_ValueError,
"Can not re-register object under a different oid");
return -1;
} else {
/* re-register under the same oid - no work needed */
return 0;
}
}
if (PyType_Check(v)) {
if (PyDict_SetItem(self->data, key, v) < 0)
return -1;
self->klass_count++;
return 0;
} else {
PerCache *cache = ((cPersistentObject *)v)->cache;
if (cache) {
if (cache != (PerCache *)self)
/* This object is already in a different cache. */
PyErr_SetString(PyExc_ValueError,
"Cache values may only be in one cache.");
return -1;
}
/* else:
This object is already one of ours, which is ok. It
would be very strange if someone was trying to register
the same object under a different key.
*/
}
if (PyDict_SetItem(self->data, key, v) < 0)
return -1;
/* the dict should have a borrowed reference */
Py_DECREF(v);
p = (cPersistentObject *)v;
Py_INCREF(self);
p->cache = (PerCache *)self;
if (p->state >= 0) {
/* insert this non-ghost object into the ring just
behind the home position. */
self->non_ghost_count++;
ring_add(&self->ring_home, &p->ring);
/* this list should have a new reference to the object */
Py_INCREF(v);
}
return 0;
}
static int
cc_del_item(ccobject *self, PyObject *key)
{
PyObject *v;
cPersistentObject *p;
/* unlink this item from the ring */
v = PyDict_GetItem(self->data, key);
if (v == NULL) {
PyErr_SetObject(PyExc_KeyError, key);
return -1;
}
if (PyType_Check(v)) {
self->klass_count--;
} else {
p = (cPersistentObject *)v;
if (p->state >= 0) {
self->non_ghost_count--;
ring_del(&p->ring);
/* The DelItem below will account for the reference
held by the list. */
} else {
/* This is a ghost object, so we haven't kept a reference
count on it. For it have stayed alive this long
someone else must be keeping a reference to
it. Therefore we need to temporarily give it back a
reference count before calling DelItem below */
Py_INCREF(v);
}
Py_DECREF((PyObject *)p->cache);
p->cache = NULL;
}
if (PyDict_DelItem(self->data, key) < 0) {
PyErr_SetString(PyExc_RuntimeError,
"unexpectedly couldn't remove key in cc_ass_sub");
return -1;
}
return 0;
}
static int
cc_ass_sub(ccobject *self, PyObject *key, PyObject *v)
{
if (!PyString_Check(key)) {
PyErr_Format(PyExc_TypeError,
"cPickleCache key must be a string, not a %s",
key->ob_type->tp_name);
return -1;
}
if (v)
return cc_add_item(self, key, v);
else
return cc_del_item(self, key);
}
static PyMappingMethods cc_as_mapping = {
(inquiry)cc_length, /*mp_length*/
(binaryfunc)cc_subscript, /*mp_subscript*/
(objobjargproc)cc_ass_sub, /*mp_ass_subscript*/
};
static PyObject *
cc_cache_data(ccobject *self, void *context)
{
return PyDict_Copy(self->data);
}
static PyGetSetDef cc_getsets[] = {
{"cache_data", (getter)cc_cache_data},
{NULL}
};
static PyMemberDef cc_members[] = {
{"cache_size", T_INT, offsetof(ccobject, cache_size)},
{"cache_drain_resistance", T_INT,
offsetof(ccobject, cache_drain_resistance)},
{"cache_non_ghost_count", T_INT, offsetof(ccobject, non_ghost_count), RO},
{"cache_klass_count", T_INT, offsetof(ccobject, klass_count), RO},
{NULL}
};
/* 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
used to tag the slots where such addresses appear; the module init
function must fill in the tagged slots at runtime. The argument is
for documentation -- the macro ignores it.
*/
#define DEFERRED_ADDRESS(ADDR) 0
static PyTypeObject Cctype = {
PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type))
0, /* ob_size */
"persistent.PickleCache", /* tp_name */
sizeof(ccobject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)cc_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
&cc_as_mapping, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
/* tp_flags */
0, /* tp_doc */
(traverseproc)cc_traverse, /* tp_traverse */
(inquiry)cc_clear, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
cc_methods, /* tp_methods */
cc_members, /* tp_members */
cc_getsets, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)cc_init, /* tp_init */
};
void
initcPickleCache(void)
{
PyObject *m;
Cctype.ob_type = &PyType_Type;
Cctype.tp_new = &PyType_GenericNew;
if (PyType_Ready(&Cctype) < 0) {
return;
}
m = Py_InitModule3("cPickleCache", NULL, cPickleCache_doc_string);
capi = (cPersistenceCAPIstruct *)PyCObject_Import(
"persistent.cPersistence", "CAPI");
if (!capi)
return;
capi->percachedel = (percachedelfunc)cc_oid_unreferenced;
py_reload = PyString_InternFromString("reload");
py__p_jar = PyString_InternFromString("_p_jar");
py__p_changed = PyString_InternFromString("_p_changed");
py__p_oid = PyString_InternFromString("_p_oid");
if (PyModule_AddStringConstant(m, "cache_variant", "stiff/c") < 0)
return;
/* This leaks a reference to Cctype, but it doesn't matter. */
if (PyModule_AddObject(m, "PickleCache", (PyObject *)&Cctype) < 0)
return;
}
##############################################################################
#
# 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.
#
##############################################################################
"""Persistence Interfaces
$Id: interfaces.py,v 1.4 2004/04/19 21:19:09 tim_one Exp $
"""
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
"""
##############################################################################
#
# 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 list.
$Id: list.py,v 1.7 2004/02/19 18:13:35 jeremy Exp $"""
__version__='$Revision: 1.7 $'[11:-2]
import persistent
from UserList import UserList
class PersistentList(UserList, persistent.Persistent):
__super_setitem = UserList.__setitem__
__super_delitem = UserList.__delitem__
__super_setslice = UserList.__setslice__
__super_delslice = UserList.__delslice__
__super_iadd = UserList.__iadd__
__super_imul = UserList.__imul__
__super_append = UserList.append
__super_insert = UserList.insert
__super_pop = UserList.pop
__super_remove = UserList.remove
__super_reverse = UserList.reverse
__super_sort = UserList.sort
__super_extend = UserList.extend
def __setitem__(self, i, item):
self.__super_setitem(i, item)
self._p_changed = 1
def __delitem__(self, i):
self.__super_delitem(i)
self._p_changed = 1
def __setslice__(self, i, j, other):
self.__super_setslice(i, j, other)
self._p_changed = 1
def __delslice__(self, i, j):
self.__super_delslice(i, j)
self._p_changed = 1
def __iadd__(self, other):
L = self.__super_iadd(other)
self._p_changed = 1
return L
def __imul__(self, n):
L = self.__super_imul(n)
self._p_changed = 1
return L
def append(self, item):
self.__super_append(item)
self._p_changed = 1
def insert(self, i, item):
self.__super_insert(i, item)
self._p_changed = 1
def pop(self, i=-1):
rtn = self.__super_pop(i)
self._p_changed = 1
return rtn
def remove(self, item):
self.__super_remove(item)
self._p_changed = 1
def reverse(self):
self.__super_reverse()
self._p_changed = 1
def sort(self, *args):
self.__super_sort(*args)
self._p_changed = 1
def extend(self, other):
self.__super_extend(other)
self._p_changed = 1
# This works around a bug in Python 2.1.x (up to 2.1.2 at least) where the
# __cmp__ bogusly raises a RuntimeError, and because this is an extension
# class, none of the rich comparison stuff works anyway.
def __cmp__(self, other):
return cmp(self.data, self._UserList__cast(other))
##############################################################################
#
# 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 base types
$Id: mapping.py,v 1.22 2003/11/28 16:44:55 jim Exp $"""
__version__='$Revision: 1.22 $'[11:-2]
import persistent
from UserDict import UserDict
class PersistentMapping(UserDict, persistent.Persistent):
"""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.
A subclass of PersistentMapping or any code that adds new
attributes should not create an attribute named _container. This
is reserved for backwards compatibility reasons.
"""
# UserDict provides all of the mapping behavior. The
# PersistentMapping 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 = UserDict.__delitem__
__super_setitem = UserDict.__setitem__
__super_clear = UserDict.clear
__super_update = UserDict.update
__super_setdefault = UserDict.setdefault
def __delitem__(self, key):
self.__super_delitem(key)
self._p_changed = 1
def __setitem__(self, key, v):
self.__super_setitem(key, v)
self._p_changed = 1
def clear(self):
self.__super_clear()
self._p_changed = 1
def update(self, b):
self.__super_update(b)
self._p_changed = 1
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 = 1
return self.__super_setdefault(key, failobj)
try:
__super_popitem = UserDict.popitem
except AttributeError:
pass
else:
def popitem(self):
self._p_changed = 1
return self.__super_popitem()
# If the internal representation of PersistentMapping changes,
# it causes compatibility problems for pickles generated by
# different versions of the code. Compatibility works in both
# directions, because an application may want to share a database
# between applications using different versions of the code.
# Effectively, the original rep is part of the "API." To provide
# full compatibility, the getstate and setstate must read and
# right objects using the old rep.
# As a result, the PersistentMapping must save and restore the
# actual internal dictionary using the name _container.
def __getstate__(self):
state = {}
state.update(self.__dict__)
state['_container'] = state['data']
del state['data']
return state
def __setstate__(self, state):
if state.has_key('_container'):
self.data = state['_container']
del state['_container']
elif not state.has_key('data'):
self.data = {}
self.__dict__.update(state)
/*****************************************************************************
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
****************************************************************************/
#define RING_C "$Id: ring.c,v 1.3 2004/05/03 20:15:45 spascoe Exp $\n"
/* Support routines for the doubly-linked list of cached objects.
The cache stores a doubly-linked list of persistent objects, with
space for the pointers allocated in the objects themselves. The cache
stores the distinguished head of the list, which is not a valid
persistent object.
The next pointers traverse the ring in order starting with the least
recently used object. The prev pointers traverse the ring in order
starting with the most recently used object.
*/
#include "Python.h"
#include "ring.h"
void
ring_add(CPersistentRing *ring, CPersistentRing *elt)
{
assert(!elt->r_next);
elt->r_next = ring;
elt->r_prev = ring->r_prev;
ring->r_prev->r_next = elt;
ring->r_prev = elt;
}
void
ring_del(CPersistentRing *elt)
{
elt->r_next->r_prev = elt->r_prev;
elt->r_prev->r_next = elt->r_next;
elt->r_next = NULL;
elt->r_prev = NULL;
}
void
ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt)
{
elt->r_prev->r_next = elt->r_next;
elt->r_next->r_prev = elt->r_prev;
elt->r_next = ring;
elt->r_prev = ring->r_prev;
ring->r_prev->r_next = elt;
ring->r_prev = elt;
}
/*****************************************************************************
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
****************************************************************************/
/* Support routines for the doubly-linked list of cached objects.
The cache stores a headed, doubly-linked, circular list of persistent
objects, with space for the pointers allocated in the objects themselves.
The cache stores the distinguished head of the list, which is not a valid
persistent object. The other list members are non-ghost persistent
objects, linked in LRU (least-recently used) order.
The r_next pointers traverse the ring starting with the least recently used
object. The r_prev pointers traverse the ring starting with the most
recently used object.
Obscure: While each object is pointed at twice by list pointers (once by
its predecessor's r_next, again by its successor's r_prev), the refcount
on the object is bumped only by 1. This leads to some possibly surprising
sequences of incref and decref code. Note that since the refcount is
bumped at least once, the list does hold a strong reference to each
object in it.
*/
typedef struct CPersistentRing_struct
{
struct CPersistentRing_struct *r_prev;
struct CPersistentRing_struct *r_next;
} CPersistentRing;
/* The list operations here take constant time independent of the
* number of objects in the list:
*/
/* Add elt as the most recently used object. elt must not already be
* in the list, although this isn't checked.
*/
void ring_add(CPersistentRing *ring, CPersistentRing *elt);
/* Remove elt from the list. elt must already be in the list, although
* this isn't checked.
*/
void ring_del(CPersistentRing *elt);
/* elt must already be in the list, although this isn't checked. It's
* unlinked from its current position, and relinked into the list as the
* most recently used object (which is arguably the tail of the list
* instead of the head -- but the name of this function could be argued
* either way). This is equivalent to
*
* ring_del(elt);
* ring_add(ring, elt);
*
* but may be a little quicker.
*/
void ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt);
Tests for persistent.Persistent
===============================
This document is an extended doc test that covers the basics of the
Persistent base class. The test expects a class named 'P' to be
provided in its globals. The P class implements the Persistent
interface.
Test framework
--------------
The class P needs to behave like ExampleP. (Note that the code below
is *not* part of the tests.)
class ExampleP(Persistent):
def __init__(self):
self.x = 0
def inc(self):
self.x += 1
The tests use stub data managers. A data manager is responsible for
loading and storing the state of a persistent object. It's stored in
the _p_jar attribute of a persistent object.
>>> class DM:
... def __init__(self):
... 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
>>> from persistent import Persistent
Test Persistent without Data Manager
------------------------------------
First do some simple tests of a Persistent instance that does not have
a data manager (_p_jar).
>>> p = P()
>>> p.x
0
>>> p._p_changed
False
>>> p._p_state
0
>>> p._p_jar
>>> p._p_oid
Verify that modifications have no effect on _p_state of _p_changed.
>>> p.inc()
>>> p.inc()
>>> p.x
2
>>> p._p_changed
False
>>> p._p_state
0
Try all sorts of different ways to change the object's state.
>>> p._p_deactivate()
>>> p._p_state
0
>>> p._p_changed = True
>>> p._p_state
0
>>> del p._p_changed
>>> p._p_changed
False
>>> p._p_state
0
>>> p.x
2
Test Persistent with Data Manager
---------------------------------
Next try some tests of an object with a data manager. The DM class is
a simple testing stub.
>>> p = P()
>>> dm = DM()
>>> p._p_oid = "00000012"
>>> p._p_jar = dm
>>> p._p_changed
0
>>> dm.called
0
Modifying the object marks it as changed and registers it with the
data manager. Subsequent modifications don't have additional
side-effects.
>>> p.inc()
>>> p._p_changed
1
>>> dm.called
1
>>> p.inc()
>>> p._p_changed
1
>>> dm.called
1
It's not possible to deactivate a modified object.
>>> p._p_deactivate()
>>> p._p_changed
1
It is possible to invalidate it. That's the key difference
between deactivation and invalidation.
>>> p._p_invalidate()
>>> p._p_state
-1
Now that the object is a ghost, any attempt to modify it will
require that it be unghosted first. The test data manager
has the odd property that it sets the object's 'x' attribute
to 42 when it is unghosted.
>>> p.inc()
>>> p.x
43
>>> dm.called
2
You can manually reset the changed field to False, although
it's not clear why you would want to do that. The object
changes to the UPTODATE state but retains its modifications.
>>> p._p_changed = False
>>> p._p_state
0
>>> p._p_changed
False
>>> p.x
43
>>> p.inc()
>>> p._p_changed
True
>>> dm.called
3
__getstate__() and __setstate__()
---------------------------------
The next several tests cover the __getstate__() and __setstate__()
implementations.
>>> p = P()
>>> state = p.__getstate__()
>>> isinstance(state, dict)
True
>>> state['x']
0
>>> p._p_state
0
Calling setstate always leaves the object in the uptodate state?
(I'm not entirely clear on this one.)
>>> p.__setstate__({'x': 5})
>>> p._p_state
0
Assigning to a volatile attribute has no effect on the object state.
>>> p._v_foo = 2
>>> p.__getstate__()
{'x': 5}
>>> p._p_state
0
The _p_serial attribute is not affected by calling setstate.
>>> p._p_serial = "00000012"
>>> p.__setstate__(p.__getstate__())
>>> p._p_serial
'00000012'
Change Ghost test
-----------------
If an object is a ghost and it's _p_changed is set to True, it should
have no effect.
>>> p = P()
>>> p._p_jar = DM()
>>> p._p_oid = 1
>>> p._p_deactivate()
>>> p._p_changed
>>> p._p_state
-1
>>> p._p_changed = True
>>> p._p_changed
>>> p._p_state
-1
Activate, deactivate, and invalidate
------------------------------------
Some of these tests are redundant, but are included to make sure there
are explicit and simple tests of _p_activate(), _p_deactivate(), and
_p_invalidate().
>>> p = P()
>>> p._p_oid = 1
>>> p._p_jar = DM()
>>> p._p_deactivate()
>>> p._p_state
-1
>>> p._p_activate()
>>> p._p_state
0
>>> p.x
42
>>> p.inc()
>>> p.x
43
>>> p._p_state
1
>>> p._p_invalidate()
>>> p._p_state
-1
>>> p.x
42
Test failures
-------------
The following tests cover various errors cases.
When an object is modified, it registers with its data manager. If
that registration fails, the exception is propagated and the object
stays in the up-to-date state. It shouldn't change to the modified
state, because it won't be saved when the transaction commits.
>>> p = P()
>>> p._p_oid = 1
>>> p._p_jar = BrokenDM()
>>> p._p_state
0
>>> p._p_jar.called
0
>>> p._p_changed = 1
Traceback (most recent call last):
...
NotImplementedError
>>> p._p_jar.called
1
>>> p._p_state
0
Make sure that exceptions that occur inside the data manager's
setstate() method propagate out to the caller.
>>> p = P()
>>> p._p_oid = 1
>>> p._p_jar = BrokenDM()
>>> p._p_deactivate()
>>> p._p_state
-1
>>> p._p_activate()
Traceback (most recent call last):
...
NotImplementedError
>>> p._p_state
-1
Special test to cover layout of __dict__
----------------------------------------
We once had a bug in the Persistent class that calculated an incorrect
offset for the __dict__ attribute. It assigned __dict__ and _p_jar to
the same location in memory. This is a simple test to make sure they
have different locations.
>>> p = P()
>>> p.inc()
>>> p.inc()
>>> 'x' in p.__dict__
True
>>> p._p_jar
Inheritance and metaclasses
---------------------------
Simple tests to make sure it's possible to inherit from the Persistent
base class multiple times. There used to be metaclasses involved in
Persistent that probably made this a more interesting test.
>>> class A(Persistent):
... pass
>>> class B(Persistent):
... pass
>>> class C(A, B):
... pass
>>> class D(object):
... pass
>>> class E(D, B):
... pass
>>> a = A()
>>> b = B()
>>> c = C()
>>> d = D()
>>> e = E()
Also make sure that it's possible to define Persistent classes that
have a custom metaclass.
>>> class alternateMeta(type):
... type
>>> class alternate(object):
... __metaclass__ = alternateMeta
>>> class mixedMeta(alternateMeta, type):
... pass
>>> class mixed(alternate, Persistent):
... pass
>>> class mixed(Persistent, alternate):
... pass
Basic type structure
--------------------
>>> Persistent.__dictoffset__
0
>>> Persistent.__weakrefoffset__
0
>>> Persistent.__basicsize__ > object.__basicsize__
True
>>> P.__dictoffset__ > 0
True
>>> P.__weakrefoffset__ > 0
True
>>> P.__dictoffset__ < P.__weakrefoffset__
True
>>> P.__basicsize__ > Persistent.__basicsize__
True
Slots
-----
These are some simple tests of classes that have an __slots__
attribute. Some of the classes should have slots, others shouldn't.
>>> class noDict(object):
... __slots__ = ['foo']
>>> class p_noDict(Persistent):
... __slots__ = ['foo']
>>> class p_shouldHaveDict(p_noDict):
... pass
>>> p_noDict.__dictoffset__
0
>>> x = p_noDict()
>>> x.foo = 1
>>> x.foo
1
>>> x.bar = 1
Traceback (most recent call last):
...
AttributeError: 'p_noDict' object has no attribute 'bar'
>>> x._v_bar = 1
Traceback (most recent call last):
...
AttributeError: 'p_noDict' object has no attribute '_v_bar'
>>> x.__dict__
Traceback (most recent call last):
...
AttributeError: 'p_noDict' object has no attribute '__dict__'
The various _p_ attributes are unaffected by slots.
>>> p._p_oid
>>> p._p_jar
>>> p._p_state
0
If the most-derived class does not specify
>>> p_shouldHaveDict.__dictoffset__ > 0
True
>>> x = p_shouldHaveDict()
>>> isinstance(x.__dict__, dict)
True
Pickling
--------
There's actually a substantial effort involved in making subclasses of
Persistent work with plain-old pickle. The ZODB serialization layer
never calls pickle on an object; it pickles the object's class
description and its state as two separate pickles.
>>> import pickle
>>> p = P()
>>> p.inc()
>>> p2 = pickle.loads(pickle.dumps(p))
>>> p2.__class__ is P
True
>>> p2.x == p.x
True
We should also test that pickle works with custom getstate and
setstate. Perhaps even reduce. The problem is that pickling depends
on finding the class in a particular module, and classes defined here
won't appear in any module. We could require each user of the tests
to define a base class, but that might be tedious.
Interfaces
----------
Some versions of Zope and ZODB have the zope.interfaces package
available. If it is available, then persistent will be associated
with several interfaces. It's hard to write a doctest test that runs
the tests only if zope.interface is available, so this test looks a
little unusual. One problem is that the assert statements won't do
anything if you run with -O.
>>> try:
... import zope.interface
... except ImportError:
... pass
... else:
... from persistent.interfaces import IPersistent
... assert IPersistent.implementedBy(Persistent)
... p = Persistent()
... assert IPersistent.providedBy(p)
... assert IPersistent.implementedBy(P)
... p = P()
... assert IPersistent.providedBy(p)
##############################################################################
#
# 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.implementedBy(Persistent),
"%s does not implement IPersistent" % Persistent)
p = Persistent()
self.assert_(IPersistent.providedBy(p),
"%s does not implement IPersistent" % p)
self.assert_(IPersistent.implementedBy(P),
"%s does not implement IPersistent" % P)
p = self.klass()
self.assert_(IPersistent.providedBy(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__)
# XXX Can anyone defend/explain the test below? The tests classes defined here
# don't define __call__, so this weird test will always pass, but to what
# end? What the heck is the point. If a klass is given that happens
# to define __call__, the test *may* mysteriously fail. Who cares?
## 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
#############################################################################
#
# 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.
#
##############################################################################
import pickle
import time
import unittest
from persistent import Persistent, GHOST, UPTODATE, CHANGED, STICKY
from persistent.cPickleCache import PickleCache
from persistent.TimeStamp import TimeStamp
from ZODB.utils import p64
class Jar(object):
"""Testing stub for _p_jar attribute."""
def __init__(self):
self.cache = PickleCache(self)
self.oid = 1
self.registered = {}
def add(self, obj):
obj._p_oid = p64(self.oid)
self.oid += 1
obj._p_jar = self
self.cache[obj._p_oid] = obj
def close(self):
pass
# the following methods must be implemented to be a jar
def setklassstate(self):
# I don't know what this method does, but the pickle cache
# constructor calls it.
pass
def register(self, obj):
self.registered[obj] = 1
def setstate(self, obj):
# Trivial setstate() implementation that just re-initializes
# the object. This isn't what setstate() is supposed to do,
# but it suffices for the tests.
obj.__class__.__init__(obj)
class P(Persistent):
pass
class H1(Persistent):
def __init__(self):
self.n = 0
def __getattr__(self, attr):
self.n += 1
return self.n
class H2(Persistent):
def __init__(self):
self.n = 0
def __getattribute__(self, attr):
supergetattr = super(H2, self).__getattribute__
try:
return supergetattr(attr)
except AttributeError:
n = supergetattr("n")
self.n = n + 1
return n + 1
class PersistenceTest(unittest.TestCase):
def setUp(self):
self.jar = Jar()
def tearDown(self):
self.jar.close()
def testOidAndJarAttrs(self):
obj = P()
self.assertEqual(obj._p_oid, None)
obj._p_oid = 12
self.assertEqual(obj._p_oid, 12)
del obj._p_oid
self.jar.add(obj)
# Can't change oid of cache object.
def deloid():
del obj._p_oid
self.assertRaises(ValueError, deloid)
def setoid():
obj._p_oid = 12
self.assertRaises(ValueError, setoid)
def deloid():
del obj._p_jar
self.assertRaises(ValueError, deloid)
def setoid():
obj._p_jar = 12
self.assertRaises(ValueError, setoid)
def testChangedAndState(self):
obj = P()
self.jar.add(obj)
# The value returned for _p_changed can be one of:
# 0 -- it is not changed
# 1 -- it is changed
# None -- it is a ghost
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)
obj._p_changed = 1
# You can transition directly from modified to ghost if
# 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
obj = P()
self.assertEqual(obj._p_serial, noserial)
def set(val):
obj._p_serial = val
self.assertRaises(ValueError, set, 1)
self.assertRaises(ValueError, set, "0123")
self.assertRaises(ValueError, set, "012345678")
self.assertRaises(ValueError, set, u"01234567")
obj._p_serial = "01234567"
del obj._p_serial
self.assertEqual(obj._p_serial, noserial)
def testMTime(self):
obj = P()
self.assertEqual(obj._p_mtime, None)
t = int(time.time())
ts = TimeStamp(*time.gmtime(t)[:6])
obj._p_serial = repr(ts)
self.assertEqual(obj._p_mtime, t)
self.assert_(isinstance(obj._p_mtime, float))
def testPicklable(self):
obj = P()
obj.attr = "test"
s = pickle.dumps(obj)
obj2 = pickle.loads(s)
self.assertEqual(obj.attr, obj2.attr)
def testGetattr(self):
obj = H1()
self.assertEqual(obj.larry, 1)
self.assertEqual(obj.curly, 2)
self.assertEqual(obj.moe, 3)
self.jar.add(obj)
obj._p_deactivate()
# The simple Jar used for testing re-initializes the object.
self.assertEqual(obj.larry, 1)
# 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)
def testGetattribute(self):
obj = H2()
self.assertEqual(obj.larry, 1)
self.assertEqual(obj.curly, 2)
self.assertEqual(obj.moe, 3)
self.jar.add(obj)
obj._p_deactivate()
# The simple Jar used for testing re-initializes the object.
self.assertEqual(obj.larry, 1)
# 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)
# XXX Need to decide how __setattr__ and __delattr__ should work,
# then write tests.
def test_suite():
return unittest.makeSuite(PersistenceTest)
##############################################################################
#
# 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.7 2004/04/19 21:19:10 tim_one Exp $
"""
from persistent import Persistent
import transaction
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
False
>>> o._p_oid
>>> o._p_jar
>>> o.spam
('SPAM', False)
>>> 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
>>> transaction.commit()
>>> o._p_deactivate()
>>> o._p_changed
And now, if we ask for an attribute it doesn't have,
>>> o.eggs
('EGGS', False)
And we see that the object was activated before calling the
__getattr__ method.
We always close databases after we use them:
>>> db.close()
"""
# Don't pretend we have any special attributes.
if name.startswith("__") and name.endswrith("__"):
raise AttributeError, name
else:
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
>>> 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
>>> 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:
>>> 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
>>> 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:
>>> 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 doctest
import os
import sys
import unittest
import persistent.tests
from persistent import Persistent
class P(Persistent):
def __init__(self):
self.x = 0
def inc(self):
self.x += 1
def DocFileSuite(path, globs=None):
# It's not entirely obvious how to connection this single string
# with unittest. For now, re-use the _utest() function that comes
# standard with doctest in Python 2.3. One problem is that the
# error indicator doesn't point to the line of the doctest file
# that failed.
source = open(path).read()
if globs is None:
globs = sys._getframe(1).f_globals
t = doctest.Tester(globs=globs)
def runit():
doctest._utest(t, path, source, path, 0)
f = unittest.FunctionTestCase(runit, description="doctest from %s" % path)
suite = unittest.TestSuite()
suite.addTest(f)
return suite
def test_suite():
path = os.path.join(persistent.tests.__path__[0], "persistent.txt")
return DocFileSuite(path, {"P": P})
##############################################################################
#
# 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.
#
##############################################################################
"""Basic pickling tests
$Id: test_pickle.py,v 1.6 2004/04/19 21:19:10 tim_one Exp $
"""
from persistent import Persistent
import pickle
def print_dict(d):
d = d.items()
d.sort()
print '{%s}' % (', '.join(
[('%r: %r' % (k, v)) for (k, v) in d]
))
def cmpattrs(self, other, *attrs):
for attr in attrs:
if attr[:3] in ('_v_', '_p_'):
continue
c = cmp(getattr(self, attr, None), getattr(other, attr, None))
if c:
return c
return 0
class Simple(Persistent):
def __init__(self, name, **kw):
self.__name__ = name
self.__dict__.update(kw)
self._v_favorite_color = 'blue'
self._p_foo = 'bar'
def __cmp__(self, other):
return cmpattrs(self, other, '__class__', *(self.__dict__.keys()))
def test_basic_pickling():
"""
>>> x = Simple('x', aaa=1, bbb='foo')
>>> print_dict(x.__getstate__())
{'__name__': 'x', 'aaa': 1, 'bbb': 'foo'}
>>> f, (c,), state = x.__reduce__()
>>> f.__name__
'__newobj__'
>>> f.__module__
'copy_reg'
>>> c.__name__
'Simple'
>>> print_dict(state)
{'__name__': 'x', 'aaa': 1, 'bbb': 'foo'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.__setstate__({'z': 1})
>>> x.__dict__
{'z': 1}
"""
class Custom(Simple):
def __new__(cls, x, y):
r = Persistent.__new__(cls)
r.x, r.y = x, y
return r
def __init__(self, x, y):
self.a = 42
def __getnewargs__(self):
return self.x, self.y
def __getstate__(self):
return self.a
def __setstate__(self, a):
self.a = a
def test_pickling_w_overrides():
"""
>>> x = Custom('x', 'y')
>>> x.a = 99
>>> (f, (c, ax, ay), a) = x.__reduce__()
>>> f.__name__
'__newobj__'
>>> f.__module__
'copy_reg'
>>> c.__name__
'Custom'
>>> ax, ay, a
('x', 'y', 99)
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
class Slotted(Persistent):
__slots__ = 's1', 's2', '_p_splat', '_v_eek'
def __init__(self, s1, s2):
self.s1, self.s2 = s1, s2
self._v_eek = 1
self._p_splat = 2
class SubSlotted(Slotted):
__slots__ = 's3', 's4'
def __init__(self, s1, s2, s3):
Slotted.__init__(self, s1, s2)
self.s3 = s3
def __cmp__(self, other):
return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4')
def test_pickling_w_slots_only():
"""
>>> x = SubSlotted('x', 'y', 'z')
>>> d, s = x.__getstate__()
>>> d
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.s4 = 'spam'
>>> d, s = x.__getstate__()
>>> d
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
class SubSubSlotted(SubSlotted):
def __init__(self, s1, s2, s3, **kw):
SubSlotted.__init__(self, s1, s2, s3)
self.__dict__.update(kw)
self._v_favorite_color = 'blue'
self._p_foo = 'bar'
def __cmp__(self, other):
return cmpattrs(self, other,
'__class__', 's1', 's2', 's3', 's4',
*(self.__dict__.keys()))
def test_pickling_w_slots():
"""
>>> x = SubSubSlotted('x', 'y', 'z', aaa=1, bbb='foo')
>>> d, s = x.__getstate__()
>>> print_dict(d)
{'aaa': 1, 'bbb': 'foo'}
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.s4 = 'spam'
>>> d, s = x.__getstate__()
>>> print_dict(d)
{'aaa': 1, 'bbb': 'foo'}
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
def test_pickling_w_slots_w_empty_dict():
"""
>>> x = SubSubSlotted('x', 'y', 'z')
>>> d, s = x.__getstate__()
>>> print_dict(d)
{}
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.s4 = 'spam'
>>> d, s = x.__getstate__()
>>> print_dict(d)
{}
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
XXX disable until Python 2.3.4 >>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
from doctest import DocTestSuite
import unittest
def test_suite():
return unittest.TestSuite((
DocTestSuite(),
))
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.
#
##############################################################################
"""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.3 2004/04/19 21:19:09 tim_one 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