tested django-newsletter
This commit is contained in:
@@ -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
|
||||
BIN
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
@@ -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
BIN
Binary file not shown.
@@ -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.
|
||||
Reference in New Issue
Block a user