tested django-newsletter

This commit is contained in:
Esther Kleinhenz
2018-10-26 15:21:17 +02:00
parent 7ed667d043
commit 811b7c5453
2352 changed files with 448169 additions and 1 deletions
@@ -0,0 +1,63 @@
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
#
##############################################################################
"""Prefer C implementations of Persistent / PickleCache / TimeStamp.
Fall back to pure Python implementations.
"""
import sys
__all__ = [
'IPersistent',
'Persistent',
'GHOST',
'UPTODATE',
'CHANGED',
'STICKY',
'PickleCache',
'TimeStamp',
]
from persistent._compat import PURE_PYTHON
from persistent.interfaces import IPersistent
import persistent.timestamp as TimeStamp
from persistent import persistence as pyPersistence
from persistent import picklecache as pyPickleCache
try:
# Be careful not to shadow the modules
from persistent import cPersistence as _cPersistence
from persistent import cPickleCache as _cPickleCache
except ImportError: # pragma: no cover
_cPersistence = None
_cPickleCache = None
else:
# Make an interface declaration for Persistent
# Note that the Python version already does this.
from zope.interface import classImplements
classImplements(_cPersistence.Persistent, IPersistent)
_persistence = pyPersistence if PURE_PYTHON or _cPersistence is None else _cPersistence
_picklecache = pyPickleCache if PURE_PYTHON or _cPickleCache is None else _cPickleCache
Persistent = _persistence.Persistent
GHOST = _persistence.GHOST
UPTODATE = _persistence.UPTODATE
CHANGED = _persistence.CHANGED
STICKY = _persistence.STICKY
PickleCache = _picklecache.PickleCache
sys.modules['persistent.TimeStamp'] = sys.modules['persistent.timestamp']
@@ -0,0 +1,48 @@
/*****************************************************************************
Copyright (c) 2012 Zope Foundation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.1 (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 PERSISTENT__COMPAT_H
#define PERSISTENT__COMPAT_H
#include "Python.h"
#if PY_MAJOR_VERSION >= 3
#define PY3K
#endif
#ifdef PY3K
#define INTERN PyUnicode_InternFromString
#define INTERN_INPLACE PyUnicode_InternInPlace
#define NATIVE_CHECK_EXACT PyUnicode_CheckExact
#define NATIVE_FROM_STRING_AND_SIZE PyUnicode_FromStringAndSize
#define Py_TPFLAGS_HAVE_RICHCOMPARE 0
#define INT_FROM_LONG(x) PyLong_FromLong(x)
#define INT_CHECK(x) PyLong_Check(x)
#define INT_AS_LONG(x) PyLong_AS_LONG(x)
#define CAPI_CAPSULE_NAME "persistent.cPersistence.CAPI"
#else
#define INTERN PyString_InternFromString
#define INTERN_INPLACE PyString_InternInPlace
#define NATIVE_CHECK_EXACT PyString_CheckExact
#define NATIVE_FROM_STRING_AND_SIZE PyString_FromStringAndSize
#define INT_FROM_LONG(x) PyInt_FromLong(x)
#define INT_CHECK(x) PyInt_Check(x)
#define INT_AS_LONG(x) PyInt_AS_LONG(x)
#endif
#endif
@@ -0,0 +1,37 @@
##############################################################################
#
# Copyright (c) 2012 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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 sys
import os
PURE_PYTHON = os.environ.get('PURE_PYTHON')
if sys.version_info[0] > 2:
import copyreg as copy_reg
from collections import UserDict as IterableUserDict
from collections import UserList
from sys import intern
PYTHON3 = True
PYTHON2 = False
else: # pragma: no cover
import copy_reg
from UserDict import IterableUserDict
from UserList import UserList
PYTHON3 = False
PYTHON2 = True
intern = intern
Binary file not shown.
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2018 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
from __future__ import absolute_import, print_function, division
import os
from cffi import FFI
this_dir = os.path.dirname(os.path.abspath(__file__))
ffi = FFI()
with open(os.path.join(this_dir, 'ring.h')) as f:
ffi.cdef(f.read())
ffi.set_source('persistent._ring',
'#include "ring.c"',
include_dirs=[this_dir])
if __name__ == '__main__':
ffi.compile()
@@ -0,0 +1,619 @@
/*****************************************************************************
Copyright (c) 2001, 2004 Zope Foundation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.1 (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 "bytesobject.h"
#include <time.h>
#include "_compat.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$\n";
/* A magic constant having the value 0.000000013969839. When an
number of seconds between 0 and 59 is *divided* by this number, we get
a number between 0 (for 0), 71582786 (for 1) and 4223384393 (for 59),
all of which can be represented in a 32-bit unsigned integer, suitable
for packing into 4 bytes using `TS_PACK_UINT32_INTO_BYTES`.
To get (close to) the original seconds back, use
`TS_UNPACK_UINT32_FROM_BYTES` and *multiply* by this number.
*/
#define TS_SECOND_BYTES_BIAS ((double)((double)60) / ((double)(0x10000)) / ((double)(0x10000)))
#define TS_BASE_YEAR 1900
#define TS_MINUTES_PER_DAY 1440
/* We pretend there are always 31 days in a month; this has us using
372 days in a year in some calculations */
#define TS_DAYS_PER_MONTH 31
#define TS_MONTHS_PER_YEAR 12
#define TS_MINUTES_PER_MONTH (TS_DAYS_PER_MONTH * TS_MINUTES_PER_DAY)
#define TS_MINUTES_PER_YEAR (TS_MINUTES_PER_MONTH * TS_MONTHS_PER_YEAR)
/* The U suffixes matter on these constants to be sure
the compiler generates the appropriate instructions when
optimizations are enabled. On x86_64 GCC, if -fno-wrapv is given
and -O is used, the compiler might choose to treat these as 32 bit
signed quantities otherwise, producing incorrect results on
some corner cases. See
https://github.com/zopefoundation/persistent/issues/86
*/
/**
* Given an unsigned int *v*, pack it into the four
* unsigned char bytes beginning at *bytes*. If *v* is larger
* than 2^31 (i.e., it doesn't fit in 32 bits), the results will
* be invalid (the first byte will be 0.)
*
* The inverse is `TS_UNPACK_UINT32_FROM_BYTES`. This is a
* lossy operation and may lose some lower-order precision.
*
*/
#define TS_PACK_UINT32_INTO_BYTES(v, bytes) do { \
*(bytes) = v / 0x1000000U; \
*(bytes + 1) = (v % 0x1000000U) / 0x10000U; \
*(bytes + 2) = (v % 0x10000U) / 0x100U; \
*(bytes + 3) = v % 0x100U; \
} while (0)
/**
* Given a sequence of four unsigned chars beginning at *bytes*
* as produced by `TS_PACK_UINT32_INTO_BYTES`, return the
* original unsigned int.
*
* Remember this is a lossy operation, and the value you get back
* may not exactly match the original value. If the original value
* was greater than 2^31 it will definitely not match.
*/
#define TS_UNPACK_UINT32_FROM_BYTES(bytes) (*(bytes) * 0x1000000U + *(bytes + 1) * 0x10000U + *(bytes + 2) * 0x100U + *(bytes + 3))
typedef struct
{
PyObject_HEAD
/*
The first four bytes of data store the year, month, day, hour, and
minute as the number of minutes since Jan 1 00:00.
The final four bytes store the seconds since 00:00 as
the number of microseconds.
Both are normalized into those four bytes the same way with
TS_[UN]PACK_UINT32_INTO|FROM_BYTES.
*/
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;
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 -= TS_BASE_YEAR;
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 + TS_BASE_YEAR, 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 PyObject*
TimeStamp_richcompare(TimeStamp *self, TimeStamp *other, int op)
{
PyObject *result = NULL;
int cmp;
if (Py_TYPE(other) != Py_TYPE(self))
{
result = Py_NotImplemented;
}
else
{
cmp = memcmp(self->data, other->data, 8);
switch (op) {
case Py_LT:
result = (cmp < 0) ? Py_True : Py_False;
break;
case Py_LE:
result = (cmp <= 0) ? Py_True : Py_False;
break;
case Py_EQ:
result = (cmp == 0) ? Py_True : Py_False;
break;
case Py_NE:
result = (cmp != 0) ? Py_True : Py_False;
break;
case Py_GT:
result = (cmp > 0) ? Py_True : Py_False;
break;
case Py_GE:
result = (cmp >= 0) ? Py_True : Py_False;
break;
}
}
Py_XINCREF(result);
return result;
}
#ifdef PY3K
static Py_hash_t
#else
static long
#endif
TimeStamp_hash(TimeStamp *self)
{
register unsigned char *p = (unsigned char *)self->data;
register int len = 8;
register long x = *p << 7;
while (--len >= 0)
x = (1000003*x) ^ *p++;
x ^= 8;
if (x == -1)
x = -2;
return x;
}
typedef struct
{
/* TODO: 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 int minutes_since_base;
minutes_since_base = TS_UNPACK_UINT32_FROM_BYTES(self->data);
p->y = minutes_since_base / TS_MINUTES_PER_YEAR + TS_BASE_YEAR;
p->m = (minutes_since_base % TS_MINUTES_PER_YEAR) / TS_MINUTES_PER_MONTH + 1;
p->d = (minutes_since_base % TS_MINUTES_PER_MONTH) / TS_MINUTES_PER_DAY + 1;
p->mi = minutes_since_base % TS_MINUTES_PER_DAY;
}
static double
TimeStamp_sec(TimeStamp *self)
{
unsigned int v;
v = TS_UNPACK_UINT32_FROM_BYTES(self->data +4);
return TS_SECOND_BYTES_BIAS * v;
}
static PyObject *
TimeStamp_year(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return INT_FROM_LONG(p.y);
}
static PyObject *
TimeStamp_month(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return INT_FROM_LONG(p.m);
}
static PyObject *
TimeStamp_day(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return INT_FROM_LONG(p.d);
}
static PyObject *
TimeStamp_hour(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return INT_FROM_LONG(p.mi / 60);
}
static PyObject *
TimeStamp_minute(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return INT_FROM_LONG(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 PyBytes_FromStringAndSize((const char*)self->data, 8);
}
static PyObject *
TimeStamp_repr(TimeStamp *self)
{
PyObject *raw, *result;
raw = TimeStamp_raw(self);
result = PyObject_Repr(raw);
Py_DECREF(raw);
return result;
}
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 NATIVE_FROM_STRING_AND_SIZE(buf, len);
}
static PyObject *
TimeStamp_laterThan(TimeStamp *self, PyObject *obj)
{
TimeStamp *o = NULL;
TimeStampParts p;
unsigned char new[8];
int i;
if (Py_TYPE(obj) != Py_TYPE(self))
{
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((const char*)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},
};
#define DEFERRED_ADDRESS(ADDR) 0
static PyTypeObject TimeStamp_type =
{
PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(NULL), 0)
"persistent.TimeStamp",
sizeof(TimeStamp), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)TimeStamp_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc)TimeStamp_repr, /* 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 |
Py_TPFLAGS_HAVE_RICHCOMPARE, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
(richcmpfunc)&TimeStamp_richcompare, /* 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 years_since_base;
unsigned int months_since_base;
unsigned int days_since_base;
unsigned int hours_since_base;
unsigned int minutes_since_base;
unsigned int v;
if (year < TS_BASE_YEAR)
return PyErr_Format(PyExc_ValueError,
"year must be greater than %d: %d", TS_BASE_YEAR, 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);
/* months come in 1-based, hours and minutes come in 0-based */
/* The base time is Jan 1, 00:00 of TS_BASE_YEAR */
years_since_base = year - TS_BASE_YEAR;
months_since_base = years_since_base * TS_MONTHS_PER_YEAR + (month - 1);
days_since_base = months_since_base * TS_DAYS_PER_MONTH + (day - 1);
hours_since_base = days_since_base * 24 + hour;
minutes_since_base = hours_since_base * 60 + min;
TS_PACK_UINT32_INTO_BYTES(minutes_since_base, ts->data);
sec /= TS_SECOND_BYTES_BIAS;
v = (unsigned int)sec;
TS_PACK_UINT32_INTO_BYTES(v, ts->data + 4);
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;
#ifdef PY3K
if (PyArg_ParseTuple(args, "y#", &buf, &len))
#else
if (PyArg_ParseTuple(args, "s#", &buf, &len))
#endif
{
if (len != 8)
{
PyErr_SetString(PyExc_ValueError,
"8-byte array 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},
};
#ifdef PY3K
static struct PyModuleDef moduledef =
{
PyModuleDef_HEAD_INIT,
"_timestamp", /* m_name */
TimeStampModule_doc, /* m_doc */
-1, /* m_size */
TimeStampModule_functions, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
#endif
static PyObject*
module_init(void)
{
PyObject *module;
if (TimeStamp_init_gmoff() < 0)
return NULL;
#ifdef PY3K
module = PyModule_Create(&moduledef);
#else
module = Py_InitModule4("_timestamp", TimeStampModule_functions,
TimeStampModule_doc, NULL, PYTHON_API_VERSION);
#endif
if (module == NULL)
return NULL;
#ifdef PY3K
((PyObject*)&TimeStamp_type)->ob_type = &PyType_Type;
#else
TimeStamp_type.ob_type = &PyType_Type;
#endif
TimeStamp_type.tp_getattro = PyObject_GenericGetAttr;
return module;
}
#ifdef PY3K
PyMODINIT_FUNC PyInit__timestamp(void)
{
return module_init();
}
#else
PyMODINIT_FUNC init_timestamp(void)
{
module_init();
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,156 @@
/*****************************************************************************
Copyright (c) 2001, 2002 Zope Foundation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.1 (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 "_compat.h"
#include "bytesobject.h"
#include "ring.h"
#define CACHE_HEAD \
PyObject_HEAD \
CPersistentRing ring_home; \
int non_ghost_count; \
Py_ssize_t total_estimated_size;
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
4 size info
(56) so far
4 dict ptr
4 weaklist ptr
-------------------------
68 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 state:8; \
unsigned estimated_size:24;
/* We recently added estimated_size. We originally added it as a new
unsigned long field after a signed char state field and a
3-character reserved field. This didn't work because there
are packages in the wild that have their own copies of cPersistence.h
that didn't see the update.
To get around this, we used the reserved space by making
estimated_size a 24-bit bit field in the space occupied by the old
3-character reserved field. To fit in 24 bits, we made the units
of estimated_size 64-character blocks. This allows is to handle up
to a GB. We should never see that, but to be paranoid, we also
truncate sizes greater than 1GB. We also set the minimum size to
64 bytes.
We use the _estimated_size_in_24_bits and _estimated_size_in_bytes
macros both to avoid repetition and to make intent a little clearer.
*/
#define _estimated_size_in_24_bits(I) ((I) > 1073741696 ? 16777215 : (I)/64+1)
#define _estimated_size_in_bytes(I) ((I)*64)
#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;
int (*readCurrent)(cPersistentObject*);
} 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_READCURRENT(O, E) \
if (cPersistenceCAPI->readCurrent((cPersistentObject*)(O)) < 0) { E; }
#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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,16 @@
##############################################################################
#
# Copyright Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
# persistent.dict is deprecated. Use persistent.mapping
from persistent.mapping import PersistentMapping as PersistentDict
@@ -0,0 +1,568 @@
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
"""
from zope.interface import Interface
from zope.interface import Attribute
# Allowed values for _p_state
try:
from persistent.cPersistence import GHOST
from persistent.cPersistence import UPTODATE
from persistent.cPersistence import CHANGED
from persistent.cPersistence import STICKY
except ImportError: # pragma: no cover
GHOST = -1
UPTODATE = 0
CHANGED = 1
STICKY = 2
OID_TYPE = SERIAL_TYPE = bytes
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 saved 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 no Python API for detecting 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 its state has been unloaded). In this state,
the object doesn't contain any application data.
In this state, the _p_changed attribute is None, and the _p_jar
attribute is set to the data manager from which the object was
obtained.
In all the above, _p_oid (the persistent object id) is set when
_p_jar first gets set.
The following state transitions 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
Ghost -> Changed
This transition occurs when someone sets an attribute or sets
_p_changed to a true value on a saved, sticky or ghost 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.
Prior to ZODB 3.6, setting _p_changed to a true value on a ghost object
was ignored (the object remained a ghost, and getting its _p_changed
attribute continued to return None).
- Saved -> Sticky
This transition occurs when C code marks the object as sticky to
prevent its deactivation.
- Saved -> Ghost
This transition occurs when a saved object is deactivated or
invalidated. See discussion below.
- Sticky -> Saved
This transition occurs when C code unmarks the object as sticky to
allow its deactivation.
- Changed -> Saved
This transition occurs when a transaction is committed. After
saving the state of a changed object during transaction commit,
the data manager sets the object's _p_changed to a non-None false
value.
- Changed -> Ghost
This transition occurs when a transaction is aborted. All changed
objects are invalidated by the data manager by an abort.
- 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.
About Invalidation, Deactivation and the Sticky & Ghost States
The sticky state is intended to be a short-lived state, to prevent
an object's state from being discarded while we're in C routines. It
is an error to invalidate an object in the sticky state.
Deactivation is a request that an object discard its state (become
a ghost). Deactivation is an optimization, and a request to
deactivate may be ignored. There are two equivalent ways to
request deactivation:
- call _p_deactivate()
- set _p_changed to None
There are two ways to invalidate an object: call the
_p_invalidate() method (preferred) or delete its _p_changed
attribute. This cannot be ignored, and is used when semantics
require invalidation. Normally, an invalidated object transitions
to the ghost state. However, some objects cannot be ghosts. When
these objects are invalidated, they immediately reload their state
from their data manager, and are then in the saved state.
reprs
By default, persistent objects include the reprs of their
_p_oid and _p_jar, if any, in their repr. If a subclass implements
the optional method ``_p_repr``, it will be called and its results returned
instead of the default repr; if this method raises an exception, that
exception will be caught and its repr included in the default repr.
"""
_p_jar = Attribute(
"""The data manager for the object.
The data manager should implement IPersistentDataManager (note that
this constraint is not enforced).
If there is no data manager, then this is None.
Once assigned to a data manager, an object cannot be re-assigned
to another.
""")
_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. Non-None object ids must be non-empty
strings. The 8-byte string consisting of 8 NUL bytes
('\x00\x00\x00\x00\x00\x00\x00\x00') is reserved to identify the
database root object.
Once assigned an OID, an object cannot be re-assigned another.
""")
_p_changed = Attribute(
"""The persistent state of the object.
This is one of:
None -- The object is a ghost.
false but not None -- The object is saved (or has never been saved).
true -- The object has been modified since it was last saved.
The object state may be changed by assigning or deleting this
attribute; however, assigning None is ignored if the object is
not in the saved state, and may be ignored even if the object is
in the saved state.
At and after ZODB 3.6, setting _p_changed to a true value for a ghost
object activates the object; prior to 3.6, setting _p_changed to a
true value on a ghost object was ignored.
Note that an object can transition to the changed state only if
it has a data manager. When such a state change occurs, the
'register' method of the data manager must be called, passing the
persistent object.
Deleting this attribute forces invalidation independent of
existing state, although it is an error if the sticky state is
current.
""")
_p_serial = Attribute(
"""The object serial number.
This member is used by the data manager to distiguish distinct
revisions of a given persistent object.
This is an 8-byte string (not Unicode).
""")
_p_mtime = Attribute(
"""The object's modification time (read-only).
This is a float, representing seconds since the epoch (as returned
by time.time).
""")
_p_state = Attribute(
"""The object's persistence state token.
Must be one of GHOST, UPTODATE, CHANGED, or STICKY.
""")
_p_estimated_size = Attribute(
"""An estimate of the object's size in bytes.
May be set by the data manager.
""")
# Attribute access protocol
def __getattribute__(name):
""" Handle activating ghosts before returning an attribute value.
"Special" attributes and '_p_*' attributes don't require activation.
"""
def __setattr__(name, value):
""" Handle activating ghosts before setting an attribute value.
"Special" attributes and '_p_*' attributes don't require activation.
"""
def __delattr__(name):
""" Handle activating ghosts before deleting an attribute value.
"Special" attributes and '_p_*' attributes don't require activation.
"""
# Pickling protocol.
def __getstate__():
"""Get the object data.
The state should not include persistent attributes ("_p_name").
The result must be picklable.
"""
def __setstate__(state):
"""Set the object data.
"""
def __reduce__():
"""Reduce an object to contituent parts for serialization.
"""
# Custom methods
def _p_activate():
"""Activate the object.
Change the object to the saved state if it is a ghost.
"""
def _p_deactivate():
"""Deactivate the object.
Possibly change an object in the saved state to the
ghost state. It may not be possible to make some persistent
objects ghosts, and, for optimization reasons, the implementation
may choose to keep an object in the saved state.
"""
def _p_invalidate():
"""Invalidate the object.
Invalidate the object. This causes any data to be thrown
away, even if the object is in the changed state. The object
is moved to the ghost state; further accesses will cause
object data to be reloaded.
"""
def _p_getattr(name):
"""Test whether the base class must handle the name
The method unghostifies the object, if necessary.
The method records the object access, if necessary.
This method should be called by subclass __getattribute__
implementations before doing anything else. If the method
returns True, then __getattribute__ implementations must delegate
to the base class, Persistent.
"""
def _p_setattr(name, value):
"""Save persistent meta data
This method should be called by subclass __setattr__ implementations
before doing anything else. If it returns true, then the attribute
was handled by the base class.
The method unghostifies the object, if necessary.
The method records the object access, if necessary.
"""
def _p_delattr(name):
"""Delete persistent meta data
This method should be called by subclass __delattr__ implementations
before doing anything else. If it returns true, then the attribute
was handled by the base class.
The method unghostifies the object, if necessary.
The method records the object access, if necessary.
"""
# TODO: document conflict resolution.
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.
"""
_cache = Attribute("The pickle cache associated with this connection.")
def setstate(object):
"""Load the state for the given object.
The object should be in the ghost state. The object's state will be
set and the object will end up in the saved state.
The object must provide the IPersistent interface.
"""
def oldstate(obj, tid):
"""Return copy of 'obj' that was written by transaction 'tid'.
The returned object does not have the typical metadata (_p_jar, _p_oid,
_p_serial) set. I'm not sure how references to other peristent objects
are handled.
Parameters
obj: a persistent object from this Connection.
tid: id of a transaction that wrote an earlier revision.
Raises KeyError if tid does not exist or if tid deleted a revision of
obj.
"""
def register(object):
"""Register an IPersistent with the current transaction.
This method must be called when the object transitions to
the changed state.
A subclass could override this method to customize the default
policy of one transaction manager for each thread.
"""
# Maybe later:
## def mtime(object):
## """Return the modification time of the object.
## The modification time may not be known, in which case None
## is returned. If non-None, the return value is the kind of
## timestamp supplied by Python's time.time().
## """
class IPickleCache(Interface):
""" API of the cache for a ZODB connection.
"""
def __getitem__(oid):
""" -> the persistent object for OID.
o Raise KeyError if not found.
"""
def __setitem__(oid, value):
""" Save the persistent object under OID.
o 'oid' must be a string, else raise ValueError.
o Raise KeyError on duplicate
"""
def __delitem__(oid):
""" Remove the persistent object for OID.
o 'oid' must be a string, else raise ValueError.
o Raise KeyError if not found.
"""
def get(oid, default=None):
""" -> the persistent object for OID.
o Return 'default' if not found.
"""
def mru(oid):
""" Move the element corresonding to 'oid' to the head.
o Raise KeyError if no element is found.
"""
def __len__():
""" -> the number of OIDs in the cache.
"""
def items():
"""-> a sequence of tuples (oid, value) for cached objects.
o Only includes items in 'data' (no p-classes).
"""
def ringlen():
""" -> the number of persistent objects in the ring.
o Only includes items in the ring (no ghosts or p-classes).
"""
def lru_items():
""" -> a sequence of tuples (oid, value) for cached objects.
o Tuples will be in LRU order.
o Only includes items in the ring (no ghosts or p-classes).
"""
def klass_items():
"""-> a sequence of tuples (oid, value) for cached p-classes.
o Only includes persistent classes.
"""
def incrgc():
""" Perform an incremental garbage collection sweep.
o Reduce number of non-ghosts to 'cache_size', if possible.
o Ghostify in LRU order.
o Skip dirty or sticky objects.
o Quit once we get down to 'cache_size'.
"""
def full_sweep():
""" Perform a full garbage collection sweep.
o Reduce number of non-ghosts to 0, if possible.
o Ghostify all non-sticky / non-changed objecs.
"""
def minimize():
""" Alias for 'full_sweep'.
o XXX?
"""
def new_ghost(oid, obj):
""" Add the given (ghost) object to the cache.
Also, set its _p_jar and _p_oid, and ensure it is in the
GHOST state.
If the object doesn't define '_p_oid' / '_p_jar', raise.
If the object's '_p_oid' is not None, raise.
If the object's '_p_jar' is not None, raise.
If 'oid' is already in the cache, raise.
"""
def reify(to_reify):
""" Reify the indicated objects.
o If 'to_reify' is a string, treat it as an OID.
o Otherwise, iterate over it as a sequence of OIDs.
o For each OID, if present in 'data' and in GHOST state:
o Call '_p_activate' on the object.
o Add it to the ring.
o If any OID is present but not in GHOST state, skip it.
o Raise KeyErrory if any OID is not present.
"""
def invalidate(to_invalidate):
""" Invalidate the indicated objects.
o If 'to_invalidate' is a string, treat it as an OID.
o Otherwise, iterate over it as a sequence of OIDs.
o Any OID corresponding to a p-class will cause the corresponding
p-class to be removed from the cache.
o For all other OIDs, ghostify the corrsponding object and
remove it from the ring.
"""
def debug_info():
"""Return debugging data about objects in the cache.
o Return a sequence of tuples, (oid, refcount, typename, state).
"""
def update_object_size_estimation(oid, new_size):
"""Update the cache's size estimation for 'oid', if known to the cache.
"""
cache_size = Attribute('Target size of the cache')
cache_drain_resistance = Attribute('Factor for draining cache below '
'target size')
cache_non_ghost_count = Attribute('Number of non-ghosts in the cache '
'(XXX how is it different from '
'ringlen?')
cache_data = Attribute("Property: copy of our 'data' dict")
cache_klass_count = Attribute("Property: len of 'persistent_classes'")
@@ -0,0 +1,98 @@
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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$"""
import persistent
from persistent._compat import UserList
from persistent._compat import PYTHON2
class PersistentList(UserList, persistent.Persistent):
"""A persistent wrapper for list objects.
Mutating instances of this class will cause them to be marked
as changed and automatically persisted.
"""
__super_setitem = UserList.__setitem__
__super_delitem = UserList.__delitem__
__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
if PYTHON2: # pragma: no cover
__super_setslice = UserList.__setslice__
__super_delslice = UserList.__delslice__
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, **kwargs):
self.__super_sort(*args, **kwargs)
self._p_changed = 1
def extend(self, other):
self.__super_extend(other)
self._p_changed = 1
@@ -0,0 +1,102 @@
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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$"""
import persistent
from persistent._compat import IterableUserDict
class default(object):
def __init__(self, func):
self.func = func
def __get__(self, inst, class_):
if inst is None:
return self
return self.func(inst)
class PersistentMapping(IterableUserDict, 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 = IterableUserDict.__delitem__
__super_setitem = IterableUserDict.__setitem__
__super_clear = IterableUserDict.clear
__super_update = IterableUserDict.update
__super_setdefault = IterableUserDict.setdefault
__super_pop = IterableUserDict.pop
__super_popitem = IterableUserDict.popitem
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 key in self.data:
self._p_changed = 1
return self.__super_setdefault(key, failobj)
def pop(self, key, *args):
self._p_changed = 1
return self.__super_pop(key, *args)
def popitem(self):
self._p_changed = 1
return self.__super_popitem()
# Old implementations used _container rather than data.
# Use a descriptor to provide data when we have _container instead
@default
def data(self):
# We don't want to cause a write on read, so wer're careful not to
# do anything that would cause us to become marked as changed, however,
# if we're modified, then the saved record will have data, not
# _container.
data = self.__dict__.pop('_container')
self.__dict__['data'] = data
return data
@@ -0,0 +1,608 @@
##############################################################################
#
# Copyright (c) 2011 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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 struct
from zope.interface import implementer
from persistent.interfaces import IPersistent
from persistent.interfaces import GHOST
from persistent.interfaces import UPTODATE
from persistent.interfaces import CHANGED
from persistent.interfaces import STICKY
from persistent.interfaces import SERIAL_TYPE
from persistent.timestamp import TimeStamp
from persistent.timestamp import _ZERO
from persistent._compat import copy_reg
from persistent._compat import intern
_INITIAL_SERIAL = _ZERO
# Bitwise flags
_CHANGED = 0x0001
_STICKY = 0x0002
_OGA = object.__getattribute__
_OSA = object.__setattr__
_ODA = object.__delattr__
# These names can be used from a ghost without causing it to be
# activated. These are standardized with the C implementation
SPECIAL_NAMES = ('__class__',
'__del__',
'__dict__',
'__of__',
'__setstate__',)
# And this is an implementation detail of this class; it holds
# the standard names plus the slot names, allowing for just one
# check in __getattribute__
_SPECIAL_NAMES = set(SPECIAL_NAMES)
# Represent 8-byte OIDs as hex integer, just like
# ZODB does.
_OID_STRUCT = struct.Struct('>Q')
_OID_UNPACK = _OID_STRUCT.unpack
@implementer(IPersistent)
class Persistent(object):
""" Pure Python implmentation of Persistent base class
"""
__slots__ = ('__jar', '__oid', '__serial', '__flags', '__size', '__ring',)
def __new__(cls, *args, **kw):
inst = super(Persistent, cls).__new__(cls)
# We bypass the __setattr__ implementation of this object
# at __new__ time, just like the C implementation does. This
# makes us compatible with subclasses that want to access
# properties like _p_changed in their setattr implementation
_OSA(inst, '_Persistent__jar', None)
_OSA(inst, '_Persistent__oid', None)
_OSA(inst, '_Persistent__serial', None)
_OSA(inst, '_Persistent__flags', None)
_OSA(inst, '_Persistent__size', 0)
_OSA(inst, '_Persistent__ring', None)
return inst
# _p_jar: see IPersistent.
def _get_jar(self):
return _OGA(self, '_Persistent__jar')
def _set_jar(self, value):
jar = _OGA(self, '_Persistent__jar')
if self._p_is_in_cache(jar) and value is not None and jar != value:
# The C implementation only forbids changing the jar
# if we're already in a cache. Match its error message
raise ValueError('can not change _p_jar of cached object')
if _OGA(self, '_Persistent__jar') != value:
_OSA(self, '_Persistent__jar', value)
_OSA(self, '_Persistent__flags', 0)
def _del_jar(self):
jar = _OGA(self, '_Persistent__jar')
if jar is not None:
if self._p_is_in_cache(jar):
raise ValueError("can't delete _p_jar of cached object")
_OSA(self, '_Persistent__jar', None)
_OSA(self, '_Persistent__flags', None)
_p_jar = property(_get_jar, _set_jar, _del_jar)
# _p_oid: see IPersistent.
def _get_oid(self):
return _OGA(self, '_Persistent__oid')
def _set_oid(self, value):
if value == _OGA(self, '_Persistent__oid'):
return
# The C implementation allows *any* value to be
# used as the _p_oid.
#if value is not None:
# if not isinstance(value, OID_TYPE):
# raise ValueError('Invalid OID type: %s' % value)
# The C implementation only forbids changing the OID
# if we're in a cache, regardless of what the current
# value or jar is
if self._p_is_in_cache():
# match the C error message
raise ValueError('can not change _p_oid of cached object')
_OSA(self, '_Persistent__oid', value)
def _del_oid(self):
jar = _OGA(self, '_Persistent__jar')
oid = _OGA(self, '_Persistent__oid')
if jar is not None:
if oid and jar._cache.get(oid):
raise ValueError('Cannot delete _p_oid of cached object')
_OSA(self, '_Persistent__oid', None)
_p_oid = property(_get_oid, _set_oid, _del_oid)
# _p_serial: see IPersistent.
def _get_serial(self):
serial = _OGA(self, '_Persistent__serial')
if serial is not None:
return serial
return _INITIAL_SERIAL
def _set_serial(self, value):
if not isinstance(value, SERIAL_TYPE):
raise ValueError('Invalid SERIAL type: %s' % value)
if len(value) != 8:
raise ValueError('SERIAL must be 8 octets')
_OSA(self, '_Persistent__serial', value)
def _del_serial(self):
_OSA(self, '_Persistent__serial', None)
_p_serial = property(_get_serial, _set_serial, _del_serial)
# _p_changed: see IPersistent.
def _get_changed(self):
if _OGA(self, '_Persistent__jar') is None:
return False
flags = _OGA(self, '_Persistent__flags')
if flags is None: # ghost
return None
return bool(flags & _CHANGED)
def _set_changed(self, value):
if _OGA(self, '_Persistent__flags') is None:
if value:
self._p_activate()
self._p_set_changed_flag(value)
else:
if value is None: # -> ghost
self._p_deactivate()
else:
self._p_set_changed_flag(value)
def _del_changed(self):
self._p_invalidate()
_p_changed = property(_get_changed, _set_changed, _del_changed)
# _p_mtime
def _get_mtime(self):
# The C implementation automatically unghostifies the object
# when _p_mtime is accessed.
self._p_activate()
self._p_accessed()
serial = _OGA(self, '_Persistent__serial')
if serial is not None:
ts = TimeStamp(serial)
return ts.timeTime()
_p_mtime = property(_get_mtime)
# _p_state
def _get_state(self):
# Note the use of OGA and caching to avoid recursive calls to __getattribute__:
# __getattribute__ calls _p_accessed calls cache.mru() calls _p_state
if _OGA(self, '_Persistent__jar') is None:
return UPTODATE
flags = _OGA(self, '_Persistent__flags')
if flags is None:
return GHOST
if flags & _CHANGED:
result = CHANGED
else:
result = UPTODATE
if flags & _STICKY:
return STICKY
return result
_p_state = property(_get_state)
# _p_estimated_size: XXX don't want to reserve the space?
def _get_estimated_size(self):
return _OGA(self, '_Persistent__size') * 64
def _set_estimated_size(self, value):
if isinstance(value, int):
if value < 0:
raise ValueError('_p_estimated_size must not be negative')
_OSA(self, '_Persistent__size', _estimated_size_in_24_bits(value))
else:
raise TypeError("_p_estimated_size must be an integer")
def _del_estimated_size(self):
_OSA(self, '_Persistent__size', 0)
_p_estimated_size = property(
_get_estimated_size, _set_estimated_size, _del_estimated_size)
# The '_p_sticky' property is not (yet) part of the API: for now,
# it exists to simplify debugging and testing assertions.
def _get_sticky(self):
flags = _OGA(self, '_Persistent__flags')
if flags is None:
return False
return bool(flags & _STICKY)
def _set_sticky(self, value):
flags = _OGA(self, '_Persistent__flags')
if flags is None:
raise ValueError('Ghost')
if value:
flags |= _STICKY
else:
flags &= ~_STICKY
_OSA(self, '_Persistent__flags', flags)
_p_sticky = property(_get_sticky, _set_sticky)
# The '_p_status' property is not (yet) part of the API: for now,
# it exists to simplify debugging and testing assertions.
def _get_status(self):
if _OGA(self, '_Persistent__jar') is None:
return 'unsaved'
flags = _OGA(self, '_Persistent__flags')
if flags is None:
return 'ghost'
if flags & _STICKY:
return 'sticky'
if flags & _CHANGED:
return 'changed'
return 'saved'
_p_status = property(_get_status)
# Methods from IPersistent.
def __getattribute__(self, name):
""" See IPersistent.
"""
oga = _OGA
if (not name.startswith('_p_') and
name not in _SPECIAL_NAMES):
if oga(self, '_Persistent__flags') is None:
oga(self, '_p_activate')()
oga(self, '_p_accessed')()
return oga(self, name)
def __setattr__(self, name, value):
special_name = (name in _SPECIAL_NAMES or
name.startswith('_p_'))
volatile = name.startswith('_v_')
if not special_name:
if _OGA(self, '_Persistent__flags') is None:
_OGA(self, '_p_activate')()
if not volatile:
_OGA(self, '_p_accessed')()
_OSA(self, name, value)
if (_OGA(self, '_Persistent__jar') is not None and
_OGA(self, '_Persistent__oid') is not None and
not special_name and
not volatile):
before = _OGA(self, '_Persistent__flags')
after = before | _CHANGED
if before != after:
_OSA(self, '_Persistent__flags', after)
_OGA(self, '_p_register')()
def __delattr__(self, name):
special_name = (name in _SPECIAL_NAMES or
name.startswith('_p_'))
if not special_name:
if _OGA(self, '_Persistent__flags') is None:
_OGA(self, '_p_activate')()
_OGA(self, '_p_accessed')()
before = _OGA(self, '_Persistent__flags')
after = before | _CHANGED
if before != after:
_OSA(self, '_Persistent__flags', after)
if (_OGA(self, '_Persistent__jar') is not None and
_OGA(self, '_Persistent__oid') is not None):
_OGA(self, '_p_register')()
_ODA(self, name)
def _slotnames(self, _v_exclude=True):
slotnames = copy_reg._slotnames(type(self))
return [x for x in slotnames
if not x.startswith('_p_') and
not (x.startswith('_v_') and _v_exclude) and
not x.startswith('_Persistent__') and
x not in Persistent.__slots__]
def __getstate__(self):
""" See IPersistent.
"""
idict = getattr(self, '__dict__', None)
slotnames = self._slotnames()
if idict is not None:
d = dict([x for x in idict.items()
if not x[0].startswith('_p_') and
not x[0].startswith('_v_')])
else:
d = None
if slotnames:
s = {}
for slotname in slotnames:
value = getattr(self, slotname, self)
if value is not self:
s[slotname] = value
return d, s
return d
def __setstate__(self, state):
""" See IPersistent.
"""
if isinstance(state,tuple):
inst_dict, slots = state
else:
inst_dict, slots = state, ()
idict = getattr(self, '__dict__', None)
if inst_dict is not None:
if idict is None:
raise TypeError('No instance dict')
idict.clear()
for k, v in inst_dict.items():
# Normally the keys for instance attributes are interned.
# Do that here, but only if it is possible to do so.
idict[intern(k) if type(k) is str else k] = v
slotnames = self._slotnames()
if slotnames:
for k, v in slots.items():
setattr(self, k, v)
def __reduce__(self):
""" See IPersistent.
"""
gna = getattr(self, '__getnewargs__', lambda: ())
return (copy_reg.__newobj__,
(type(self),) + gna(), self.__getstate__())
def _p_activate(self):
""" See IPersistent.
"""
oga = _OGA
before = oga(self, '_Persistent__flags')
if before is None: # Only do this if we're a ghost
# Begin by marking up-to-date in case we bail early
_OSA(self, '_Persistent__flags', 0)
jar = oga(self, '_Persistent__jar')
if jar is None:
return
oid = oga(self, '_Persistent__oid')
if oid is None:
return
# If we're actually going to execute a set-state,
# mark as changed to prevent any recursive call
# (actually, our earlier check that we're a ghost should
# prevent this, but the C implementation sets it to changed
# while calling jar.setstate, and this is observable to clients).
# The main point of this is to prevent changes made during
# setstate from registering the object with the jar.
_OSA(self, '_Persistent__flags', CHANGED)
try:
jar.setstate(self)
except:
_OSA(self, '_Persistent__flags', before)
raise
else:
# If we succeed, no matter what the implementation
# of setstate did, mark ourself as up-to-date. The
# C implementation unconditionally does this.
_OSA(self, '_Persistent__flags', 0) # up-to-date
# In the C implementation, _p_invalidate winds up calling
# _p_deactivate. There are ZODB tests that depend on this;
# it's not documented but there may be code in the wild
# that does as well
def _p_deactivate(self):
""" See IPersistent.
"""
flags = _OGA(self, '_Persistent__flags')
if flags is not None and not flags:
self._p_invalidate_deactivate_helper()
def _p_invalidate(self):
""" See IPersistent.
"""
# If we think we have changes, we must pretend
# like we don't so that deactivate does its job
_OSA(self, '_Persistent__flags', 0)
self._p_deactivate()
def _p_invalidate_deactivate_helper(self, clear=True):
jar = _OGA(self, '_Persistent__jar')
if jar is None:
return
if _OGA(self, '_Persistent__flags') is not None:
_OSA(self, '_Persistent__flags', None)
if clear:
try:
idict = _OGA(self, '__dict__')
except AttributeError:
pass
else:
idict.clear()
type_ = type(self)
# for backward-compatibility reason we release __slots__ only if
# class does not override __new__
if type_.__new__ is Persistent.__new__:
for slotname in Persistent._slotnames(self, _v_exclude=False):
try:
getattr(type_, slotname).__delete__(self)
except AttributeError:
# AttributeError means slot variable was not initialized at all -
# - we can simply skip its deletion.
pass
# Implementation detail: deactivating/invalidating
# updates the size of the cache (if we have one)
# by telling it this object no longer takes any bytes
# (-1 is a magic number to compensate for the implementation,
# which always adds one to the size given)
try:
cache = jar._cache
except AttributeError:
pass
else:
cache.update_object_size_estimation(_OGA(self, '_Persistent__oid'), -1)
# See notes in PickleCache.sweep for why we have to do this
cache._persistent_deactivate_ran = True
def _p_getattr(self, name):
""" See IPersistent.
"""
if name.startswith('_p_') or name in _SPECIAL_NAMES:
return True
self._p_activate()
self._p_accessed()
return False
def _p_setattr(self, name, value):
""" See IPersistent.
"""
if name.startswith('_p_'):
_OSA(self, name, value)
return True
self._p_activate()
self._p_accessed()
return False
def _p_delattr(self, name):
""" See IPersistent.
"""
if name.startswith('_p_'):
if name == '_p_oid' and self._p_is_in_cache(_OGA(self, '_Persistent__jar')):
# The C implementation forbids deleting the oid
# if we're already in a cache. Match its error message
raise ValueError('can not change _p_jar of cached object')
_ODA(self, name)
return True
self._p_activate()
self._p_accessed()
return False
# Helper methods: not APIs: we name them with '_p_' to bypass
# the __getattribute__ bit which bumps the cache.
def _p_register(self):
jar = _OGA(self, '_Persistent__jar')
if jar is not None and _OGA(self, '_Persistent__oid') is not None:
jar.register(self)
def _p_set_changed_flag(self, value):
if value:
before = _OGA(self, '_Persistent__flags')
after = before | _CHANGED
if before != after:
self._p_register()
_OSA(self, '_Persistent__flags', after)
else:
flags = _OGA(self, '_Persistent__flags')
flags &= ~_CHANGED
_OSA(self, '_Persistent__flags', flags)
def _p_accessed(self):
# Notify the jar's pickle cache that we have been accessed.
# This relies on what has been (until now) an implementation
# detail, the '_cache' attribute of the jar. We made it a
# private API to avoid the cycle of keeping a reference to
# the cache on the persistent object.
# The below is the equivalent of this, but avoids
# several recursive through __getattribute__, especially for _p_state,
# and benchmarks much faster
#
# if(self.__jar is None or
# self.__oid is None or
# self._p_state < 0 ): return
oga = _OGA
jar = oga(self, '_Persistent__jar')
if jar is None:
return
oid = oga(self, '_Persistent__oid')
if oid is None:
return
flags = oga(self, '_Persistent__flags')
if flags is None: # ghost
return
# The KeyError arises in ZODB: ZODB.serialize.ObjectWriter
# can assign a jar and an oid to newly seen persistent objects,
# but because they are newly created, they aren't in the
# pickle cache yet. There doesn't seem to be a way to distinguish
# that at this level, all we can do is catch it.
# The AttributeError arises in ZODB test cases
try:
jar._cache.mru(oid)
except (AttributeError,KeyError):
pass
def _p_is_in_cache(self, jar=None):
oid = _OGA(self, '_Persistent__oid')
if not oid:
return False
jar = jar or _OGA(self, '_Persistent__jar')
cache = getattr(jar, '_cache', None)
if cache is not None:
return cache.get(oid) is self
def __repr__(self):
p_repr_str = ''
p_repr = getattr(type(self), '_p_repr', None)
if p_repr is not None:
try:
return p_repr(self)
except Exception as e:
p_repr_str = ' _p_repr %r' % (e,)
oid = _OGA(self, '_Persistent__oid')
jar = _OGA(self, '_Persistent__jar')
oid_str = ''
jar_str = ''
if oid is not None:
try:
if isinstance(oid, bytes) and len(oid) == 8:
oid_str = ' oid 0x%x' % (_OID_UNPACK(oid)[0],)
else:
oid_str = ' oid %r' % (oid,)
except Exception as e:
oid_str = ' oid %r' % (e,)
if jar is not None:
try:
jar_str = ' in %r' % (jar,)
except Exception as e:
jar_str = ' in %r' % (e,)
return '<%s.%s object at 0x%x%s%s%s>' % (
# Match the C name for this exact class
type(self).__module__ if type(self) is not Persistent else 'persistent',
type(self).__name__, id(self),
oid_str, jar_str, p_repr_str
)
def _estimated_size_in_24_bits(value):
if value > 1073741696:
return 16777215
return (value//64) + 1
_SPECIAL_NAMES.update([intern('_Persistent' + x) for x in Persistent.__slots__])
@@ -0,0 +1,384 @@
##############################################################################
#
# Copyright (c) 2009 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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 gc
import weakref
from zope.interface import implementer
from persistent.interfaces import GHOST
from persistent.interfaces import IPickleCache
from persistent.interfaces import OID_TYPE
from persistent.interfaces import UPTODATE
from persistent.persistence import Persistent
from persistent.persistence import _estimated_size_in_24_bits
# Tests may modify this to add additional types
_CACHEABLE_TYPES = (type, Persistent)
_SWEEPABLE_TYPES = (Persistent,)
# On Jython, we need to explicitly ask it to monitor
# objects if we want a more deterministic GC
if hasattr(gc, 'monitorObject'): # pragma: no cover
_gc_monitor = gc.monitorObject
else:
def _gc_monitor(o):
pass
_OGA = object.__getattribute__
def _sweeping_ring(f):
# A decorator for functions in the PickleCache
# that are sweeping the entire ring (mutating it);
# serves as a pseudo-lock to not mutate the ring further
# in other functions
def locked(self, *args, **kwargs):
self._is_sweeping_ring = True
try:
return f(self, *args, **kwargs)
finally:
self._is_sweeping_ring = False
return locked
from .ring import Ring
@implementer(IPickleCache)
class PickleCache(object):
total_estimated_size = 0
cache_size_bytes = 0
# Set by functions that sweep the entire ring (via _sweeping_ring)
# Serves as a pseudo-lock
_is_sweeping_ring = False
def __init__(self, jar, target_size=0, cache_size_bytes=0):
# TODO: forward-port Dieter's bytes stuff
self.jar = jar
# We expect the jars to be able to have a pointer to
# us; this is a reference cycle, but certain
# aspects of invalidation and accessing depend on it.
# The actual Connection objects we're used with do set this
# automatically, but many test objects don't.
# TODO: track this on the persistent objects themself?
try:
jar._cache = self
except AttributeError:
# Some ZODB tests pass in an object that cannot have an _cache
pass
self.cache_size = target_size
self.drain_resistance = 0
self.non_ghost_count = 0
self.persistent_classes = {}
self.data = weakref.WeakValueDictionary()
self.ring = Ring()
self.cache_size_bytes = cache_size_bytes
# IPickleCache API
def __len__(self):
""" See IPickleCache.
"""
return (len(self.persistent_classes) +
len(self.data))
def __getitem__(self, oid):
""" See IPickleCache.
"""
value = self.data.get(oid)
if value is not None:
return value
return self.persistent_classes[oid]
def __setitem__(self, oid, value):
""" See IPickleCache.
"""
# The order of checks matters for C compatibility;
# the ZODB tests depend on this
# The C impl requires either a type or a Persistent subclass
if not isinstance(value, _CACHEABLE_TYPES):
raise TypeError("Cache values must be persistent objects.")
value_oid = value._p_oid
if not isinstance(oid, OID_TYPE) or not isinstance(value_oid, OID_TYPE):
raise TypeError('OID must be %s: key=%s _p_oid=%s' % (OID_TYPE, oid, value_oid))
if value_oid != oid:
raise ValueError("Cache key does not match oid")
# XXX
if oid in self.persistent_classes or oid in self.data:
# Have to be careful here, a GC might have just run
# and cleaned up the object
existing_data = self.get(oid)
if existing_data is not None and existing_data is not value:
# Raise the same type of exception as the C impl with the same
# message.
raise ValueError('A different object already has the same oid')
# Match the C impl: it requires a jar
jar = getattr(value, '_p_jar', None)
if jar is None and not isinstance(value, type):
raise ValueError("Cached object jar missing")
# It also requires that it cannot be cached more than one place
existing_cache = getattr(jar, '_cache', None)
if (existing_cache is not None
and existing_cache is not self
and existing_cache.data.get(oid) is not None):
raise ValueError("Object already in another cache")
if isinstance(value, type): # ZODB.persistentclass.PersistentMetaClass
self.persistent_classes[oid] = value
else:
self.data[oid] = value
_gc_monitor(value)
if _OGA(value, '_p_state') != GHOST and value not in self.ring:
self.ring.add(value)
self.non_ghost_count += 1
def __delitem__(self, oid):
""" See IPickleCache.
"""
if not isinstance(oid, OID_TYPE):
raise TypeError('OID must be %s: %s' % (OID_TYPE, oid))
if oid in self.persistent_classes:
del self.persistent_classes[oid]
else:
value = self.data.pop(oid)
self.ring.delete(value)
def get(self, oid, default=None):
""" See IPickleCache.
"""
value = self.data.get(oid, self)
if value is not self:
return value
return self.persistent_classes.get(oid, default)
def mru(self, oid):
""" See IPickleCache.
"""
if self._is_sweeping_ring:
# accessess during sweeping, such as with an
# overridden _p_deactivate, don't mutate the ring
# because that could leave it inconsistent
return False # marker return for tests
value = self.data[oid]
was_in_ring = value in self.ring
if not was_in_ring:
if _OGA(value, '_p_state') != GHOST:
self.ring.add(value)
self.non_ghost_count += 1
else:
self.ring.move_to_head(value)
def ringlen(self):
""" See IPickleCache.
"""
return len(self.ring)
def items(self):
""" See IPickleCache.
"""
return self.data.items()
def lru_items(self):
""" See IPickleCache.
"""
result = []
for obj in self.ring:
result.append((obj._p_oid, obj))
return result
def klass_items(self):
""" See IPickleCache.
"""
return self.persistent_classes.items()
def incrgc(self, ignored=None):
""" See IPickleCache.
"""
target = self.cache_size
if self.drain_resistance >= 1:
size = self.non_ghost_count
target2 = size - 1 - (size // self.drain_resistance)
if target2 < target:
target = target2
# return value for testing
return self._sweep(target, self.cache_size_bytes)
def full_sweep(self, target=None):
""" See IPickleCache.
"""
# return value for testing
return self._sweep(0)
minimize = full_sweep
def new_ghost(self, oid, obj):
""" See IPickleCache.
"""
if obj._p_oid is not None:
raise ValueError('Object already has oid')
if obj._p_jar is not None:
raise ValueError('Object already has jar')
if oid in self.persistent_classes or oid in self.data:
raise KeyError('Duplicate OID: %s' % oid)
obj._p_oid = oid
obj._p_jar = self.jar
if not isinstance(obj, type):
if obj._p_state != GHOST:
# The C implementation sets this stuff directly,
# but we delegate to the class. However, we must be
# careful to avoid broken _p_invalidate and _p_deactivate
# that don't call the super class. See ZODB's
# testConnection.doctest_proper_ghost_initialization_with_empty__p_deactivate
obj._p_invalidate_deactivate_helper(False)
self[oid] = obj
def reify(self, to_reify):
""" See IPickleCache.
"""
if isinstance(to_reify, OID_TYPE): #bytes
to_reify = [to_reify]
for oid in to_reify:
value = self[oid]
if value._p_state == GHOST:
value._p_activate()
self.non_ghost_count += 1
self.mru(oid)
def invalidate(self, to_invalidate):
""" See IPickleCache.
"""
if isinstance(to_invalidate, OID_TYPE):
self._invalidate(to_invalidate)
else:
for oid in to_invalidate:
self._invalidate(oid)
def debug_info(self):
result = []
for oid, klass in self.persistent_classes.items():
result.append((oid,
len(gc.get_referents(klass)),
type(klass).__name__,
klass._p_state,
))
for oid, value in self.data.items():
result.append((oid,
len(gc.get_referents(value)),
type(value).__name__,
value._p_state,
))
return result
def update_object_size_estimation(self, oid, new_size):
""" See IPickleCache.
"""
value = self.data.get(oid)
if value is not None:
# Recall that while the argument is given in bytes,
# we have to work with 64-block chunks (plus one)
# to match the C implementation. Hence the convoluted
# arithmetic
new_size_in_24 = _estimated_size_in_24_bits(new_size)
p_est_size_in_24 = value._Persistent__size
new_est_size_in_bytes = (new_size_in_24 - p_est_size_in_24) * 64
self.total_estimated_size += new_est_size_in_bytes
cache_drain_resistance = property(lambda self: self.drain_resistance)
cache_non_ghost_count = property(lambda self: self.non_ghost_count)
cache_data = property(lambda self: dict(self.data.items()))
cache_klass_count = property(lambda self: len(self.persistent_classes))
# Helpers
# Set to true when a deactivation happens in our code. For
# compatibility with the C implementation, we can only remove the
# node and decrement our non-ghost count if our implementation
# actually runs (broken subclasses can forget to call super; ZODB
# has tests for this). This gets set to false everytime we examine
# a node and checked afterwards. The C implementation has a very
# incestuous relationship between cPickleCache and cPersistence:
# the pickle cache calls _p_deactivate, which is responsible for
# both decrementing the non-ghost count and removing its node from
# the cache ring (and, if it gets deallocated, from the pickle
# cache's dictionary). We're trying to keep that to a minimum, but
# there's no way around it if we want full compatibility.
_persistent_deactivate_ran = False
@_sweeping_ring
def _sweep(self, target, target_size_bytes=0):
# To avoid mutating datastructures in place or making a copy,
# and to work efficiently with both the CFFI ring and the
# deque-based ring, we collect the objects and their indexes
# up front and then hand them off for ejection.
# We don't use enumerate because that's slow under PyPy
i = -1
to_eject = []
for value in self.ring:
if self.non_ghost_count <= target and (self.total_estimated_size <= target_size_bytes or not target_size_bytes):
break
i += 1
if value._p_state == UPTODATE:
# The C implementation will only evict things that are specifically
# in the up-to-date state
self._persistent_deactivate_ran = False
# sweeping an object out of the cache should also
# ghost it---that's what C does. This winds up
# calling `update_object_size_estimation`.
# Also in C, if this was the last reference to the object,
# it removes itself from the `data` dictionary.
# If we're under PyPy or Jython, we need to run a GC collection
# to make this happen...this is only noticeable though, when
# we eject objects. Also, note that we can only take any of these
# actions if our _p_deactivate ran, in case of buggy subclasses.
# see _persistent_deactivate_ran
value._p_deactivate()
if (self._persistent_deactivate_ran
# Test-cases sneak in non-Persistent objects, sigh, so naturally
# they don't cooperate (without this check a bunch of test_picklecache
# breaks)
or not isinstance(value, _SWEEPABLE_TYPES)):
to_eject.append((i, value))
self.non_ghost_count -= 1
ejected = len(to_eject)
if ejected:
self.ring.delete_all(to_eject)
return ejected
@_sweeping_ring
def _invalidate(self, oid):
value = self.data.get(oid)
if value is not None and value._p_state != GHOST:
value._p_invalidate()
was_in_ring = self.ring.delete(value)
self.non_ghost_count -= 1
elif oid in self.persistent_classes:
persistent_class = self.persistent_classes.pop(oid)
try:
# ZODB.persistentclass.PersistentMetaClass objects
# have this method and it must be called for transaction abort
# and other forms of invalidation to work
persistent_class._p_invalidate()
except AttributeError:
pass
@@ -0,0 +1,61 @@
/*****************************************************************************
Copyright (c) 2003 Zope Foundation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.1 (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$\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;
}
@@ -0,0 +1,66 @@
/*****************************************************************************
Copyright (c) 2003 Zope Foundation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.1 (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);
@@ -0,0 +1,217 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2015 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
#pylint: disable=W0212,E0211,W0622,E0213,W0221,E0239
from zope.interface import Interface
from zope.interface import implementer
class IRing(Interface):
"""Conceptually, a doubly-linked list for efficiently keeping track of least-
and most-recently used :class:`persistent.interfaces.IPersistent` objects.
This is meant to be used by the :class:`persistent.picklecache.PickleCache`
and should not be considered a public API. This interface documentation exists
to assist development of the picklecache and alternate implementations by
explaining assumptions and performance requirements.
"""
def __len__():
"""Return the number of persistent objects stored in the ring.
Should be constant time.
"""
def __contains__(object):
"""Answer whether the given persistent object is found in the ring.
Must not rely on object equality or object hashing, but only
identity or the `_p_oid`. Should be constant time.
"""
def add(object):
"""Add the persistent object to the ring as most-recently used.
When an object is in the ring, the ring holds a strong
reference to it so it can be deactivated later by the pickle
cache. Should be constant time.
The object should not already be in the ring, but this is not necessarily
enforced.
"""
def delete(object):
"""Remove the object from the ring if it is present.
Returns a true value if it was present and a false value
otherwise. An ideal implementation should be constant time,
but linear time is allowed.
"""
def move_to_head(object):
"""Place the object as the most recently used object in the ring.
The object should already be in the ring, but this is not
necessarily enforced, and attempting to move an object that is
not in the ring has undefined consequences. An ideal
implementation should be constant time, but linear time is
allowed.
"""
def delete_all(indexes_and_values):
"""Given a sequence of pairs (index, object), remove all of them from
the ring.
This should be equivalent to calling :meth:`delete` for each
value, but allows for a more efficient bulk deletion process.
If the index and object pairs do not match with the actual state of the
ring, this operation is undefined.
Should be at least linear time (not quadratic).
"""
def __iter__():
"""Iterate over each persistent object in the ring, in the order of least
recently used to most recently used.
Mutating the ring while an iteration is in progress has
undefined consequences.
"""
from collections import deque
@implementer(IRing)
class _DequeRing(object):
"""A ring backed by the :class:`collections.deque` class.
Operations are a mix of constant and linear time.
It is available on all platforms.
"""
__slots__ = ('ring', 'ring_oids')
def __init__(self):
self.ring = deque()
self.ring_oids = set()
def __len__(self):
return len(self.ring)
def __contains__(self, pobj):
return pobj._p_oid in self.ring_oids
def add(self, pobj):
self.ring.append(pobj)
self.ring_oids.add(pobj._p_oid)
def delete(self, pobj):
# Note that we do not use self.ring.remove() because that
# uses equality semantics and we don't want to call the persistent
# object's __eq__ method (which might wake it up just after we
# tried to ghost it)
for i, o in enumerate(self.ring):
if o is pobj:
del self.ring[i]
self.ring_oids.discard(pobj._p_oid)
return 1
def move_to_head(self, pobj):
self.delete(pobj)
self.add(pobj)
def delete_all(self, indexes_and_values):
for ix, value in reversed(indexes_and_values):
del self.ring[ix]
self.ring_oids.discard(value._p_oid)
def __iter__(self):
return iter(self.ring)
try:
from persistent import _ring
except ImportError: # pragma: no cover
_CFFIRing = None
else:
ffi = _ring.ffi
_FFI_RING = _ring.lib
_OGA = object.__getattribute__
_OSA = object.__setattr__
#pylint: disable=E1101
@implementer(IRing)
class _CFFIRing(object):
"""A ring backed by a C implementation. All operations are constant time.
It is only available on platforms with ``cffi`` installed.
"""
__slots__ = ('ring_home', 'ring_to_obj')
def __init__(self):
node = self.ring_home = ffi.new("CPersistentRing*")
node.r_next = node
node.r_prev = node
# In order for the CFFI objects to stay alive, we must keep
# a strong reference to them, otherwise they get freed. We must
# also keep strong references to the objects so they can be deactivated
self.ring_to_obj = dict()
def __len__(self):
return len(self.ring_to_obj)
def __contains__(self, pobj):
return getattr(pobj, '_Persistent__ring', self) in self.ring_to_obj
def add(self, pobj):
node = ffi.new("CPersistentRing*")
_FFI_RING.ring_add(self.ring_home, node)
self.ring_to_obj[node] = pobj
_OSA(pobj, '_Persistent__ring', node)
def delete(self, pobj):
its_node = getattr(pobj, '_Persistent__ring', None)
our_obj = self.ring_to_obj.pop(its_node, None)
if its_node is not None and our_obj is not None and its_node.r_next:
_FFI_RING.ring_del(its_node)
return 1
def move_to_head(self, pobj):
node = _OGA(pobj, '_Persistent__ring')
_FFI_RING.ring_move_to_head(self.ring_home, node)
def delete_all(self, indexes_and_values):
for _, value in indexes_and_values:
self.delete(value)
def iteritems(self):
head = self.ring_home
here = head.r_next
while here != head:
yield here
here = here.r_next
def __iter__(self):
ring_to_obj = self.ring_to_obj
for node in self.iteritems():
yield ring_to_obj[node]
# Export the best available implementation
Ring = _CFFIRing if _CFFIRing else _DequeRing
@@ -0,0 +1 @@
# package
@@ -0,0 +1,123 @@
##############################################################################
#
# Copyright (c) 2004 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
Examples for overriding attribute access methods.
"""
from persistent import Persistent
def _resettingJar():
from persistent.tests.utils import ResettingJar
return ResettingJar()
def _rememberingJar():
from persistent.tests.utils import RememberingJar
return RememberingJar()
class OverridesGetattr(Persistent):
"""Example of overriding __getattr__
"""
def __getattr__(self, name):
"""Get attributes that can't be gotten the usual way
"""
# Don't pretend we have any special attributes.
if name.startswith("__") and name.endswrith("__"):
raise AttributeError(name) # pragma: no cover
return name.upper(), self._p_changed
class VeryPrivate(Persistent):
"""Example of overriding __getattribute__, __setattr__, and __delattr__
"""
def __init__(self, **kw):
self.__dict__['__secret__'] = kw.copy()
def __getattribute__(self, name):
"""Get an attribute value
See the very important note in the comment below!
"""
#################################################################
# 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
"""
#################################################################
# 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
"""
#################################################################
# 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
@@ -0,0 +1,109 @@
##############################################################################
#
# Copyright (c) 2003 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
# Example objects for pickling.
from persistent import Persistent
def print_dict(d):
d = sorted(d.items())
print('{%s}' % (', '.join(
[('%r: %r' % (k, v)) for (k, v) in d]
)))
def cmpattrs(self, other, *attrs):
result = 0
for attr in attrs:
if attr[:3] in ('_v_', '_p_'):
raise AssertionError("_v_ and _p_ attrs not allowed")
lhs = getattr(self, attr, None)
rhs = getattr(other, attr, None)
result += lhs != rhs
return result
class Simple(Persistent):
def __init__(self, name, **kw):
self.__name__ = name
self.__dict__.update(kw)
self._v_favorite_color = 'blue'
self._p_foo = 'bar'
@property
def _attrs(self):
return list(self.__dict__.keys())
def __eq__(self, other):
return cmpattrs(self, other, '__class__', *self._attrs) == 0
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
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
@property
def _attrs(self):
raise NotImplementedError()
def __eq__(self, other):
return cmpattrs(self, other, '__class__', *self._attrs) == 0
class SubSlotted(Slotted):
__slots__ = 's3', 's4'
def __init__(self, s1, s2, s3):
Slotted.__init__(self, s1, s2)
self.s3 = s3
@property
def _attrs(self):
return ('s1', 's2', 's3', 's4')
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'
@property
def _attrs(self):
return ['s1', 's2', 's3', 's4'] + list(self.__dict__.keys())
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
"""
Tests for the documentation.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
# disable: accessing protected members, too many methods
# pylint: disable=W0212,R0904
import os.path
import unittest
import doctest
import manuel.capture
import manuel.codeblock
import manuel.doctest
import manuel.ignore
import manuel.testing
def test_suite():
here = os.path.dirname(os.path.abspath(__file__))
while not os.path.exists(os.path.join(here, 'setup.py')):
prev, here = here, os.path.dirname(here)
if here == prev:
# Let's avoid infinite loops at root
raise AssertionError('could not find my setup.py')
docs = os.path.join(here, 'docs', 'api')
files_to_test = (
'cache.rst',
'attributes.rst',
'pickling.rst',
)
paths = [os.path.join(docs, f) for f in files_to_test]
m = manuel.ignore.Manuel()
m += manuel.doctest.Manuel(optionflags=(
doctest.NORMALIZE_WHITESPACE
| doctest.ELLIPSIS
| doctest.IGNORE_EXCEPTION_DETAIL
))
m += manuel.codeblock.Manuel()
m += manuel.capture.Manuel()
suite = unittest.TestSuite()
suite.addTest(
manuel.testing.TestSuite(
m,
*paths
)
)
return suite
@@ -0,0 +1,332 @@
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
"""Tests for PersistentList
"""
import unittest
l0 = []
l1 = [0]
l2 = [0, 1]
# pylint:disable=protected-access
class OtherList:
def __init__(self, initlist):
self.__data = initlist
def __len__(self):
return len(self.__data)
def __getitem__(self, i):
return self.__data[i]
class TestPList(unittest.TestCase):
def _getTargetClass(self):
from persistent.list import PersistentList
return PersistentList
def _makeJar(self):
class Jar(object):
def register(self, obj):
"no-op"
return Jar()
def _makeOne(self, *args):
inst = self._getTargetClass()(*args)
inst._p_jar = self._makeJar()
return inst
def test_volatile_attributes_not_persisted(self):
# http://www.zope.org/Collectors/Zope/2052
m = self._getTargetClass()()
m.foo = 'bar'
m._v_baz = 'qux'
state = m.__getstate__()
self.assertTrue('foo' in state)
self.assertFalse('_v_baz' in state)
def testTheWorld(self):
from persistent._compat import PYTHON2
# Test constructors
pl = self._getTargetClass()
u = pl()
u0 = pl(l0)
u1 = pl(l1)
u2 = pl(l2)
uu = pl(u)
uu0 = pl(u0)
uu1 = pl(u1)
uu2 = pl(u2)
pl(tuple(u))
pl(OtherList(u0))
pl("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)")
# Test __cmp__ and __len__
try:
cmp
except NameError:
def cmp(a, b):
if a == b:
return 0
if a < b:
return -1
return 1
def mycmp(a, b):
r = cmp(a, b)
if r < 0:
return -1
if r > 0:
return 1
return r
to_test = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2]
for a in to_test:
for b in to_test:
eq(mycmp(a, b), mycmp(len(a), len(b)),
"mycmp(a, b) == mycmp(len(a), len(b))")
# Test __getitem__
for i, val in enumerate(u2):
eq(val, i, "u2[i] == i")
# Test __setitem__
uu2[0] = 0
uu2[1] = 100
with self.assertRaises(IndexError):
uu2[2] = 200
# Test __delitem__
del uu2[1]
del uu2[0]
with self.assertRaises(IndexError):
del uu2[0]
# 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.assertTrue(i in u2, "i in u2")
for i in min(u2)-1, max(u2)+1:
self.assertTrue(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.assertTrue(u1 + [] == [] + u1 == u1, "u1 + [] == [] + u1 == u1")
self.assertTrue(u1 + [1] == u2, "u1 + [1] == u2")
#self.assertTrue([-1] + u1 == [-1, 0], "[-1] + u1 == [-1, 0]")
self.assertTrue(u2 == u2*1 == 1*u2, "u2 == u2*1 == 1*u2")
self.assertTrue(u2+u2 == u2*2 == 2*u2, "u2+u2 == u2*2 == 2*u2")
self.assertTrue(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 = pl([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")
with self.assertRaises(ValueError):
u2.index(2)
# Test reverse
u = u2[:]
u.reverse()
eq(u, [1, 0], "u == [1, 0]")
u.reverse()
eq(u, u2, "u == u2")
# Test sort
u = pl([1, 0])
u.sort()
eq(u, u2, "u == u2")
# Test keyword arguments to sort
if PYTHON2: # pragma: no cover
u.sort(cmp=lambda x, y: cmp(y, x))
eq(u, [1, 0], "u == [1, 0]")
u.sort(key=lambda x: -x)
eq(u, [1, 0], "u == [1, 0]")
u.sort(reverse=True)
eq(u, [1, 0], "u == [1, 0]")
# Passing any other keyword arguments results in a TypeError
with self.assertRaises(TypeError):
u.sort(blah=True)
# 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_setslice(self):
inst = self._makeOne()
self.assertFalse(inst._p_changed)
inst[:] = [1, 2, 3]
self.assertEqual(inst, [1, 2, 3])
self.assertTrue(inst._p_changed)
def test_delslice(self):
inst = self._makeOne([1, 2, 3])
self.assertFalse(inst._p_changed)
self.assertEqual(inst, [1, 2, 3])
del inst[:]
self.assertTrue(inst._p_changed)
def test_iadd(self):
inst = self._makeOne()
self.assertFalse(inst._p_changed)
inst += [1, 2, 3]
self.assertEqual(inst, [1, 2, 3])
self.assertTrue(inst._p_changed)
def test_extend(self):
inst = self._makeOne()
self.assertFalse(inst._p_changed)
inst.extend([1, 2, 3])
self.assertEqual(inst, [1, 2, 3])
self.assertTrue(inst._p_changed)
def test_imul(self):
inst = self._makeOne([1])
self.assertFalse(inst._p_changed)
inst *= 2
self.assertEqual(inst, [1, 1])
self.assertTrue(inst._p_changed)
def test_append(self):
inst = self._makeOne()
self.assertFalse(inst._p_changed)
inst.append(1)
self.assertEqual(inst, [1])
self.assertTrue(inst._p_changed)
def test_insert(self):
inst = self._makeOne()
self.assertFalse(inst._p_changed)
inst.insert(0, 1)
self.assertEqual(inst, [1])
self.assertTrue(inst._p_changed)
def test_remove(self):
inst = self._makeOne([1])
self.assertFalse(inst._p_changed)
inst.remove(1)
self.assertEqual(inst, [])
self.assertTrue(inst._p_changed)
def test_reverse(self):
inst = self._makeOne([2, 1])
self.assertFalse(inst._p_changed)
inst.reverse()
self.assertEqual(inst, [1, 2])
self.assertTrue(inst._p_changed)
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,237 @@
##############################################################################
#
# Copyright (c) Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
# pylint:disable=blacklisted-name, protected-access
class Test_default(unittest.TestCase):
def _getTargetClass(self):
from persistent.mapping import default
return default
def _makeOne(self, func):
return self._getTargetClass()(func)
def test___get___from_class(self):
def _test(inst):
raise AssertionError("Must not be caled")
descr = self._makeOne(_test)
class Foo(object):
testing = descr
self.assertIs(Foo.testing, descr)
def test___get___from_instance(self):
_called_with = []
def _test(inst):
_called_with.append(inst)
return 'TESTING'
descr = self._makeOne(_test)
class Foo(object):
testing = descr
foo = Foo()
self.assertEqual(foo.testing, 'TESTING')
self.assertEqual(_called_with, [foo])
class PersistentMappingTests(unittest.TestCase):
def _getTargetClass(self):
from persistent.mapping import PersistentMapping
return PersistentMapping
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
def test_volatile_attributes_not_persisted(self):
# http://www.zope.org/Collectors/Zope/2052
m = self._makeOne()
m.foo = 'bar'
m._v_baz = 'qux'
state = m.__getstate__()
self.assertTrue('foo' in state)
self.assertFalse('_v_baz' in state)
def testTheWorld(self):
from persistent._compat import PYTHON2
# Test constructors
l0 = {}
l1 = {0:0}
l2 = {0:0, 1:1}
u = self._makeOne()
u0 = self._makeOne(l0)
u1 = self._makeOne(l1)
u2 = self._makeOne(l2)
uu = self._makeOne(u)
uu0 = self._makeOne(u0)
uu1 = self._makeOne(u1)
uu2 = self._makeOne(u2)
class OtherMapping(dict):
def __init__(self, initmapping):
self.__data = initmapping
def items(self):
raise AssertionError("Not called")
self._makeOne(OtherMapping(u0))
self._makeOne([(0, 0), (1, 1)])
# Test __repr__
eq = self.assertEqual
eq(str(u0), str(l0), "str(u0) == str(l0)")
eq(repr(u1), repr(l1), "repr(u1) == repr(l1)")
# Test __cmp__ and __len__
try:
cmp
except NameError:
def cmp(a, b):
if a == b:
return 0
if hasattr(a, 'items'):
a = sorted(a.items())
b = sorted(b.items())
if a < b:
return -1
return 1
def mycmp(a, b):
r = cmp(a, b)
if r < 0:
return -1
if r > 0:
return 1
return r
to_test = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2]
for a in to_test:
for b in to_test:
eq(mycmp(a, b), mycmp(len(a), len(b)),
"mycmp(a, b) == mycmp(len(a), len(b))")
# Test __getitem__
for i, val in enumerate(u2):
eq(val, i, "u2[i] == i")
# Test get
for i in range(len(u2)):
eq(u2.get(i), i, "u2.get(i) == i")
eq(u2.get(i, 5), i, "u2.get(i, 5) == i")
for i in min(u2)-1, max(u2)+1:
eq(u2.get(i), None, "u2.get(i) == None")
eq(u2.get(i, 5), 5, "u2.get(i, 5) == 5")
# Test __setitem__
uu2[0] = 0
uu2[1] = 100
uu2[2] = 200
# Test __delitem__
del uu2[1]
del uu2[0]
with self.assertRaises(KeyError):
del uu2[0]
# Test __contains__
for i in u2:
self.assertTrue(i in u2, "i in u2")
for i in min(u2)-1, max(u2)+1:
self.assertTrue(i not in u2, "i not in u2")
# Test update
l = {"a":"b"}
u = self._makeOne(l)
u.update(u2)
for i in u:
self.assertTrue(i in l or i in u2, "i in l or i in u2")
for i in l:
self.assertTrue(i in u, "i in u")
for i in u2:
self.assertTrue(i in u, "i in u")
# Test setdefault
x = u2.setdefault(0, 5)
eq(x, 0, "u2.setdefault(0, 5) == 0")
x = u2.setdefault(5, 5)
eq(x, 5, "u2.setdefault(5, 5) == 5")
self.assertTrue(5 in u2, "5 in u2")
# Test pop
x = u2.pop(1)
eq(x, 1, "u2.pop(1) == 1")
self.assertTrue(1 not in u2, "1 not in u2")
with self.assertRaises(KeyError):
u2.pop(1)
x = u2.pop(1, 7)
eq(x, 7, "u2.pop(1, 7) == 7")
# Test popitem
items = list(u2.items())
key, value = u2.popitem()
self.assertTrue((key, value) in items, "key, value in items")
self.assertTrue(key not in u2, "key not in u2")
# Test clear
u2.clear()
eq(u2, {}, "u2 == {}")
def test___repr___converts_legacy_container_attr(self):
# In the past, PM used a _container attribute. For some time, the
# implementation continued to use a _container attribute in pickles
# (__get/setstate__) to be compatible with older releases. This isn't
# really necessary any more. In fact, releases for which this might
# matter can no longer share databases with current releases. Because
# releases as recent as 3.9.0b5 still use _container in saved state, we
# need to accept such state, but we stop producing it.
pm = self._makeOne()
self.assertEqual(pm.__dict__, {'data': {}})
# Make it look like an older instance
pm.__dict__.clear()
pm.__dict__['_container'] = {'a': 1}
self.assertEqual(pm.__dict__, {'_container': {'a': 1}})
pm._p_changed = 0
self.assertEqual(repr(pm), "{'a': 1}")
self.assertEqual(pm.__dict__, {'data': {'a': 1}})
self.assertEqual(pm.__getstate__(), {'data': {'a': 1}})
class Test_legacy_PersistentDict(unittest.TestCase):
def _getTargetClass(self):
from persistent.dict import PersistentDict
return PersistentDict
def test_PD_is_alias_to_PM(self):
from persistent.mapping import PersistentMapping
self.assertTrue(self._getTargetClass() is PersistentMapping)
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,996 @@
##############################################################################
#
# Copyright (c) 2009 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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 gc
import os
import platform
import sys
import unittest
from persistent.interfaces import UPTODATE
_is_pypy = platform.python_implementation() == 'PyPy'
_is_jython = 'java' in sys.platform
_marker = object()
class PickleCacheTests(unittest.TestCase):
# py2/3 compat
assertRaisesRegex = getattr(unittest.TestCase,
'assertRaisesRegex',
unittest.TestCase.assertRaisesRegexp)
def setUp(self):
import persistent.picklecache
self.orig_types = persistent.picklecache._CACHEABLE_TYPES
persistent.picklecache._CACHEABLE_TYPES += (DummyPersistent,)
def tearDown(self):
import persistent.picklecache
persistent.picklecache._CACHEABLE_TYPES = self.orig_types
def _getTargetClass(self):
from persistent.picklecache import PickleCache
return PickleCache
def _makeOne(self, jar=None, target_size=10):
if jar is None:
jar = DummyConnection()
return self._getTargetClass()(jar, target_size)
def _makePersist(self, state=None, oid=b'foo', jar=_marker):
from persistent.interfaces import GHOST
if state is None:
state = GHOST
if jar is _marker:
jar = DummyConnection()
persist = DummyPersistent()
persist._p_state = state
persist._p_oid = oid
persist._p_jar = jar
return persist
def test_class_conforms_to_IPickleCache(self):
from zope.interface.verify import verifyClass
from persistent.interfaces import IPickleCache
verifyClass(IPickleCache, self._getTargetClass())
def test_instance_conforms_to_IPickleCache(self):
from zope.interface.verify import verifyObject
from persistent.interfaces import IPickleCache
verifyObject(IPickleCache, self._makeOne())
def test_empty(self):
cache = self._makeOne()
self.assertEqual(len(cache), 0)
self.assertEqual(_len(cache.items()), 0)
self.assertEqual(_len(cache.klass_items()), 0)
self.assertEqual(cache.ringlen(), 0)
self.assertEqual(len(cache.lru_items()), 0)
self.assertEqual(cache.cache_size, 10)
self.assertEqual(cache.cache_drain_resistance, 0)
self.assertEqual(cache.cache_non_ghost_count, 0)
self.assertEqual(dict(cache.cache_data), {})
self.assertEqual(cache.cache_klass_count, 0)
def test___getitem___nonesuch_raises_KeyError(self):
cache = self._makeOne()
self.assertRaises(KeyError, lambda: cache['nonesuch'])
def test_get_nonesuch_no_default(self):
cache = self._makeOne()
self.assertEqual(cache.get('nonesuch'), None)
def test_get_nonesuch_w_default(self):
cache = self._makeOne()
default = object
self.assertTrue(cache.get('nonesuch', default) is default)
def test___setitem___non_string_oid_raises_TypeError(self):
cache = self._makeOne()
with self.assertRaises(TypeError):
cache[object()] = self._makePersist()
def test___setitem___duplicate_oid_same_obj(self):
KEY = b'original'
cache = self._makeOne()
original = self._makePersist(oid=KEY)
cache[KEY] = original
cache[KEY] = original
def test___setitem___duplicate_oid_raises_ValueError(self):
KEY = b'original'
cache = self._makeOne()
original = self._makePersist(oid=KEY)
cache[KEY] = original
duplicate = self._makePersist(oid=KEY)
with self.assertRaises(ValueError):
cache[KEY] = duplicate
def test___setitem___ghost(self):
from persistent.interfaces import GHOST
KEY = b'ghost'
cache = self._makeOne()
ghost = self._makePersist(state=GHOST, oid=KEY)
cache[KEY] = ghost
self.assertEqual(len(cache), 1)
items = list(cache.items())
self.assertEqual(len(items), 1)
self.assertEqual(_len(cache.klass_items()), 0)
self.assertEqual(items[0][0], KEY)
self.assertEqual(cache.ringlen(), 0)
self.assertTrue(items[0][1] is ghost)
self.assertTrue(cache[KEY] is ghost)
def test___setitem___mismatch_key_oid(self):
KEY = b'uptodate'
cache = self._makeOne()
uptodate = self._makePersist(state=UPTODATE)
with self.assertRaises(ValueError):
cache[KEY] = uptodate
def test___setitem___non_ghost(self):
KEY = b'uptodate'
cache = self._makeOne()
uptodate = self._makePersist(state=UPTODATE, oid=KEY)
cache[KEY] = uptodate
self.assertEqual(len(cache), 1)
items = list(cache.items())
self.assertEqual(len(items), 1)
self.assertEqual(_len(cache.klass_items()), 0)
self.assertEqual(items[0][0], KEY)
self.assertEqual(cache.ringlen(), 1)
self.assertTrue(items[0][1] is uptodate)
self.assertTrue(cache[KEY] is uptodate)
self.assertTrue(cache.get(KEY) is uptodate)
def test___setitem___persistent_class(self):
KEY = b'pclass'
class pclass(object):
_p_oid = KEY
cache = self._makeOne()
cache[KEY] = pclass
kitems = list(cache.klass_items())
self.assertEqual(len(cache), 1)
self.assertEqual(_len(cache.items()), 0)
self.assertEqual(len(kitems), 1)
self.assertEqual(kitems[0][0], KEY)
self.assertTrue(kitems[0][1] is pclass)
self.assertTrue(cache[KEY] is pclass)
self.assertTrue(cache.get(KEY) is pclass)
def test___delitem___non_string_oid_raises_TypeError(self):
cache = self._makeOne()
with self.assertRaises(TypeError):
del cache[object()]
def test___delitem___nonesuch_raises_KeyError(self):
cache = self._makeOne()
with self.assertRaises(KeyError):
del cache[b'nonesuch']
def test___delitem___w_persistent_class(self):
KEY = b'pclass'
cache = self._makeOne()
class pclass(object):
_p_oid = KEY
cache = self._makeOne()
cache[KEY] = pclass
del cache[KEY]
self.assertTrue(cache.get(KEY, self) is self)
self.assertFalse(KEY in cache.persistent_classes)
self.assertEqual(cache.ringlen(), 0)
def test___delitem___w_normal_object(self):
KEY = b'uptodate'
cache = self._makeOne()
uptodate = self._makePersist(state=UPTODATE, oid=KEY)
cache[KEY] = uptodate
del cache[KEY]
self.assertTrue(cache.get(KEY, self) is self)
def test___delitem___w_ghost(self):
from persistent.interfaces import GHOST
cache = self._makeOne()
KEY = b'ghost'
ghost = self._makePersist(state=GHOST, oid=KEY)
cache[KEY] = ghost
del cache[KEY]
self.assertTrue(cache.get(KEY, self) is self)
def test___delitem___w_remaining_object(self):
cache = self._makeOne()
REMAINS = b'remains'
UPTODATE = b'uptodate'
remains = self._makePersist(state=UPTODATE, oid=REMAINS)
uptodate = self._makePersist(state=UPTODATE, oid=UPTODATE)
cache[REMAINS] = remains
cache[UPTODATE] = uptodate
del cache[UPTODATE]
self.assertTrue(cache.get(UPTODATE, self) is self)
self.assertTrue(cache.get(REMAINS, self) is remains)
def test_lruitems(self):
cache = self._makeOne()
ONE = b'one'
TWO = b'two'
THREE = b'three'
cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
cache[TWO] = self._makePersist(oid=b'two', state=UPTODATE)
cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
items = cache.lru_items()
self.assertEqual(_len(items), 3)
self.assertEqual(items[0][0], ONE)
self.assertEqual(items[1][0], TWO)
self.assertEqual(items[2][0], THREE)
def test_mru_nonesuch_raises_KeyError(self):
cache = self._makeOne()
self.assertRaises(KeyError, cache.mru, b'nonesuch')
def test_mru_normal(self):
ONE = b'one'
TWO = b'two'
THREE = b'three'
cache = self._makeOne()
cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
cache[TWO] = self._makePersist(oid=b'two', state=UPTODATE)
cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
cache.mru(TWO)
self.assertEqual(cache.ringlen(), 3)
items = cache.lru_items()
self.assertEqual(_len(items), 3)
self.assertEqual(items[0][0], ONE)
self.assertEqual(items[1][0], THREE)
self.assertEqual(items[2][0], TWO)
def test_mru_ghost(self):
from persistent.interfaces import GHOST
ONE = b'one'
TWO = b'two'
THREE = b'three'
cache = self._makeOne()
cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
two = cache[TWO] = self._makePersist(oid=b'two', state=GHOST)
# two must live to survive gc
self.assertIsNotNone(two)
cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
cache.mru(TWO)
self.assertEqual(cache.ringlen(), 2)
items = cache.lru_items()
self.assertEqual(_len(items), 2)
self.assertEqual(items[0][0], ONE)
self.assertEqual(items[1][0], THREE)
def test_mru_was_ghost_now_active(self):
from persistent.interfaces import GHOST
ONE = b'one'
TWO = b'two'
THREE = b'three'
cache = self._makeOne()
cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
two = cache[TWO] = self._makePersist(oid=b'two', state=GHOST)
cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
two._p_state = UPTODATE
cache.mru(TWO)
self.assertEqual(cache.ringlen(), 3)
items = cache.lru_items()
self.assertEqual(_len(items), 3)
self.assertEqual(items[0][0], ONE)
self.assertEqual(items[1][0], THREE)
self.assertEqual(items[2][0], TWO)
def test_mru_first(self):
ONE = b'one'
TWO = b'two'
THREE = b'three'
cache = self._makeOne()
cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
cache[TWO] = self._makePersist(oid=b'two', state=UPTODATE)
cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
cache.mru(ONE)
self.assertEqual(cache.ringlen(), 3)
items = cache.lru_items()
self.assertEqual(_len(items), 3)
self.assertEqual(items[0][0], TWO)
self.assertEqual(items[1][0], THREE)
self.assertEqual(items[2][0], ONE)
def test_mru_last(self):
ONE = b'one'
TWO = b'two'
THREE = b'three'
cache = self._makeOne()
cache[ONE] = self._makePersist(oid=b'one', state=UPTODATE)
cache[TWO] = self._makePersist(oid=b'two', state=UPTODATE)
cache[THREE] = self._makePersist(oid=b'three', state=UPTODATE)
cache.mru(THREE)
self.assertEqual(cache.ringlen(), 3)
items = cache.lru_items()
self.assertEqual(_len(items), 3)
self.assertEqual(items[0][0], ONE)
self.assertEqual(items[1][0], TWO)
self.assertEqual(items[2][0], THREE)
def _numbered_oid(self, i):
# Python 3.4 doesn't support % on bytes,
# so we go the long way
oid_s = 'oid_%04d' % i
return oid_s.encode('ascii')
def _populate_cache(self, cache, count=100,
state_0=UPTODATE,
state_rest=UPTODATE):
oids = []
for i in range(100):
oid = self._numbered_oid(i)
oids.append(oid)
state = state_0 if i == 0 else state_rest
cache[oid] = self._makePersist(oid=oid, state=state)
return oids
def test_incrgc_simple(self):
cache = self._makeOne()
oids = self._populate_cache(cache)
self.assertEqual(cache.cache_non_ghost_count, 100)
cache.incrgc()
gc.collect() # banish the ghosts who are no longer in the ring
self.assertEqual(cache.cache_non_ghost_count, 10)
items = cache.lru_items()
self.assertEqual(_len(items), 10)
self.assertEqual(items[0][0], b'oid_0090')
self.assertEqual(items[1][0], b'oid_0091')
self.assertEqual(items[2][0], b'oid_0092')
self.assertEqual(items[3][0], b'oid_0093')
self.assertEqual(items[4][0], b'oid_0094')
self.assertEqual(items[5][0], b'oid_0095')
self.assertEqual(items[6][0], b'oid_0096')
self.assertEqual(items[7][0], b'oid_0097')
self.assertEqual(items[8][0], b'oid_0098')
self.assertEqual(items[9][0], b'oid_0099')
for oid in oids[:90]:
self.assertTrue(cache.get(oid) is None)
for oid in oids[90:]:
self.assertFalse(cache.get(oid) is None)
def test_incrgc_w_smaller_drain_resistance(self):
cache = self._makeOne()
cache.drain_resistance = 2
self._populate_cache(cache)
self.assertEqual(cache.cache_non_ghost_count, 100)
cache.incrgc()
self.assertEqual(cache.cache_non_ghost_count, 10)
def test_incrgc_w_larger_drain_resistance(self):
cache = self._makeOne()
cache.drain_resistance = 2
cache.cache_size = 90
self._populate_cache(cache)
self.assertEqual(cache.cache_non_ghost_count, 100)
cache.incrgc()
self.assertEqual(cache.cache_non_ghost_count, 49)
def test_full_sweep(self):
cache = self._makeOne()
oids = self._populate_cache(cache)
self.assertEqual(cache.cache_non_ghost_count, 100)
cache.full_sweep()
gc.collect() # banish the ghosts who are no longer in the ring
self.assertEqual(cache.cache_non_ghost_count, 0)
for oid in oids:
self.assertTrue(cache.get(oid) is None)
def test_full_sweep_w_sticky(self):
from persistent.interfaces import STICKY
cache = self._makeOne()
oids = self._populate_cache(cache, state_0=STICKY)
self.assertEqual(cache.cache_non_ghost_count, 100)
cache.full_sweep()
gc.collect() # banish the ghosts who are no longer in the ring
self.assertEqual(cache.cache_non_ghost_count, 1)
self.assertTrue(cache.get(oids[0]) is not None)
for oid in oids[1:]:
self.assertTrue(cache.get(oid) is None)
def test_full_sweep_w_changed(self):
from persistent.interfaces import CHANGED
cache = self._makeOne()
oids = self._populate_cache(cache, state_0=CHANGED)
self.assertEqual(cache.cache_non_ghost_count, 100)
cache.full_sweep()
gc.collect() # banish the ghosts who are no longer in the ring
self.assertEqual(cache.cache_non_ghost_count, 1)
self.assertTrue(cache.get(oids[0]) is not None)
for oid in oids[1:]:
self.assertTrue(cache.get(oid) is None)
def test_minimize(self):
cache = self._makeOne()
oids = self._populate_cache(cache)
self.assertEqual(cache.cache_non_ghost_count, 100)
cache.minimize()
gc.collect() # banish the ghosts who are no longer in the ring
self.assertEqual(cache.cache_non_ghost_count, 0)
for oid in oids:
self.assertTrue(cache.get(oid) is None)
def test_minimize_turns_into_ghosts(self):
from persistent.interfaces import GHOST
cache = self._makeOne()
oid = self._numbered_oid(1)
obj = cache[oid] = self._makePersist(oid=oid, state=UPTODATE)
self.assertEqual(cache.cache_non_ghost_count, 1)
cache.minimize()
gc.collect() # banish the ghosts who are no longer in the ring
self.assertEqual(cache.cache_non_ghost_count, 0)
self.assertEqual(obj._p_state, GHOST)
def test_new_ghost_non_persistent_object(self):
cache = self._makeOne()
with self.assertRaises(AttributeError):
cache.new_ghost(b'123', object())
def test_new_ghost_obj_already_has_oid(self):
from persistent.interfaces import GHOST
candidate = self._makePersist(oid=b'123', state=GHOST)
cache = self._makeOne()
with self.assertRaises(ValueError):
cache.new_ghost(b'123', candidate)
def test_new_ghost_obj_already_has_jar(self):
cache = self._makeOne()
candidate = self._makePersist(oid=None, jar=object())
with self.assertRaises(ValueError):
cache.new_ghost(b'123', candidate)
def test_new_ghost_obj_already_in_cache(self):
KEY = b'123'
cache = self._makeOne()
candidate = self._makePersist(oid=KEY)
cache[KEY] = candidate
# Now, normally we can't get in the cache without an oid and jar
# (the C implementation doesn't allow it), so if we try to create
# a ghost, we get the value error
self.assertRaises(ValueError, cache.new_ghost, KEY, candidate)
candidate._p_oid = None
self.assertRaises(ValueError, cache.new_ghost, KEY, candidate)
# if we're sneaky and remove the OID and jar, then we get the duplicate
# key error
candidate._p_jar = None
self.assertRaises(KeyError, cache.new_ghost, KEY, candidate)
def test_new_ghost_success_already_ghost(self):
from persistent.interfaces import GHOST
KEY = b'123'
cache = self._makeOne()
candidate = self._makePersist(oid=None, jar=None)
cache.new_ghost(KEY, candidate)
self.assertTrue(cache.get(KEY) is candidate)
self.assertEqual(candidate._p_oid, KEY)
self.assertEqual(candidate._p_jar, cache.jar)
self.assertEqual(candidate._p_state, GHOST)
def test_new_ghost_success_not_already_ghost(self):
from persistent.interfaces import GHOST
KEY = b'123'
cache = self._makeOne()
candidate = self._makePersist(oid=None, jar=None, state=UPTODATE)
cache.new_ghost(KEY, candidate)
self.assertTrue(cache.get(KEY) is candidate)
self.assertEqual(candidate._p_oid, KEY)
self.assertEqual(candidate._p_jar, cache.jar)
self.assertEqual(candidate._p_state, GHOST)
def test_new_ghost_w_pclass_non_ghost(self):
KEY = b'123'
class Pclass(object):
_p_oid = None
_p_jar = None
cache = self._makeOne()
cache.new_ghost(KEY, Pclass)
self.assertTrue(cache.get(KEY) is Pclass)
self.assertTrue(cache.persistent_classes[KEY] is Pclass)
self.assertEqual(Pclass._p_oid, KEY)
self.assertEqual(Pclass._p_jar, cache.jar)
def test_new_ghost_w_pclass_ghost(self):
KEY = b'123'
class Pclass(object):
_p_oid = None
_p_jar = None
cache = self._makeOne()
cache.new_ghost(KEY, Pclass)
self.assertTrue(cache.get(KEY) is Pclass)
self.assertTrue(cache.persistent_classes[KEY] is Pclass)
self.assertEqual(Pclass._p_oid, KEY)
self.assertEqual(Pclass._p_jar, cache.jar)
def test_reify_miss_single(self):
KEY = b'123'
cache = self._makeOne()
self.assertRaises(KeyError, cache.reify, KEY)
def test_reify_miss_multiple(self):
KEY = b'123'
KEY2 = b'456'
cache = self._makeOne()
self.assertRaises(KeyError, cache.reify, [KEY, KEY2])
def test_reify_hit_single_ghost(self):
from persistent.interfaces import GHOST
KEY = b'123'
cache = self._makeOne()
candidate = self._makePersist(oid=KEY, jar=cache.jar, state=GHOST)
cache[KEY] = candidate
self.assertEqual(cache.ringlen(), 0)
cache.reify(KEY)
self.assertEqual(cache.ringlen(), 1)
items = cache.lru_items()
self.assertEqual(items[0][0], KEY)
self.assertTrue(items[0][1] is candidate)
self.assertEqual(candidate._p_state, UPTODATE)
def test_reify_hit_single_non_ghost(self):
KEY = b'123'
cache = self._makeOne()
candidate = self._makePersist(oid=KEY, jar=cache.jar, state=UPTODATE)
cache[KEY] = candidate
self.assertEqual(cache.ringlen(), 1)
cache.reify(KEY)
self.assertEqual(cache.ringlen(), 1)
self.assertEqual(candidate._p_state, UPTODATE)
def test_reify_hit_multiple_mixed(self):
from persistent.interfaces import GHOST
KEY = b'123'
KEY2 = b'456'
cache = self._makeOne()
c1 = self._makePersist(oid=KEY, jar=cache.jar, state=GHOST)
cache[KEY] = c1
c2 = self._makePersist(oid=KEY2, jar=cache.jar, state=UPTODATE)
cache[KEY2] = c2
self.assertEqual(cache.ringlen(), 1)
cache.reify([KEY, KEY2])
self.assertEqual(cache.ringlen(), 2)
self.assertEqual(c1._p_state, UPTODATE)
self.assertEqual(c2._p_state, UPTODATE)
def test_invalidate_miss_single(self):
KEY = b'123'
cache = self._makeOne()
cache.invalidate(KEY) # doesn't raise
def test_invalidate_miss_multiple(self):
KEY = b'123'
KEY2 = b'456'
cache = self._makeOne()
cache.invalidate([KEY, KEY2]) # doesn't raise
def test_invalidate_hit_single_ghost(self):
from persistent.interfaces import GHOST
KEY = b'123'
cache = self._makeOne()
candidate = self._makePersist(oid=b'123', jar=cache.jar, state=GHOST)
cache[KEY] = candidate
self.assertEqual(cache.ringlen(), 0)
cache.invalidate(KEY)
self.assertEqual(cache.ringlen(), 0)
self.assertEqual(candidate._p_state, GHOST)
def test_invalidate_hit_single_non_ghost(self):
from persistent.interfaces import GHOST
KEY = b'123'
cache = self._makeOne()
candidate = self._makePersist(oid=b'123', jar=cache.jar, state=UPTODATE)
cache[KEY] = candidate
self.assertEqual(cache.ringlen(), 1)
cache.invalidate(KEY)
self.assertEqual(cache.ringlen(), 0)
self.assertEqual(candidate._p_state, GHOST)
def test_invalidate_hit_multiple_mixed(self):
from persistent.interfaces import GHOST
KEY = b'123'
KEY2 = b'456'
cache = self._makeOne()
c1 = self._makePersist(oid=KEY, jar=cache.jar, state=GHOST)
cache[KEY] = c1
c2 = self._makePersist(oid=KEY2, jar=cache.jar, state=UPTODATE)
cache[KEY2] = c2
self.assertEqual(cache.ringlen(), 1)
cache.invalidate([KEY, KEY2])
self.assertEqual(cache.ringlen(), 0)
self.assertEqual(c1._p_state, GHOST)
self.assertEqual(c2._p_state, GHOST)
def test_invalidate_hit_multiple_non_ghost(self):
from persistent.interfaces import GHOST
KEY = b'123'
KEY2 = b'456'
cache = self._makeOne()
c1 = self._makePersist(oid=KEY, jar=cache.jar, state=UPTODATE)
cache[KEY] = c1
c2 = self._makePersist(oid=KEY2, jar=cache.jar, state=UPTODATE)
cache[KEY2] = c2
self.assertEqual(cache.ringlen(), 2)
# These should be in the opposite order of how they were
# added to the ring to ensure ring traversal works
cache.invalidate([KEY2, KEY])
self.assertEqual(cache.ringlen(), 0)
self.assertEqual(c1._p_state, GHOST)
self.assertEqual(c2._p_state, GHOST)
def test_invalidate_hit_pclass(self):
KEY = b'123'
class Pclass(object):
_p_oid = KEY
_p_jar = None
cache = self._makeOne()
cache[KEY] = Pclass
self.assertTrue(cache.persistent_classes[KEY] is Pclass)
cache.invalidate(KEY)
self.assertFalse(KEY in cache.persistent_classes)
def test_debug_info_w_persistent_class(self):
KEY = b'pclass'
class pclass(object):
_p_oid = KEY
cache = self._makeOne()
pclass._p_state = UPTODATE
cache[KEY] = pclass
gc.collect() # pypy vs. refcounting
info = cache.debug_info()
self.assertEqual(len(info), 1)
oid, refc, typ, state = info[0]
self.assertEqual(oid, KEY)
self.assertEqual(refc, len(gc.get_referents(pclass)))
self.assertEqual(typ, 'type')
self.assertEqual(state, UPTODATE)
def test_debug_info_w_normal_object(self):
KEY = b'uptodate'
cache = self._makeOne()
uptodate = self._makePersist(state=UPTODATE, oid=KEY)
cache[KEY] = uptodate
gc.collect() # pypy vs. refcounting
info = cache.debug_info()
self.assertEqual(len(info), 1)
oid, refc, typ, state = info[0]
self.assertEqual(oid, KEY)
self.assertEqual(refc, len(gc.get_referents(uptodate)))
self.assertEqual(typ, 'DummyPersistent')
self.assertEqual(state, UPTODATE)
def test_debug_info_w_ghost(self):
from persistent.interfaces import GHOST
KEY = b'ghost'
cache = self._makeOne()
ghost = self._makePersist(state=GHOST, oid=KEY)
cache[KEY] = ghost
gc.collect() # pypy vs. refcounting
info = cache.debug_info()
self.assertEqual(len(info), 1)
oid, refc, typ, state = info[0]
self.assertEqual(oid, KEY)
self.assertEqual(refc, len(gc.get_referents(ghost)))
self.assertEqual(typ, 'DummyPersistent')
self.assertEqual(state, GHOST)
def test_init_with_cacheless_jar(self):
# Sometimes ZODB tests pass objects that don't
# have a _cache
class Jar(object):
was_set = False
def __setattr__(self, name, value):
if name == '_cache':
object.__setattr__(self, 'was_set', True)
raise AttributeError(name)
jar = Jar()
self._makeOne(jar)
self.assertTrue(jar.was_set)
def test_setting_non_persistent_item(self):
cache = self._makeOne()
with self.assertRaisesRegex(TypeError,
"Cache values must be persistent objects."):
cache[None] = object()
def test_setting_without_jar(self):
cache = self._makeOne()
p = self._makePersist(jar=None)
with self.assertRaisesRegex(ValueError,
"Cached object jar missing"):
cache[p._p_oid] = p
def test_setting_already_cached(self):
cache1 = self._makeOne()
p = self._makePersist(jar=cache1.jar)
cache1[p._p_oid] = p
cache2 = self._makeOne()
with self.assertRaisesRegex(ValueError,
"Object already in another cache"):
cache2[p._p_oid] = p
def test_cannot_update_mru_while_already_locked(self):
cache = self._makeOne()
cache._is_sweeping_ring = True
updated = cache.mru(None)
self.assertFalse(updated)
def test_update_object_size_estimation_simple(self):
cache = self._makeOne()
p = self._makePersist(jar=cache.jar)
cache[p._p_oid] = p
# The cache accesses the private attribute directly to bypass
# the bit conversion.
# Note that the _p_estimated_size is set *after*
# the update call is made in ZODB's serialize
p._Persistent__size = 0
cache.update_object_size_estimation(p._p_oid, 2)
self.assertEqual(cache.total_estimated_size, 64)
# A missing object does nothing
cache.update_object_size_estimation(None, 2)
self.assertEqual(cache.total_estimated_size, 64)
def test_cache_size(self):
size = 42
cache = self._makeOne(target_size=size)
self.assertEqual(cache.cache_size, size)
cache.cache_size = 64
self.assertEqual(cache.cache_size, 64)
def test_sweep_empty(self):
cache = self._makeOne()
self.assertEqual(cache.incrgc(), 0)
def test_sweep_of_non_deactivating_object(self):
cache = self._makeOne()
p = self._makePersist(jar=cache.jar)
p._p_state = 0 # non-ghost, get in the ring
cache[p._p_oid] = p
def bad_deactivate():
"Doesn't call super, for it's own reasons, so can't be ejected"
p._p_deactivate = bad_deactivate
import persistent.picklecache
sweep_types = persistent.picklecache._SWEEPABLE_TYPES
persistent.picklecache._SWEEPABLE_TYPES = DummyPersistent
try:
self.assertEqual(cache.full_sweep(), 0)
finally:
persistent.picklecache._SWEEPABLE_TYPES = sweep_types
del p._p_deactivate
self.assertEqual(cache.full_sweep(), 1)
if _is_jython: # pragma: no cover
def with_deterministic_gc(f):
def test(self):
old_flags = gc.getMonitorGlobal()
gc.setMonitorGlobal(True)
try:
f(self, force_collect=True)
finally:
gc.setMonitorGlobal(old_flags)
return test
else:
def with_deterministic_gc(f):
return f
@with_deterministic_gc
def test_cache_garbage_collection_bytes_also_deactivates_object(self,
force_collect=_is_pypy or _is_jython):
cache = self._makeOne()
cache.cache_size = 1000
oids = []
for i in range(100):
oid = self._numbered_oid(i)
oids.append(oid)
o = cache[oid] = self._makePersist(oid=oid, state=UPTODATE)
o._Persistent__size = 0 # must start 0, ZODB sets it AFTER updating the size
cache.update_object_size_estimation(oid, 64)
o._Persistent__size = 2
# mimic what the real persistent object does to update the cache
# size; if we don't get deactivated by sweeping, the cache size
# won't shrink so this also validates that _p_deactivate gets
# called when ejecting an object.
o._p_deactivate = lambda: cache.update_object_size_estimation(oid, -1)
self.assertEqual(cache.cache_non_ghost_count, 100)
# A GC at this point does nothing
cache.incrgc()
self.assertEqual(cache.cache_non_ghost_count, 100)
self.assertEqual(len(cache), 100)
# Now if we set a byte target:
cache.cache_size_bytes = 1
# verify the change worked as expected
self.assertEqual(cache.cache_size_bytes, 1)
# verify our entrance assumption is fulfilled
self.assertTrue(cache.cache_size > 100)
self.assertTrue(cache.total_estimated_size > 1)
# A gc shrinks the bytes
cache.incrgc()
self.assertEqual(cache.total_estimated_size, 0)
# It also shrank the measured size of the cache;
# this would fail under PyPy if _SWEEP_NEEDS_GC was False
if force_collect: # pragma: no cover
gc.collect()
self.assertEqual(len(cache), 1)
def test_invalidate_persistent_class_calls_p_invalidate(self):
KEY = b'pclass'
class pclass(object):
_p_oid = KEY
invalidated = False
@classmethod
def _p_invalidate(cls):
cls.invalidated = True
cache = self._makeOne()
cache[KEY] = pclass
cache.invalidate(KEY)
self.assertTrue(pclass.invalidated)
def test_ring_impl(self):
from .. import ring
expected = (ring._CFFIRing
if _is_pypy or ring._CFFIRing is not None or os.environ.get('USING_CFFI')
else ring._DequeRing)
self.assertIs(ring.Ring, expected)
class DummyPersistent(object):
def _p_invalidate(self):
from persistent.interfaces import GHOST
self._p_state = GHOST
_p_deactivate = _p_invalidate
def _p_invalidate_deactivate_helper(self, clear=True):
self._p_invalidate()
def _p_activate(self):
self._p_state = UPTODATE
class DummyConnection(object):
pass
def _len(seq):
return len(list(seq))
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,157 @@
##############################################################################
#
# Copyright (c) 2015 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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 .. import ring
#pylint: disable=R0904,W0212,E1101
class DummyPersistent(object):
_p_oid = None
__next_oid = 0
@classmethod
def _next_oid(cls):
cls.__next_oid += 1
return cls.__next_oid
def __init__(self, oid=None):
if oid is None:
self._p_oid = self._next_oid()
def __repr__(self): # pragma: no cover
return "<Dummy %r>" % self._p_oid
class _Ring_Base(object):
def _getTargetClass(self):
"""Return the type of the ring to test"""
raise NotImplementedError()
def _makeOne(self):
return self._getTargetClass()()
def test_empty_len(self):
self.assertEqual(0, len(self._makeOne()))
def test_empty_contains(self):
r = self._makeOne()
self.assertFalse(DummyPersistent() in r)
def test_empty_iter(self):
self.assertEqual([], list(self._makeOne()))
def test_add_one_len1(self):
r = self._makeOne()
p = DummyPersistent()
r.add(p)
self.assertEqual(1, len(r))
def test_add_one_contains(self):
r = self._makeOne()
p = DummyPersistent()
r.add(p)
self.assertTrue(p in r)
def test_delete_one_len0(self):
r = self._makeOne()
p = DummyPersistent()
r.add(p)
r.delete(p)
self.assertEqual(0, len(r))
def test_delete_one_multiple(self):
r = self._makeOne()
p = DummyPersistent()
r.add(p)
r.delete(p)
self.assertEqual(0, len(r))
self.assertFalse(p in r)
r.delete(p)
self.assertEqual(0, len(r))
self.assertFalse(p in r)
def test_delete_from_wrong_ring(self):
r1 = self._makeOne()
r2 = self._makeOne()
p1 = DummyPersistent()
p2 = DummyPersistent()
r1.add(p1)
r2.add(p2)
r2.delete(p1)
self.assertEqual(1, len(r1))
self.assertEqual(1, len(r2))
self.assertEqual([p1], list(r1))
self.assertEqual([p2], list(r2))
def test_move_to_head(self):
r = self._makeOne()
p1 = DummyPersistent()
p2 = DummyPersistent()
p3 = DummyPersistent()
r.add(p1)
r.add(p2)
r.add(p3)
self.assertEqual([p1, p2, p3], list(r))
self.assertEqual(3, len(r))
r.move_to_head(p1)
self.assertEqual([p2, p3, p1], list(r))
r.move_to_head(p3)
self.assertEqual([p2, p1, p3], list(r))
r.move_to_head(p3)
self.assertEqual([p2, p1, p3], list(r))
def test_delete_all(self):
r = self._makeOne()
p1 = DummyPersistent()
p2 = DummyPersistent()
p3 = DummyPersistent()
r.add(p1)
r.add(p2)
r.add(p3)
self.assertEqual([p1, p2, p3], list(r))
r.delete_all([(0, p1), (2, p3)])
self.assertEqual([p2], list(r))
self.assertEqual(1, len(r))
class DequeRingTests(unittest.TestCase, _Ring_Base):
def _getTargetClass(self):
return ring._DequeRing
_add_to_suite = [DequeRingTests]
if ring._CFFIRing:
class CFFIRingTests(unittest.TestCase, _Ring_Base):
def _getTargetClass(self):
return ring._CFFIRing
_add_to_suite.append(CFFIRingTests)
def test_suite():
return unittest.TestSuite([unittest.makeSuite(x) for x in _add_to_suite])
@@ -0,0 +1,429 @@
##############################################################################
#
# Copyright (c) 2011 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
import sys
MAX_32_BITS = 2 ** 31 - 1
MAX_64_BITS = 2 ** 63 - 1
import persistent.timestamp
class Test__UTC(unittest.TestCase):
def _getTargetClass(self):
from persistent.timestamp import _UTC
return _UTC
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
def test_tzname(self):
utc = self._makeOne()
self.assertEqual(utc.tzname(), 'UTC')
def test_utcoffset(self):
from datetime import timedelta
utc = self._makeOne()
self.assertEqual(utc.utcoffset(object()), timedelta(0))
def test_dst(self):
utc = self._makeOne()
self.assertEqual(utc.dst(), 0)
def test_fromutc(self):
source = object()
utc = self._makeOne()
self.assertTrue(utc.fromutc(source) is source)
class pyTimeStampTests(unittest.TestCase):
def _getTargetClass(self):
from persistent.timestamp import pyTimeStamp
return pyTimeStamp
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
def test_ctor_invalid_arglist(self):
BAD_ARGS = [(),
(1,),
(1, 2),
(1, 2, 3),
(1, 2, 3, 4),
(1, 2, 3, 4, 5),
('1', '2', '3', '4', '5', '6'),
(1, 2, 3, 4, 5, 6, 7),
(b'123',),
]
for args in BAD_ARGS:
with self.assertRaises((TypeError, ValueError)):
self._makeOne(*args)
def test_ctor_from_invalid_strings(self):
BAD_ARGS = [''
'\x00',
'\x00' * 2,
'\x00' * 3,
'\x00' * 4,
'\x00' * 5,
'\x00' * 7,
]
for args in BAD_ARGS:
self.assertRaises((TypeError, ValueError), self._makeOne, *args)
def test_ctor_from_string(self):
from persistent.timestamp import _makeUTC
ZERO = _makeUTC(1900, 1, 1, 0, 0, 0)
EPOCH = _makeUTC(1970, 1, 1, 0, 0, 0)
DELTA = ZERO - EPOCH
DELTA_SECS = DELTA.days * 86400 + DELTA.seconds
SERIAL = b'\x00' * 8
ts = self._makeOne(SERIAL)
self.assertEqual(ts.raw(), SERIAL)
self.assertEqual(ts.year(), 1900)
self.assertEqual(ts.month(), 1)
self.assertEqual(ts.day(), 1)
self.assertEqual(ts.hour(), 0)
self.assertEqual(ts.minute(), 0)
self.assertEqual(ts.second(), 0.0)
self.assertEqual(ts.timeTime(), DELTA_SECS)
def test_ctor_from_string_non_zero(self):
before = self._makeOne(2011, 2, 16, 14, 37, 22.80544)
after = self._makeOne(before.raw())
self.assertEqual(before.raw(), after.raw())
self.assertEqual(before.timeTime(), 1297867042.80544)
def test_ctor_from_elements(self):
from persistent.timestamp import _makeUTC
ZERO = _makeUTC(1900, 1, 1, 0, 0, 0)
EPOCH = _makeUTC(1970, 1, 1, 0, 0, 0)
DELTA = ZERO - EPOCH
DELTA_SECS = DELTA.days * 86400 + DELTA.seconds
SERIAL = b'\x00' * 8
ts = self._makeOne(1900, 1, 1, 0, 0, 0.0)
self.assertEqual(ts.raw(), SERIAL)
self.assertEqual(ts.year(), 1900)
self.assertEqual(ts.month(), 1)
self.assertEqual(ts.day(), 1)
self.assertEqual(ts.hour(), 0)
self.assertEqual(ts.minute(), 0)
self.assertEqual(ts.second(), 0.0)
self.assertEqual(ts.timeTime(), DELTA_SECS)
def test_laterThan_invalid(self):
ERRORS = (ValueError, TypeError)
SERIAL = b'\x01' * 8
ts = self._makeOne(SERIAL)
self.assertRaises(ERRORS, ts.laterThan, None)
self.assertRaises(ERRORS, ts.laterThan, '')
self.assertRaises(ERRORS, ts.laterThan, ())
self.assertRaises(ERRORS, ts.laterThan, [])
self.assertRaises(ERRORS, ts.laterThan, {})
self.assertRaises(ERRORS, ts.laterThan, object())
def test_laterThan_self_is_earlier(self):
SERIAL1 = b'\x01' * 8
SERIAL2 = b'\x02' * 8
ts1 = self._makeOne(SERIAL1)
ts2 = self._makeOne(SERIAL2)
later = ts1.laterThan(ts2)
self.assertEqual(later.raw(), b'\x02' * 7 + b'\x03')
def test_laterThan_self_is_later(self):
SERIAL1 = b'\x01' * 8
SERIAL2 = b'\x02' * 8
ts1 = self._makeOne(SERIAL1)
ts2 = self._makeOne(SERIAL2)
later = ts2.laterThan(ts1)
self.assertTrue(later is ts2)
def test_repr(self):
SERIAL = b'\x01' * 8
ts = self._makeOne(SERIAL)
self.assertEqual(repr(ts), repr(SERIAL))
def test_comparisons_to_non_timestamps(self):
import operator
from persistent._compat import PYTHON2
# Check the corner cases when comparing non-comparable types
ts = self._makeOne(2011, 2, 16, 14, 37, 22.0)
def check_common(op, passes):
if passes == 'neither':
self.assertFalse(op(ts, None))
self.assertFalse(op(None, ts))
return True
if passes == 'both':
self.assertTrue(op(ts, None))
self.assertTrue(op(None, ts))
return True
return False
def check_py2(op, passes): # pragma: no cover
if passes == 'first':
self.assertTrue(op(ts, None))
self.assertFalse(op(None, ts))
else:
self.assertFalse(op(ts, None))
self.assertTrue(op(None, ts))
def check_py3(op, passes):
self.assertRaises(TypeError, op, ts, None)
self.assertRaises(TypeError, op, None, ts)
check = check_py2 if PYTHON2 else check_py3
for op_name, passes in (('lt', 'second'),
('gt', 'first'),
('le', 'second'),
('ge', 'first'),
('eq', 'neither'),
('ne', 'both')):
op = getattr(operator, op_name)
if not check_common(op, passes):
check(op, passes)
class TimeStampTests(pyTimeStampTests):
def _getTargetClass(self):
from persistent.timestamp import TimeStamp
return TimeStamp
@unittest.skipIf(persistent.timestamp.CTimeStamp is None,
"CTimeStamp not available")
class PyAndCComparisonTests(unittest.TestCase):
"""
Compares C and Python implementations.
"""
# A particular instant in time
now = 1229959248.3
# That instant in time split as the result of this expression:
# (time.gmtime(now)[:5] + (now % 60,))
now_ts_args = (2008, 12, 22, 15, 20, 48.299999952316284)
def _make_many_instants(self):
# Given the above data, return many slight variations on
# it to test matching
yield self.now_ts_args
for i in range(2000):
yield self.now_ts_args[:-1] + (self.now_ts_args[-1] + (i % 60.0)/100.0, )
def _makeC(self, *args, **kwargs):
from persistent.timestamp import TimeStamp
return TimeStamp(*args, **kwargs)
def _makePy(self, *args, **kwargs):
from persistent.timestamp import pyTimeStamp
return pyTimeStamp(*args, **kwargs)
@property
def _is_jython(self):
import platform
py_impl = getattr(platform, 'python_implementation', lambda: None)
return py_impl() == 'Jython'
def _make_C_and_Py(self, *args, **kwargs):
return self._makeC(*args, **kwargs), self._makePy(*args, **kwargs)
def test_reprs_equal(self):
for args in self._make_many_instants():
c, py = self._make_C_and_Py(*args)
self.assertEqual(repr(c), repr(py))
def test_strs_equal(self):
for args in self._make_many_instants():
c, py = self._make_C_and_Py(*args)
self.assertEqual(str(c), str(py))
def test_raw_equal(self):
c, py = self._make_C_and_Py(*self.now_ts_args)
self.assertEqual(c.raw(), py.raw())
def test_equal(self):
c, py = self._make_C_and_Py(*self.now_ts_args)
self.assertEqual(c, py)
def test_hash_equal(self):
c, py = self._make_C_and_Py(*self.now_ts_args)
self.assertEqual(hash(c), hash(py))
def test_py_hash_32_64_bit(self):
# We happen to know that on a 32-bit platform, the hashcode
# of the c version should be exactly
# -1419374591
# and the 64-bit should be exactly:
# -3850693964765720575
# Fake out the python version to think it's on a 32-bit
# platform and test the same; also verify 64 bit
from persistent import timestamp as MUT
bit_32_hash = -1419374591
bit_64_hash = -3850693964765720575
orig_maxint = MUT._MAXINT
is_32_bit_hash = orig_maxint == MAX_32_BITS
orig_c_long = None
c_int64 = None
c_int32 = None
if hasattr(MUT, 'c_long'):
import ctypes
orig_c_long = MUT.c_long
c_int32 = ctypes.c_int32
c_int64 = ctypes.c_int64
# win32, even on 64-bit long, has funny sizes
is_32_bit_hash = c_int32 == ctypes.c_long
try:
MUT._MAXINT = MAX_32_BITS
MUT.c_long = c_int32
py = self._makePy(*self.now_ts_args)
self.assertEqual(hash(py), bit_32_hash)
MUT._MAXINT = int(2 ** 63 - 1)
MUT.c_long = c_int64
# call __hash__ directly to avoid interpreter truncation
# in hash() on 32-bit platforms
if not self._is_jython:
self.assertEqual(py.__hash__(), bit_64_hash)
else: # pragma: no cover
# Jython 2.7's ctypes module doesn't properly
# implement the 'value' attribute by truncating.
# (It does for native calls, but not visibly to Python).
# Therefore we get back the full python long. The actual
# hash() calls are correct, though, because the JVM uses
# 32-bit ints for its hashCode methods.
self.assertEqual(
py.__hash__(),
384009219096809580920179179233996861765753210540033)
finally:
MUT._MAXINT = orig_maxint
if orig_c_long is not None:
MUT.c_long = orig_c_long
else: # pragma: no cover
del MUT.c_long
# These are *usually* aliases, but aren't required
# to be (and aren't under Jython 2.7).
expected_hash = bit_32_hash if is_32_bit_hash else bit_64_hash
self.assertEqual(py.__hash__(), expected_hash)
def test_hash_equal_constants(self):
# The simple constants make it easier to diagnose
# a difference in algorithms
import persistent.timestamp as MUT
# We get 32-bit hash values on 32-bit platforms, or on the JVM
# OR on Windows (whether compiled in 64 or 32-bit mode)
is_32_bit = MUT._MAXINT == (2**31 - 1) or self._is_jython or sys.platform == 'win32'
c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertEqual(hash(c), 8)
self.assertEqual(hash(c), hash(py))
c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x00\x00\x01')
self.assertEqual(hash(c), 9)
self.assertEqual(hash(c), hash(py))
c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x00\x01\x00')
self.assertEqual(hash(c), 1000011)
self.assertEqual(hash(c), hash(py))
# overflow kicks in here on 32-bit platforms
c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x00\x01\x00\x00')
expected = -721379967 if is_32_bit else 1000006000001
self.assertEqual(hash(c), expected)
self.assertEqual(hash(c), hash(py))
c, py = self._make_C_and_Py(b'\x00\x00\x00\x00\x01\x00\x00\x00')
expected = 583896275 if is_32_bit else 1000009000027000019
self.assertEqual(hash(c), expected)
self.assertEqual(hash(c), hash(py))
# Overflow kicks in at this point on 64-bit platforms
c, py = self._make_C_and_Py(b'\x00\x00\x00\x01\x00\x00\x00\x00')
expected = 1525764953 if is_32_bit else -4442925868394654887
self.assertEqual(hash(c), expected)
self.assertEqual(hash(c), hash(py))
c, py = self._make_C_and_Py(b'\x00\x00\x01\x00\x00\x00\x00\x00')
expected = -429739973 if is_32_bit else -3993531167153147845
self.assertEqual(hash(c), expected)
self.assertEqual(hash(c), hash(py))
c, py = self._make_C_and_Py(b'\x01\x00\x00\x00\x00\x00\x00\x00')
expected = 263152323 if is_32_bit else -3099646879006235965
self.assertEqual(hash(c), expected)
self.assertEqual(hash(c), hash(py))
def test_ordering(self):
small_c = self._makeC(b'\x00\x00\x00\x00\x00\x00\x00\x01')
small_py = self._makePy(b'\x00\x00\x00\x00\x00\x00\x00\x01')
big_c = self._makeC(b'\x01\x00\x00\x00\x00\x00\x00\x00')
big_py = self._makePy(b'\x01\x00\x00\x00\x00\x00\x00\x00')
self.assertTrue(small_py < big_py)
self.assertTrue(small_py <= big_py)
self.assertTrue(small_py < big_c)
self.assertTrue(small_py <= big_c)
self.assertTrue(small_py <= small_c)
self.assertTrue(small_c < big_c)
self.assertTrue(small_c <= big_c)
self.assertTrue(small_c <= big_py)
self.assertTrue(big_c > small_py)
self.assertTrue(big_c >= big_py)
self.assertFalse(big_c == small_py)
self.assertFalse(small_py == big_c)
self.assertTrue(big_c != small_py)
self.assertTrue(small_py != big_c)
def test_seconds_precision(self, seconds=6.123456789):
# https://github.com/zopefoundation/persistent/issues/41
args = (2001, 2, 3, 4, 5, seconds)
c = self._makeC(*args)
py = self._makePy(*args)
self.assertEqual(c, py)
self.assertEqual(c.second(), py.second())
py2 = self._makePy(c.raw())
self.assertEqual(py2, c)
c2 = self._makeC(c.raw())
self.assertEqual(c2, c)
def test_seconds_precision_half(self):
# make sure our rounding matches
self.test_seconds_precision(seconds=6.5)
self.test_seconds_precision(seconds=6.55)
self.test_seconds_precision(seconds=6.555)
self.test_seconds_precision(seconds=6.5555)
self.test_seconds_precision(seconds=6.55555)
self.test_seconds_precision(seconds=6.555555)
self.test_seconds_precision(seconds=6.5555555)
self.test_seconds_precision(seconds=6.55555555)
self.test_seconds_precision(seconds=6.555555555)
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
@@ -0,0 +1,338 @@
##############################################################################
#
# Copyright (c) 2003 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
class WeakRefTests(unittest.TestCase):
def _getTargetClass(self):
from persistent.wref import WeakRef
return WeakRef
def _makeOne(self, ob):
return self._getTargetClass()(ob)
def test_ctor_target_wo_jar(self):
target = _makeTarget()
wref = self._makeOne(target)
self.assertTrue(wref._v_ob is target)
self.assertEqual(wref.oid, b'OID')
self.assertTrue(wref.dm is None)
self.assertFalse('database_name' in wref.__dict__)
def test_ctor_target_w_jar(self):
target = _makeTarget()
target._p_jar = jar = _makeJar()
wref = self._makeOne(target)
self.assertTrue(wref._v_ob is target)
self.assertEqual(wref.oid, b'OID')
self.assertTrue(wref.dm is jar)
self.assertEqual(wref.database_name, 'testing')
def test___call___target_in_volatile(self):
target = _makeTarget()
target._p_jar = jar = _makeJar()
wref = self._makeOne(target)
self.assertTrue(wref() is target)
def test___call___target_in_jar(self):
target = _makeTarget()
target._p_jar = jar = _makeJar()
jar[target._p_oid] = target
wref = self._makeOne(target)
del wref._v_ob
self.assertTrue(wref() is target)
def test___call___target_not_in_jar(self):
target = _makeTarget()
target._p_jar = jar = _makeJar()
wref = self._makeOne(target)
del wref._v_ob
self.assertTrue(wref() is None)
def test___hash___w_target(self):
target = _makeTarget()
target._p_jar = jar = _makeJar()
wref = self._makeOne(target)
self.assertEqual(hash(wref), hash(target))
def test___hash___wo_target(self):
target = _makeTarget()
target._p_jar = jar = _makeJar()
wref = self._makeOne(target)
del wref._v_ob
self.assertRaises(TypeError, hash, wref)
def test___eq___w_non_weakref(self):
target = _makeTarget()
lhs = self._makeOne(target)
self.assertNotEqual(lhs, object())
# Test belt-and-suspenders directly
self.assertFalse(lhs.__eq__(object()))
def test___eq___w_both_same_target(self):
target = _makeTarget()
lhs = self._makeOne(target)
rhs_target = _makeTarget()
rhs = self._makeOne(target)
self.assertEqual(lhs, rhs)
def test___eq___w_both_different_targets(self):
lhs_target = _makeTarget(oid='LHS')
lhs = self._makeOne(lhs_target)
rhs_target = _makeTarget(oid='RHS')
rhs = self._makeOne(rhs_target)
self.assertNotEqual(lhs, rhs)
def test___eq___w_lhs_gone_target_not_in_jar(self):
target = _makeTarget()
target._p_jar = jar = _makeJar()
lhs = self._makeOne(target)
del lhs._v_ob
rhs = self._makeOne(target)
self.assertRaises(TypeError, lambda: lhs == rhs)
def test___eq___w_lhs_gone_target_in_jar(self):
target = _makeTarget()
target._p_jar = jar = _makeJar()
jar[target._p_oid] = target
lhs = self._makeOne(target)
del lhs._v_ob
rhs_target = _makeTarget()
rhs = self._makeOne(target)
self.assertEqual(lhs, rhs)
def test___eq___w_rhs_gone_target_not_in_jar(self):
target = _makeTarget()
target._p_jar = jar = _makeJar()
lhs = self._makeOne(target)
rhs = self._makeOne(target)
del rhs._v_ob
self.assertRaises(TypeError, lambda: lhs == rhs)
def test___eq___w_rhs_gone_target_in_jar(self):
target = _makeTarget()
target._p_jar = jar = _makeJar()
jar[target._p_oid] = target
lhs = self._makeOne(target)
rhs = self._makeOne(target)
del rhs._v_ob
self.assertEqual(lhs, rhs)
class PersistentWeakKeyDictionaryTests(unittest.TestCase):
def _getTargetClass(self):
from persistent.wref import PersistentWeakKeyDictionary
return PersistentWeakKeyDictionary
def _makeOne(self, adict, **kw):
return self._getTargetClass()(adict, **kw)
def test_ctor_w_adict_none_no_kwargs(self):
pwkd = self._makeOne(None)
self.assertEqual(pwkd.data, {})
def test_ctor_w_adict_as_dict(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
pwkd = self._makeOne({key: value})
self.assertTrue(pwkd[key] is value)
def test_ctor_w_adict_as_items(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
pwkd = self._makeOne([(key, value)])
self.assertTrue(pwkd[key] is value)
def test___getstate___empty(self):
pwkd = self._makeOne(None)
self.assertEqual(pwkd.__getstate__(), {'data': []})
def test___getstate___filled(self):
from persistent.wref import WeakRef
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
pwkd = self._makeOne([(key, value)])
self.assertEqual(pwkd.__getstate__(),
{'data': [(WeakRef(key), value)]})
def test___setstate___empty(self):
from persistent.wref import WeakRef
jar = _makeJar()
KEY = b'KEY'
KEY2 = b'KEY2'
KEY3 = b'KEY3'
VALUE = b'VALUE'
VALUE2 = b'VALUE2'
VALUE3 = b'VALUE3'
key = jar[KEY] = _makeTarget(oid=KEY)
key._p_jar = jar
kref = WeakRef(key)
value = jar[VALUE] = _makeTarget(oid=VALUE)
value._p_jar = jar
key2 = _makeTarget(oid=KEY2)
key2._p_jar = jar # not findable
kref2 = WeakRef(key2)
del kref2._v_ob # force a miss
value2 = jar[VALUE2] = _makeTarget(oid=VALUE2)
value2._p_jar = jar
key3 = jar[KEY3] = _makeTarget(oid=KEY3) # findable
key3._p_jar = jar
kref3 = WeakRef(key3)
del kref3._v_ob # force a miss, but win in the lookup
value3 = jar[VALUE3] = _makeTarget(oid=VALUE3)
value3._p_jar = jar
pwkd = self._makeOne(None)
pwkd.__setstate__({'data':
[(kref, value), (kref2, value2), (kref3, value3)]})
self.assertTrue(pwkd[key] is value)
self.assertTrue(pwkd.get(key2) is None)
self.assertTrue(pwkd[key3] is value3)
def test___setitem__(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
pwkd = self._makeOne(None)
pwkd[key] = value
self.assertTrue(pwkd[key] is value)
def test___getitem___miss(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
pwkd = self._makeOne(None)
def _try():
return pwkd[key]
self.assertRaises(KeyError, _try)
def test___delitem__(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
pwkd = self._makeOne([(key, value)])
del pwkd[key]
self.assertTrue(pwkd.get(key) is None)
def test___delitem___miss(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
pwkd = self._makeOne(None)
def _try():
del pwkd[key]
self.assertRaises(KeyError, _try)
def test_get_miss_w_explicit_default(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
pwkd = self._makeOne(None)
self.assertTrue(pwkd.get(key, value) is value)
def test___contains___miss(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
pwkd = self._makeOne(None)
self.assertFalse(key in pwkd)
def test___contains___hit(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
pwkd = self._makeOne([(key, value)])
self.assertTrue(key in pwkd)
def test___iter___empty(self):
jar = _makeJar()
pwkd = self._makeOne(None)
self.assertEqual(list(pwkd), [])
def test___iter___filled(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
pwkd = self._makeOne([(key, value)])
self.assertEqual(list(pwkd), [key])
def test_update_w_other_pwkd(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
source = self._makeOne([(key, value)])
target = self._makeOne(None)
target.update(source)
self.assertTrue(target[key] is value)
def test_update_w_dict(self):
jar = _makeJar()
key = jar['key'] = _makeTarget(oid='KEY')
key._p_jar = jar
value = jar['value'] = _makeTarget(oid='VALUE')
value._p_jar = jar
source = dict([(key, value)])
target = self._makeOne(None)
target.update(source)
self.assertTrue(target[key] is value)
def _makeTarget(oid=b'OID'):
from persistent import Persistent
class Derived(Persistent):
def __hash__(self):
return hash(self._p_oid)
def __eq__(self, other):
return self._p_oid == other._p_oid
def __repr__(self): # pragma: no cover
return 'Derived: %s' % self._p_oid
derived = Derived()
derived._p_oid = oid
return derived
def _makeJar():
class _DB(object):
database_name = 'testing'
class _Jar(dict):
db = lambda self: _DB()
return _Jar()
def test_suite():
return unittest.defaultTestLoader.loadTestsFromName(__name__)
@@ -0,0 +1,64 @@
class ResettingJar(object):
"""Testing stub for _p_jar attribute.
"""
def __init__(self):
from persistent import PickleCache # XXX stub it!
from persistent.interfaces import IPersistentDataManager
from zope.interface import directlyProvides
self.cache = self._cache = PickleCache(self)
self.oid = 1
self.registered = {}
directlyProvides(self, IPersistentDataManager)
def add(self, obj):
import struct
obj._p_oid = struct.pack(">Q", self.oid)
self.oid += 1
obj._p_jar = self
self.cache[obj._p_oid] = obj
# the following methods must be implemented to be a jar
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 RememberingJar(object):
"""Testing stub for _p_jar attribute.
"""
def __init__(self):
from persistent import PickleCache # XXX stub it!
self.cache = PickleCache(self)
self.oid = 1
self.registered = {}
def add(self, obj):
import struct
obj._p_oid = struct.pack(">Q", self.oid)
self.oid += 1
obj._p_jar = self
self.cache[obj._p_oid] = obj
# Remember object's state for later.
self.obj = obj
self.remembered = obj.__getstate__()
def fake_commit(self):
self.remembered = self.obj.__getstate__()
self.obj._p_changed = 0
# the following methods must be implemented to be a jar
def register(self, obj):
self.registered[obj] = 1
def setstate(self, obj):
# Trivial setstate() implementation that resets the object's
# state as of the time it was added to the jar.
# This isn't what setstate() is supposed to do,
# but it suffices for the tests.
obj.__setstate__(self.remembered)
@@ -0,0 +1,220 @@
##############################################################################
#
# Copyright (c) 2011 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
__all__ = ('TimeStamp',)
import datetime
import math
import struct
import sys
from persistent._compat import PURE_PYTHON
_RAWTYPE = bytes
_MAXINT = sys.maxsize
_ZERO = b'\x00' * 8
try:
# Make sure to overflow and wraparound just
# like the C code does.
from ctypes import c_long
except ImportError: # pragma: no cover
# XXX: This is broken on 64-bit windows, where
# sizeof(long) != sizeof(Py_ssize_t)
# sizeof(long) == 4, sizeof(Py_ssize_t) == 8
# It can be fixed by setting _MAXINT = 2 ** 31 - 1 on all
# win32 platforms, but then that breaks PyPy3 64 bit for an unknown
# reason.
def _wraparound(x):
return int(((x + (_MAXINT + 1)) & ((_MAXINT << 1) + 1)) - (_MAXINT + 1))
else:
def _wraparound(x):
return c_long(x).value
class _UTC(datetime.tzinfo):
def tzname(self):
return 'UTC'
def utcoffset(self, when):
return datetime.timedelta(0, 0, 0)
def dst(self):
return 0
def fromutc(self, dt):
return dt
def _makeUTC(y, mo, d, h, mi, s):
s = round(s, 6) # microsecond precision, to match the C implementation
usec, sec = math.modf(s)
sec = int(sec)
usec = int(usec * 1e6)
return datetime.datetime(y, mo, d, h, mi, sec, usec, tzinfo=_UTC())
_EPOCH = _makeUTC(1970, 1, 1, 0, 0, 0)
_TS_SECOND_BYTES_BIAS = 60.0 / (1<<16) / (1<<16)
def _makeRaw(year, month, day, hour, minute, second):
a = (((year - 1900) * 12 + month - 1) * 31 + day - 1)
a = (a * 24 + hour) * 60 + minute
b = int(second / _TS_SECOND_BYTES_BIAS) # Don't round() this; the C version does simple truncation
return struct.pack('>II', a, b)
def _parseRaw(octets):
a, b = struct.unpack('>II', octets)
minute = a % 60
hour = a // 60 % 24
day = a // (60 * 24) % 31 + 1
month = a // (60 * 24 * 31) % 12 + 1
year = a // (60 * 24 * 31 * 12) + 1900
second = b * _TS_SECOND_BYTES_BIAS
return (year, month, day, hour, minute, second)
class pyTimeStamp(object):
__slots__ = ('_raw', '_elements')
def __init__(self, *args):
self._elements = None
if len(args) == 1:
raw = args[0]
if not isinstance(raw, _RAWTYPE):
raise TypeError('Raw octets must be of type: %s' % _RAWTYPE)
if len(raw) != 8:
raise TypeError('Raw must be 8 octets')
self._raw = raw
elif len(args) == 6:
self._raw = _makeRaw(*args)
# Note that we don't preserve the incoming arguments in self._elements,
# we derive them from the raw value. This is because the incoming
# seconds value could have more precision than would survive
# in the raw data, so we must be consistent.
else:
raise TypeError('Pass either a single 8-octet arg '
'or 5 integers and a float')
self._elements = _parseRaw(self._raw)
def raw(self):
return self._raw
def __repr__(self):
return repr(self._raw)
def __str__(self):
return "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%09.6f" % (
self.year(), self.month(), self.day(),
self.hour(), self.minute(),
self.second())
def year(self):
return self._elements[0]
def month(self):
return self._elements[1]
def day(self):
return self._elements[2]
def hour(self):
return self._elements[3]
def minute(self):
return self._elements[4]
def second(self):
return self._elements[5]
def timeTime(self):
""" -> seconds since epoch, as a float.
"""
delta = _makeUTC(*self._elements) - _EPOCH
return delta.days * 86400 + delta.seconds + delta.microseconds / 1e6
def laterThan(self, other):
""" Return a timestamp instance which is later than 'other'.
If self already qualifies, return self.
Otherwise, return a new instance one moment later than 'other'.
"""
if not isinstance(other, self.__class__):
raise ValueError()
if self._raw > other._raw:
return self
a, b = struct.unpack('>II', other._raw)
later = struct.pack('>II', a, b + 1)
return self.__class__(later)
def __eq__(self, other):
try:
return self.raw() == other.raw()
except AttributeError:
return NotImplemented
def __ne__(self, other):
try:
return self.raw() != other.raw()
except AttributeError:
return NotImplemented
def __hash__(self):
# Match the C implementation
a = bytearray(self._raw)
x = a[0] << 7
for i in a:
x = (1000003 * x) ^ i
x ^= 8
x = _wraparound(x)
if x == -1: # pragma: no cover
# The C version has this condition, but it's not clear
# why; it's also not immediately obvious what bytestring
# would generate this---hence the no-cover
x = -2
return x
# Now the rest of the comparison operators
# Sigh. Python 2.6 doesn't have functools.total_ordering
# so we have to do it by hand
def __lt__(self, other):
try:
return self.raw() < other.raw()
except AttributeError:
return NotImplemented
def __gt__(self, other):
try:
return self.raw() > other.raw()
except AttributeError:
return NotImplemented
def __le__(self, other):
try:
return self.raw() <= other.raw()
except AttributeError:
return NotImplemented
def __ge__(self, other):
try:
return self.raw() >= other.raw()
except AttributeError:
return NotImplemented
try:
from persistent._timestamp import TimeStamp as CTimeStamp
except ImportError: # pragma: no cover
CTimeStamp = None
TimeStamp = pyTimeStamp if PURE_PYTHON or CTimeStamp is None else CTimeStamp
@@ -0,0 +1,129 @@
##############################################################################
#
# Copyright (c) 2003 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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
"""
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.
"""
# 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
if self.dm is not None:
self.database_name = self.dm.db().database_name
def __call__(self):
try:
return self._v_ob
except AttributeError:
try:
self._v_ob = self.dm[self.oid]
except (KeyError, AttributeError):
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):
if not isinstance(other, WeakRef):
return False
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.
"""
# TODO: It's 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)
# XXX 'kwargs' is pointless, because keys must be strings, but we
# are going to try (and fail) to wrap a WeakRef around them.
if kwargs: # pragma: no cover
self.update(kwargs)
def __getstate__(self):
state = Persistent.__getstate__(self)
state['data'] = list(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.
"""
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.data)
else:
for k, v in adict.items():
self.data[WeakRef(k)] = v
# TODO: May need more methods and tests.