Commit 5f986107 authored by Boxiang Sun's avatar Boxiang Sun

Original implementation

parents
=======
CHANGES
=======
3.11.2 (2010-09-25)
-------------------
- Added not declared, but needed test dependency on `zope.testing`.
3.11.1 (2010-04-30)
-------------------
- Prefer the standard libraries doctest module to the one from zope.testing.
- Added compatibility with ZODB3 3.10 by importing the IBroken interface from
it directly. Once we can rely on the new ZODB3 version exclusively, we can
remove the dependency onto the zope.broken distribution.
- Never fail if the suggested name is in a wrong type (#227617)
- ``checkName`` first checks the parameter type before the emptiness.
3.11.0 (2009-12-31)
-------------------
- Copy two trivial classes from zope.cachedescriptors into this package, which
allows us to remove that dependency. We didn't actually use any caching
properties as the dependency suggested.
3.10.1 (2009-12-29)
-------------------
- Moved zope.copypastemove related tests into that package.
- Removed no longer used zcml prefix from the configure file.
- Stop importing DocTestSuite from zope.testing.doctestunit. Fixes
compatibility problems with zope.testing 3.8.4.
3.10.0 (2009-12-15)
-------------------
- Break testing dependency on zope.app.testing.
- Break testing dependency on zope.app.dependable by moving the code and tests
into that package.
- Import ISite from zope.component after it was moved there from
zope.location.
3.9.1 (2009-10-18)
------------------
- Rerelease 3.9.0 as it had a broken Windows 2.6 egg.
- Marked as part of the ZTK.
3.9.0 (2009-08-28)
------------------
- Previous releases should be versioned 3.9.0 as they are not pure bugfix
releases and worth a "feature" release, increasing feature version.
Packages that depend on any changes introduced in version 3.8.2 or 3.8.3
should depend on version 3.9 or greater.
3.8.3 (2009-08-27)
------------------
- Move IXMLRPCPublisher ZCML registrations for containers from
zope.app.publisher.xmlrpc to zope.container for now.
3.8.2 (2009-05-17)
------------------
- Rid ourselves of ``IContained`` interface. This interface was moved
to ``zope.location.interfaces``. A b/w compat import still exists
to keep old code running. Depend on ``zope.location``>=3.5.4.
- Rid ourselves of the implementations of ``IObjectMovedEvent``,
``IObjectAddedEvent``, ``IObjectRemovedEvent`` interfaces and
``ObjectMovedEvent``, ``ObjectAddedEvent`` and
``ObjectRemovedEvent`` classes. B/w compat imports still exist.
All of these were moved to ``zope.lifecycleevent``. Depend on
``zope.lifecycleevent``>=3.5.2.
- Fix a bug in OrderedContainer where trying to set the value for a
key that already exists (duplication error) would actually delete the
key from the order, leaving a dangling reference.
- Partially break dependency on ``zope.traversing`` by disusing
zope.traversing.api.getPath in favor of using
ILocationInfo(object).getPath(). The rest of the runtime
dependencies on zope.traversing are currently interface
dependencies.
- Break runtime dependency on ``zope.app.dependable`` by using a zcml
condition on the qsubscriber ZCML directive that registers the
CheckDependency handler for IObjectRemovedEvent. If
``zope.app.dependable`` is not installed, this subscriber will never
be registered. ``zope.app.dependable`` is now a testing dependency
only.
3.8.1 (2009-04-03)
------------------
- Fixed misspackaged 3.8.0
3.8.0 (2009-04-03)
------------------
- Change configure.zcml to not depend on zope.app.component.
Fixes: https://bugs.launchpad.net/bugs/348329
- Moved the declaration of ``IOrderedContainer.updateOrder`` to a new, basic
``IOrdered`` interface and let ``IOrderedContainer`` inherit it. This allows
easier reuse of the declaration.
3.7.2 (2009-03-12)
------------------
- Fix: added missing ComponentLookupError, missing since revision 95429 and
missing in last release.
- Adapt to the move of IDefaultViewName from zope.component.interfaces
to zope.publisher.interfaces.
- Add support for reserved names for containers. To specify reserved
names for some container, you need to provide an adapter from the
container to the ``zope.container.interfaces.IReservedNames`` interface.
The default NameChooser is now also aware of reserved names.
3.7.1 (2009-02-05)
------------------
- Raise more "Pythonic" errors from ``__setitem__``, losing the dependency
on ``zope.exceptions``:
o ``zope.exceptions.DuplicationError`` -> ``KeyError``
o ``zope.exceptions.UserError`` -> ``ValueError``
- Moved import of ``IBroken`` interface to use new ``zope.broken``
package, which has no dependencies beyond ``zope.interface``.
- Made ``test`` part pull in the extra test requirements of this package.
- Split the ``z3c.recipe.compattest`` configuration out into a new file,
``compat.cfg``, to reduce the burden of doing standard unit tests.
- Stripped out bogus develop eggs from ``buildout.cfg``.
3.7.0 (2009-01-31)
------------------
- Split this package off ``zope.app.container``. This package is
intended to have far less dependencies than ``zope.app.container``.
- This package also contains the container implementation that
used to be in ``zope.app.folder``.
Metadata-Version: 1.0
Name: zope.container
Version: 3.11.2
Summary: Zope Container
Home-page: http://pypi.python.org/pypi/zope.container
Author: Zope Corporation and Contributors
Author-email: zope-dev@zope.org
License: ZPL 2.1
Description: This package define interfaces of container components, and provides
container implementations such as a BTreeContainer and
OrderedContainer, as well as the base class used by ``zope.site.folder``
for the Folder implementation.
.. contents::
=========================
Containment constraints
=========================
Containment constraints allow us to express restrictions on the types
of items that can be placed in containers or on the types of
containers an item can be placed in. We express these constraints in
interfaces. Let's define some container and item interfaces:
>>> from zope.container.interfaces import IContainer
>>> from zope.location.interfaces import IContained
>>> from zope.container.constraints import containers, contains
>>> class IBuddyFolder(IContainer):
... contains('.IBuddy')
In this example, we used the contains function to declare that objects
that provide IBuddyFolder can only contain items that provide IBuddy.
Note that we used a string containing a dotted name for the IBuddy
interface. This is because IBuddy hasn't been defined yet. When we
define IBuddy, we can use IBuddyFolder directly:
>>> class IBuddy(IContained):
... containers(IBuddyFolder)
Now, with these interfaces in place, we can define Buddy and
BuddyFolder classes and verify that we can put buddies in buddy
folders:
>>> from zope import interface
>>> class Buddy:
... interface.implements(IBuddy)
>>> class BuddyFolder:
... interface.implements(IBuddyFolder)
>>> from zope.container.constraints import checkObject, checkFactory
>>> from zope.component.factory import Factory
>>> checkObject(BuddyFolder(), 'x', Buddy())
>>> checkFactory(BuddyFolder(), 'x', Factory(Buddy))
True
If we try to use other containers or folders, we'll get errors:
>>> class Container:
... interface.implements(IContainer)
>>> class Contained:
... interface.implements(IContained)
>>> checkObject(Container(), 'x', Buddy())
... # doctest: +ELLIPSIS
Traceback (most recent call last):
InvalidContainerType: ...
>>> checkFactory(Container(), 'x', Factory(Buddy))
False
>>> checkObject(BuddyFolder(), 'x', Contained())
... # doctest: +ELLIPSIS
Traceback (most recent call last):
InvalidItemType: ...
>>> checkFactory(BuddyFolder(), 'x', Factory(Contained))
False
In the example, we defined the container first and then the items. We
could have defined these in the opposite order:
>>> class IContact(IContained):
... containers('.IContacts')
>>> class IContacts(IContainer):
... contains(IContact)
>>> class Contact:
... interface.implements(IContact)
>>> class Contacts:
... interface.implements(IContacts)
>>> checkObject(Contacts(), 'x', Contact())
>>> checkFactory(Contacts(), 'x', Factory(Contact))
True
>>> checkObject(Contacts(), 'x', Buddy())
... # doctest: +ELLIPSIS
Traceback (most recent call last):
InvalidItemType: ...
>>> checkFactory(Contacts(), 'x', Factory(Buddy))
False
=======
CHANGES
=======
3.11.2 (2010-09-25)
-------------------
- Added not declared, but needed test dependency on `zope.testing`.
3.11.1 (2010-04-30)
-------------------
- Prefer the standard libraries doctest module to the one from zope.testing.
- Added compatibility with ZODB3 3.10 by importing the IBroken interface from
it directly. Once we can rely on the new ZODB3 version exclusively, we can
remove the dependency onto the zope.broken distribution.
- Never fail if the suggested name is in a wrong type (#227617)
- ``checkName`` first checks the parameter type before the emptiness.
3.11.0 (2009-12-31)
-------------------
- Copy two trivial classes from zope.cachedescriptors into this package, which
allows us to remove that dependency. We didn't actually use any caching
properties as the dependency suggested.
3.10.1 (2009-12-29)
-------------------
- Moved zope.copypastemove related tests into that package.
- Removed no longer used zcml prefix from the configure file.
- Stop importing DocTestSuite from zope.testing.doctestunit. Fixes
compatibility problems with zope.testing 3.8.4.
3.10.0 (2009-12-15)
-------------------
- Break testing dependency on zope.app.testing.
- Break testing dependency on zope.app.dependable by moving the code and tests
into that package.
- Import ISite from zope.component after it was moved there from
zope.location.
3.9.1 (2009-10-18)
------------------
- Rerelease 3.9.0 as it had a broken Windows 2.6 egg.
- Marked as part of the ZTK.
3.9.0 (2009-08-28)
------------------
- Previous releases should be versioned 3.9.0 as they are not pure bugfix
releases and worth a "feature" release, increasing feature version.
Packages that depend on any changes introduced in version 3.8.2 or 3.8.3
should depend on version 3.9 or greater.
3.8.3 (2009-08-27)
------------------
- Move IXMLRPCPublisher ZCML registrations for containers from
zope.app.publisher.xmlrpc to zope.container for now.
3.8.2 (2009-05-17)
------------------
- Rid ourselves of ``IContained`` interface. This interface was moved
to ``zope.location.interfaces``. A b/w compat import still exists
to keep old code running. Depend on ``zope.location``>=3.5.4.
- Rid ourselves of the implementations of ``IObjectMovedEvent``,
``IObjectAddedEvent``, ``IObjectRemovedEvent`` interfaces and
``ObjectMovedEvent``, ``ObjectAddedEvent`` and
``ObjectRemovedEvent`` classes. B/w compat imports still exist.
All of these were moved to ``zope.lifecycleevent``. Depend on
``zope.lifecycleevent``>=3.5.2.
- Fix a bug in OrderedContainer where trying to set the value for a
key that already exists (duplication error) would actually delete the
key from the order, leaving a dangling reference.
- Partially break dependency on ``zope.traversing`` by disusing
zope.traversing.api.getPath in favor of using
ILocationInfo(object).getPath(). The rest of the runtime
dependencies on zope.traversing are currently interface
dependencies.
- Break runtime dependency on ``zope.app.dependable`` by using a zcml
condition on the qsubscriber ZCML directive that registers the
CheckDependency handler for IObjectRemovedEvent. If
``zope.app.dependable`` is not installed, this subscriber will never
be registered. ``zope.app.dependable`` is now a testing dependency
only.
3.8.1 (2009-04-03)
------------------
- Fixed misspackaged 3.8.0
3.8.0 (2009-04-03)
------------------
- Change configure.zcml to not depend on zope.app.component.
Fixes: https://bugs.launchpad.net/bugs/348329
- Moved the declaration of ``IOrderedContainer.updateOrder`` to a new, basic
``IOrdered`` interface and let ``IOrderedContainer`` inherit it. This allows
easier reuse of the declaration.
3.7.2 (2009-03-12)
------------------
- Fix: added missing ComponentLookupError, missing since revision 95429 and
missing in last release.
- Adapt to the move of IDefaultViewName from zope.component.interfaces
to zope.publisher.interfaces.
- Add support for reserved names for containers. To specify reserved
names for some container, you need to provide an adapter from the
container to the ``zope.container.interfaces.IReservedNames`` interface.
The default NameChooser is now also aware of reserved names.
3.7.1 (2009-02-05)
------------------
- Raise more "Pythonic" errors from ``__setitem__``, losing the dependency
on ``zope.exceptions``:
o ``zope.exceptions.DuplicationError`` -> ``KeyError``
o ``zope.exceptions.UserError`` -> ``ValueError``
- Moved import of ``IBroken`` interface to use new ``zope.broken``
package, which has no dependencies beyond ``zope.interface``.
- Made ``test`` part pull in the extra test requirements of this package.
- Split the ``z3c.recipe.compattest`` configuration out into a new file,
``compat.cfg``, to reduce the burden of doing standard unit tests.
- Stripped out bogus develop eggs from ``buildout.cfg``.
3.7.0 (2009-01-31)
------------------
- Split this package off ``zope.app.container``. This package is
intended to have far less dependencies than ``zope.app.container``.
- This package also contains the container implementation that
used to be in ``zope.app.folder``.
Keywords: zope container
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Zope Public License
Classifier: Programming Language :: Python
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Framework :: Zope3
This package define interfaces of container components, and provides
container implementations such as a BTreeContainer and
OrderedContainer, as well as the base class used by ``zope.site.folder``
for the Folder implementation.
##############################################################################
#
# Copyright (c) 2006 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.
#
##############################################################################
"""Bootstrap a buildout-based project
Simply run this script in a directory containing a buildout.cfg.
The script accepts buildout command-line options, so you can
use the -c option to specify an alternate configuration file.
$Id: bootstrap.py 111712 2010-04-30 20:39:57Z hannosch $
"""
import os, shutil, sys, tempfile, urllib2
from optparse import OptionParser
tmpeggs = tempfile.mkdtemp()
is_jython = sys.platform.startswith('java')
# parsing arguments
parser = OptionParser()
parser.add_option("-v", "--version", dest="version",
help="use a specific zc.buildout version")
parser.add_option("-d", "--distribute",
action="store_true", dest="distribute", default=False,
help="Use Disribute rather than Setuptools.")
parser.add_option("-c", None, action="store", dest="config_file",
help=("Specify the path to the buildout configuration "
"file to be used."))
options, args = parser.parse_args()
# if -c was provided, we push it back into args for buildout' main function
if options.config_file is not None:
args += ['-c', options.config_file]
if options.version is not None:
VERSION = '==%s' % options.version
else:
VERSION = ''
USE_DISTRIBUTE = options.distribute
args = args + ['bootstrap']
to_reload = False
try:
import pkg_resources
if not hasattr(pkg_resources, '_distribute'):
to_reload = True
raise ImportError
except ImportError:
ez = {}
if USE_DISTRIBUTE:
exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py'
).read() in ez
ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True)
else:
exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
).read() in ez
ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
if to_reload:
reload(pkg_resources)
else:
import pkg_resources
if sys.platform == 'win32':
def quote(c):
if ' ' in c:
return '"%s"' % c # work around spawn lamosity on windows
else:
return c
else:
def quote (c):
return c
cmd = 'from setuptools.command.easy_install import main; main()'
ws = pkg_resources.working_set
if USE_DISTRIBUTE:
requirement = 'distribute'
else:
requirement = 'setuptools'
if is_jython:
import subprocess
assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd',
quote(tmpeggs), 'zc.buildout' + VERSION],
env=dict(os.environ,
PYTHONPATH=
ws.find(pkg_resources.Requirement.parse(requirement)).location
),
).wait() == 0
else:
assert os.spawnle(
os.P_WAIT, sys.executable, quote (sys.executable),
'-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION,
dict(os.environ,
PYTHONPATH=
ws.find(pkg_resources.Requirement.parse(requirement)).location
),
) == 0
ws.add_entry(tmpeggs)
ws.require('zc.buildout' + VERSION)
import zc.buildout.buildout
zc.buildout.buildout.main(args)
shutil.rmtree(tmpeggs)
[buildout]
develop = .
parts = test graph coverage-test coverage-report
[test]
recipe = zc.recipe.testrunner
eggs = zope.container[test]
[graph]
recipe = zc.recipe.egg
eggs = ${test:eggs}
tl.eggdeps
[coverage-test]
recipe = zc.recipe.testrunner
eggs = ${test:eggs}
defaults = ['--coverage', '../../coverage']
[coverage-report]
recipe = zc.recipe.egg
eggs = z3c.coverage
scripts = coverage=coverage-report
arguments = ('coverage', 'coverage/report')
[buildout]
extends = buildout.cfg
develop = .
parts = test graph compat
versions = versions
[versions]
ZODB3 = 3.8
zope.app.apidoc = 3.5
[compat]
recipe = z3c.recipe.compattest
max_jobs = 5
# the following are needed by the tests
transaction
ZODB
zope.interface
zope.testing
===================
Persistence support
===================
(This document is under construction. More basic documentation will eventually
appear here.)
Overriding `__getattr__`, `__getattribute__`, `__setattr__`, and `__delattr__`
------------------------------------------------------------------------------
Subclasses can override the attribute-management methods. For the
`__getattr__` method, the behavior is like that for regular Python
classes and for earlier versions of ZODB 3.
For `__getattribute__`, __setattr__`, and `__delattr__`, it is necessary
to call certain methods defined by `persistent.Persistent`. Detailed
examples and documentation is provided in the test module,
`persistent.tests.test_overriding_attrs`.
# Extension information for zpkg.
# Mark an "exported" header for use from other packages.
# This is not needed for headers only used within the package.
#
header cPersistence.h
# This is included by cPersistence.h, so all users of cPersistence.h
# have to be able to include this indirectly.
#
header ring.h
<extension cPersistence>
source cPersistence.c
source ring.c
depends-on cPersistence.h
depends-on ring.h
</extension>
<extension cPickleCache>
source cPickleCache.c
source ring.c
depends-on cPersistence.h
depends-on ring.h
</extension>
<extension TimeStamp>
source TimeStamp.c
</extension>
/*****************************************************************************
Copyright (c) 2001, 2004 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.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 <time.h>
PyObject *TimeStamp_FromDate(int, int, int, int, int, double);
PyObject *TimeStamp_FromString(const char *);
static char TimeStampModule_doc[] =
"A 64-bit TimeStamp used as a ZODB serial number.\n"
"\n"
"$Id: TimeStamp.c 41599 2006-02-11 21:33:49Z tseaver $\n";
typedef struct {
PyObject_HEAD
unsigned char data[8];
} TimeStamp;
/* The first dimension of the arrays below is non-leapyear / leapyear */
static char month_len[2][12]={
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
static short joff[2][12] = {
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
};
static double gmoff=0;
/* TODO: May be better (faster) to store in a file static. */
#define SCONV ((double)60) / ((double)(1<<16)) / ((double)(1<<16))
static int
leap(int year)
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
static int
days_in_month(int year, int month)
{
return month_len[leap(year)][month];
}
static double
TimeStamp_yad(int y)
{
double d, s;
y -= 1900;
d = (y - 1) * 365;
if (y > 0) {
s = 1.0;
y -= 1;
} else {
s = -1.0;
y = -y;
}
return d + s * (y / 4 - y / 100 + (y + 300) / 400);
}
static double
TimeStamp_abst(int y, int mo, int d, int m, int s)
{
return (TimeStamp_yad(y) + joff[leap(y)][mo] + d) * 86400 + m * 60 + s;
}
static int
TimeStamp_init_gmoff(void)
{
struct tm *t;
time_t z=0;
t = gmtime(&z);
if (t == NULL) {
PyErr_SetString(PyExc_SystemError, "gmtime failed");
return -1;
}
gmoff = TimeStamp_abst(t->tm_year+1900, t->tm_mon, t->tm_mday - 1,
t->tm_hour * 60 + t->tm_min, t->tm_sec);
return 0;
}
static void
TimeStamp_dealloc(TimeStamp *ts)
{
PyObject_Del(ts);
}
static int
TimeStamp_compare(TimeStamp *v, TimeStamp *w)
{
int cmp = memcmp(v->data, w->data, 8);
if (cmp < 0) return -1;
if (cmp > 0) return 1;
return 0;
}
static long
TimeStamp_hash(TimeStamp *self)
{
register unsigned char *p = (unsigned char *)self->data;
register int len = 8;
register long x = *p << 7;
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 long v;
v = (self->data[0] * 16777216 + self->data[1] * 65536
+ self->data[2] * 256 + self->data[3]);
p->y = v / 535680 + 1900;
p->m = (v % 535680) / 44640 + 1;
p->d = (v % 44640) / 1440 + 1;
p->mi = v % 1440;
}
static double
TimeStamp_sec(TimeStamp *self)
{
unsigned int v;
v = (self->data[4] * 16777216 + self->data[5] * 65536
+ self->data[6] * 256 + self->data[7]);
return SCONV * v;
}
static PyObject *
TimeStamp_year(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyInt_FromLong(p.y);
}
static PyObject *
TimeStamp_month(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyInt_FromLong(p.m);
}
static PyObject *
TimeStamp_day(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyInt_FromLong(p.d);
}
static PyObject *
TimeStamp_hour(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyInt_FromLong(p.mi / 60);
}
static PyObject *
TimeStamp_minute(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyInt_FromLong(p.mi % 60);
}
static PyObject *
TimeStamp_second(TimeStamp *self)
{
return PyFloat_FromDouble(TimeStamp_sec(self));
}
static PyObject *
TimeStamp_timeTime(TimeStamp *self)
{
TimeStampParts p;
TimeStamp_unpack(self, &p);
return PyFloat_FromDouble(TimeStamp_abst(p.y, p.m - 1, p.d - 1, p.mi, 0)
+ TimeStamp_sec(self) - gmoff);
}
static PyObject *
TimeStamp_raw(TimeStamp *self)
{
return PyString_FromStringAndSize((const char*)self->data, 8);
}
static PyObject *
TimeStamp_str(TimeStamp *self)
{
char buf[128];
TimeStampParts p;
int len;
TimeStamp_unpack(self, &p);
len =sprintf(buf, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%09.6f",
p.y, p.m, p.d, p.mi / 60, p.mi % 60,
TimeStamp_sec(self));
return PyString_FromStringAndSize(buf, len);
}
static PyObject *
TimeStamp_laterThan(TimeStamp *self, PyObject *obj)
{
TimeStamp *o = NULL;
TimeStampParts p;
unsigned char new[8];
int i;
if (obj->ob_type != self->ob_type) {
PyErr_SetString(PyExc_TypeError, "expected TimeStamp object");
return NULL;
}
o = (TimeStamp *)obj;
if (memcmp(self->data, o->data, 8) > 0) {
Py_INCREF(self);
return (PyObject *)self;
}
memcpy(new, o->data, 8);
for (i = 7; i > 3; i--) {
if (new[i] == 255)
new[i] = 0;
else {
new[i]++;
return TimeStamp_FromString((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},
};
static PyTypeObject TimeStamp_type = {
PyObject_HEAD_INIT(NULL)
0,
"persistent.TimeStamp",
sizeof(TimeStamp),
0,
(destructor)TimeStamp_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
(cmpfunc)TimeStamp_compare, /* tp_compare */
(reprfunc)TimeStamp_raw, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
(hashfunc)TimeStamp_hash, /* tp_hash */
0, /* tp_call */
(reprfunc)TimeStamp_str, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
TimeStamp_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
};
PyObject *
TimeStamp_FromString(const char *buf)
{
/* buf must be exactly 8 characters */
TimeStamp *ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type);
memcpy(ts->data, buf, 8);
return (PyObject *)ts;
}
#define CHECK_RANGE(VAR, LO, HI) if ((VAR) < (LO) || (VAR) > (HI)) { \
return PyErr_Format(PyExc_ValueError, \
# VAR " must be between %d and %d: %d", \
(LO), (HI), (VAR)); \
}
PyObject *
TimeStamp_FromDate(int year, int month, int day, int hour, int min,
double sec)
{
TimeStamp *ts = NULL;
int d;
unsigned int v;
if (year < 1900)
return PyErr_Format(PyExc_ValueError,
"year must be greater than 1900: %d", year);
CHECK_RANGE(month, 1, 12);
d = days_in_month(year, month - 1);
if (day < 1 || day > d)
return PyErr_Format(PyExc_ValueError,
"day must be between 1 and %d: %d", d, day);
CHECK_RANGE(hour, 0, 23);
CHECK_RANGE(min, 0, 59);
/* Seconds are allowed to be anything, so chill
If we did want to be pickly, 60 would be a better choice.
if (sec < 0 || sec > 59)
return PyErr_Format(PyExc_ValueError,
"second must be between 0 and 59: %f", sec);
*/
ts = (TimeStamp *)PyObject_New(TimeStamp, &TimeStamp_type);
v = (((year - 1900) * 12 + month - 1) * 31 + day - 1);
v = (v * 24 + hour) * 60 + min;
ts->data[0] = v / 16777216;
ts->data[1] = (v % 16777216) / 65536;
ts->data[2] = (v % 65536) / 256;
ts->data[3] = v % 256;
sec /= SCONV;
v = (unsigned int)sec;
ts->data[4] = v / 16777216;
ts->data[5] = (v % 16777216) / 65536;
ts->data[6] = (v % 65536) / 256;
ts->data[7] = v % 256;
return (PyObject *)ts;
}
PyObject *
TimeStamp_TimeStamp(PyObject *obj, PyObject *args)
{
char *buf = NULL;
int len = 0, y, mo, d, h = 0, m = 0;
double sec = 0;
if (PyArg_ParseTuple(args, "s#:TimeStamp", &buf, &len)) {
if (len != 8) {
PyErr_SetString(PyExc_ValueError, "8-character string expected");
return NULL;
}
return TimeStamp_FromString(buf);
}
PyErr_Clear();
if (!PyArg_ParseTuple(args, "iii|iid", &y, &mo, &d, &h, &m, &sec))
return NULL;
return TimeStamp_FromDate(y, mo, d, h, m, sec);
}
static PyMethodDef TimeStampModule_functions[] = {
{"TimeStamp", TimeStamp_TimeStamp, METH_VARARGS},
{NULL, NULL},
};
void
initTimeStamp(void)
{
PyObject *m;
if (TimeStamp_init_gmoff() < 0)
return;
m = Py_InitModule4("TimeStamp", TimeStampModule_functions,
TimeStampModule_doc, NULL, PYTHON_API_VERSION);
if (m == NULL)
return;
TimeStamp_type.ob_type = &PyType_Type;
TimeStamp_type.tp_getattro = PyObject_GenericGetAttr;
}
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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
#
##############################################################################
"""Provide access to Persistent and PersistentMapping.
$Id: __init__.py 25186 2004-06-02 15:07:33Z jim $
"""
from cPersistence import Persistent, GHOST, UPTODATE, CHANGED, STICKY
from cPickleCache import PickleCache
from cPersistence import simple_new
import copy_reg
copy_reg.constructor(simple_new)
# Make an interface declaration for Persistent,
# if zope.interface is available.
try:
from zope.interface import classImplements
except ImportError:
pass
else:
from persistent.interfaces import IPersistent
classImplements(Persistent, IPersistent)
/*****************************************************************************
Copyright (c) 2001, 2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.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
****************************************************************************/
static char cPersistence_doc_string[] =
"Defines Persistent mixin class for persistent objects.\n"
"\n"
"$Id: cPersistence.c 38459 2005-09-13 22:38:19Z tim_one $\n";
#include "cPersistence.h"
#include "structmember.h"
struct ccobject_head_struct {
CACHE_HEAD
};
/* These two objects are initialized when the module is loaded */
static PyObject *TimeStamp, *py_simple_new;
/* Strings initialized by init_strings() below. */
static PyObject *py_keys, *py_setstate, *py___dict__, *py_timeTime;
static PyObject *py__p_changed, *py__p_deactivate;
static PyObject *py___getattr__, *py___setattr__, *py___delattr__;
static PyObject *py___slotnames__, *copy_reg_slotnames, *__newobj__;
static PyObject *py___getnewargs__, *py___getstate__;
static int
init_strings(void)
{
#define INIT_STRING(S) \
if (!(py_ ## S = PyString_InternFromString(#S))) \
return -1;
INIT_STRING(keys);
INIT_STRING(setstate);
INIT_STRING(timeTime);
INIT_STRING(__dict__);
INIT_STRING(_p_changed);
INIT_STRING(_p_deactivate);
INIT_STRING(__getattr__);
INIT_STRING(__setattr__);
INIT_STRING(__delattr__);
INIT_STRING(__slotnames__);
INIT_STRING(__getnewargs__);
INIT_STRING(__getstate__);
#undef INIT_STRING
return 0;
}
#ifdef Py_DEBUG
static void
fatal_1350(cPersistentObject *self, const char *caller, const char *detail)
{
char buf[1000];
PyOS_snprintf(buf, sizeof(buf),
"cPersistence.c %s(): object at %p with type %.200s\n"
"%s.\n"
"The only known cause is multiple threads trying to ghost and\n"
"unghost the object simultaneously.\n"
"That's not legal, but ZODB can't stop it.\n"
"See Collector #1350.\n",
caller, self, self->ob_type->tp_name, detail);
Py_FatalError(buf);
}
#endif
static void ghostify(cPersistentObject*);
/* Load the state of the object, unghostifying it. Upon success, return 1.
* If an error occurred, re-ghostify the object and return -1.
*/
static int
unghostify(cPersistentObject *self)
{
if (self->state < 0 && self->jar) {
PyObject *r;
/* Is it ever possibly to not have a cache? */
if (self->cache) {
/* Create a node in the ring for this unghostified object. */
self->cache->non_ghost_count++;
ring_add(&self->cache->ring_home, &self->ring);
Py_INCREF(self);
}
/* set state to CHANGED while setstate() call is in progress
to prevent a recursive call to _PyPersist_Load().
*/
self->state = cPersistent_CHANGED_STATE;
/* Call the object's __setstate__() */
r = PyObject_CallMethod(self->jar, "setstate", "O", (PyObject *)self);
if (r == NULL) {
ghostify(self);
return -1;
}
self->state = cPersistent_UPTODATE_STATE;
Py_DECREF(r);
if (self->cache && self->ring.r_next == NULL) {
#ifdef Py_DEBUG
fatal_1350(self, "unghostify",
"is not in the cache despite that we just "
"unghostified it");
#else
PyErr_Format(PyExc_SystemError, "object at %p with type "
"%.200s not in the cache despite that we just "
"unghostified it", self, self->ob_type->tp_name);
return -1;
#endif
}
}
return 1;
}
/****************************************************************************/
static PyTypeObject Pertype;
static void
accessed(cPersistentObject *self)
{
/* Do nothing unless the object is in a cache and not a ghost. */
if (self->cache && self->state >= 0 && self->ring.r_next)
ring_move_to_head(&self->cache->ring_home, &self->ring);
}
static void
unlink_from_ring(cPersistentObject *self)
{
/* If the cache has been cleared, then a non-ghost object
isn't in the ring any longer.
*/
if (self->ring.r_next == NULL)
return;
/* if we're ghostifying an object, we better have some non-ghosts */
assert(self->cache->non_ghost_count > 0);
self->cache->non_ghost_count--;
ring_del(&self->ring);
}
static void
ghostify(cPersistentObject *self)
{
PyObject **dictptr;
/* are we already a ghost? */
if (self->state == cPersistent_GHOST_STATE)
return;
/* Is it ever possible to not have a cache? */
if (self->cache == NULL) {
self->state = cPersistent_GHOST_STATE;
return;
}
if (self->ring.r_next == NULL) {
/* There's no way to raise an error in this routine. */
#ifdef Py_DEBUG
fatal_1350(self, "ghostify", "claims to be in a cache but isn't");
#else
return;
#endif
}
/* If we're ghostifying an object, we better have some non-ghosts. */
assert(self->cache->non_ghost_count > 0);
self->cache->non_ghost_count--;
ring_del(&self->ring);
self->state = cPersistent_GHOST_STATE;
dictptr = _PyObject_GetDictPtr((PyObject *)self);
if (dictptr && *dictptr) {
Py_DECREF(*dictptr);
*dictptr = NULL;
}
/* We remove the reference to the just ghosted object that the ring
* holds. Note that the dictionary of oids->objects has an uncounted
* reference, so if the ring's reference was the only one, this frees
* the ghost object. Note further that the object's dealloc knows to
* inform the dictionary that it is going away.
*/
Py_DECREF(self);
}
static int
changed(cPersistentObject *self)
{
if ((self->state == cPersistent_UPTODATE_STATE ||
self->state == cPersistent_STICKY_STATE)
&& self->jar)
{
PyObject *meth, *arg, *result;
static PyObject *s_register;
if (s_register == NULL)
s_register = PyString_InternFromString("register");
meth = PyObject_GetAttr((PyObject *)self->jar, s_register);
if (meth == NULL)
return -1;
arg = PyTuple_New(1);
if (arg == NULL) {
Py_DECREF(meth);
return -1;
}
Py_INCREF(self);
PyTuple_SET_ITEM(arg, 0, (PyObject *)self);
result = PyEval_CallObject(meth, arg);
Py_DECREF(arg);
Py_DECREF(meth);
if (result == NULL)
return -1;
Py_DECREF(result);
self->state = cPersistent_CHANGED_STATE;
}
return 0;
}
static PyObject *
Per__p_deactivate(cPersistentObject *self)
{
if (self->state == cPersistent_UPTODATE_STATE && self->jar) {
PyObject **dictptr = _PyObject_GetDictPtr((PyObject *)self);
if (dictptr && *dictptr) {
Py_DECREF(*dictptr);
*dictptr = NULL;
}
/* Note that we need to set to ghost state unless we are
called directly. Methods that override this need to
do the same! */
ghostify(self);
}
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
Per__p_activate(cPersistentObject *self)
{
if (unghostify(self) < 0)
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static int Per_set_changed(cPersistentObject *self, PyObject *v);
static PyObject *
Per__p_invalidate(cPersistentObject *self)
{
signed char old_state = self->state;
if (old_state != cPersistent_GHOST_STATE) {
if (Per_set_changed(self, NULL) < 0)
return NULL;
ghostify(self);
}
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
pickle_slotnames(PyTypeObject *cls)
{
PyObject *slotnames;
slotnames = PyDict_GetItem(cls->tp_dict, py___slotnames__);
if (slotnames) {
Py_INCREF(slotnames);
return slotnames;
}
slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames,
(PyObject*)cls, NULL);
if (slotnames && !(slotnames == Py_None || PyList_Check(slotnames))) {
PyErr_SetString(PyExc_TypeError,
"copy_reg._slotnames didn't return a list or None");
Py_DECREF(slotnames);
return NULL;
}
return slotnames;
}
static PyObject *
pickle_copy_dict(PyObject *state)
{
PyObject *copy, *key, *value;
char *ckey;
int pos = 0;
copy = PyDict_New();
if (!copy)
return NULL;
if (!state)
return copy;
while (PyDict_Next(state, &pos, &key, &value)) {
if (key && PyString_Check(key)) {
ckey = PyString_AS_STRING(key);
if (*ckey == '_' &&
(ckey[1] == 'v' || ckey[1] == 'p') &&
ckey[2] == '_')
/* skip volatile and persistent */
continue;
}
if (PyObject_SetItem(copy, key, value) < 0)
goto err;
}
return copy;
err:
Py_DECREF(copy);
return NULL;
}
static char pickle___getstate__doc[] =
"Get the object serialization state\n"
"\n"
"If the object has no assigned slots and has no instance dictionary, then \n"
"None is returned.\n"
"\n"
"If the object has no assigned slots and has an instance dictionary, then \n"
"the a copy of the instance dictionary is returned. The copy has any items \n"
"with names starting with '_v_' or '_p_' ommitted.\n"
"\n"
"If the object has assigned slots, then a two-element tuple is returned. \n"
"The first element is either None or a copy of the instance dictionary, \n"
"as described above. The second element is a dictionary with items \n"
"for each of the assigned slots.\n"
;
static PyObject *
pickle___getstate__(PyObject *self)
{
PyObject *slotnames=NULL, *slots=NULL, *state=NULL;
PyObject **dictp;
int n=0;
slotnames = pickle_slotnames(self->ob_type);
if (!slotnames)
return NULL;
dictp = _PyObject_GetDictPtr(self);
if (dictp)
state = pickle_copy_dict(*dictp);
else {
state = Py_None;
Py_INCREF(state);
}
if (slotnames != Py_None) {
int i;
slots = PyDict_New();
if (!slots)
goto end;
for (i = 0; i < PyList_GET_SIZE(slotnames); i++) {
PyObject *name, *value;
char *cname;
name = PyList_GET_ITEM(slotnames, i);
if (PyString_Check(name)) {
cname = PyString_AS_STRING(name);
if (*cname == '_' &&
(cname[1] == 'v' || cname[1] == 'p') &&
cname[2] == '_')
/* skip volatile and persistent */
continue;
}
/* Unclear: Will this go through our getattr hook? */
value = PyObject_GetAttr(self, name);
if (value == NULL)
PyErr_Clear();
else {
int err = PyDict_SetItem(slots, name, value);
Py_DECREF(value);
if (err < 0)
goto end;
n++;
}
}
}
if (n)
state = Py_BuildValue("(NO)", state, slots);
end:
Py_XDECREF(slotnames);
Py_XDECREF(slots);
return state;
}
static int
pickle_setattrs_from_dict(PyObject *self, PyObject *dict)
{
PyObject *key, *value;
int pos = 0;
if (!PyDict_Check(dict)) {
PyErr_SetString(PyExc_TypeError, "Expected dictionary");
return -1;
}
while (PyDict_Next(dict, &pos, &key, &value)) {
if (PyObject_SetAttr(self, key, value) < 0)
return -1;
}
return 0;
}
static char pickle___setstate__doc[] =
"Set the object serialization state\n\n"
"The state should be in one of 3 forms:\n\n"
"- None\n\n"
" Ignored\n\n"
"- A dictionary\n\n"
" In this case, the object's instance dictionary will be cleared and \n"
" updated with the new state.\n\n"
"- A two-tuple with a string as the first element. \n\n"
" In this case, the method named by the string in the first element will be\n"
" called with the second element.\n\n"
" This form supports migration of data formats.\n\n"
"- A two-tuple with None or a Dictionary as the first element and\n"
" with a dictionary as the second element.\n\n"
" If the first element is not None, then the object's instance dictionary \n"
" will be cleared and updated with the value.\n\n"
" The items in the second element will be assigned as attributes.\n"
;
static PyObject *
pickle___setstate__(PyObject *self, PyObject *state)
{
PyObject *slots=NULL;
if (PyTuple_Check(state)) {
if (!PyArg_ParseTuple(state, "OO:__setstate__", &state, &slots))
return NULL;
}
if (state != Py_None) {
PyObject **dict;
dict = _PyObject_GetDictPtr(self);
if (dict) {
if (!*dict) {
*dict = PyDict_New();
if (!*dict)
return NULL;
}
}
if (*dict) {
PyDict_Clear(*dict);
if (PyDict_Update(*dict, state) < 0)
return NULL;
}
else if (pickle_setattrs_from_dict(self, state) < 0)
return NULL;
}
if (slots && pickle_setattrs_from_dict(self, slots) < 0)
return NULL;
Py_INCREF(Py_None);
return Py_None;
}
static char pickle___reduce__doc[] =
"Reduce an object to contituent parts for serialization\n"
;
static PyObject *
pickle___reduce__(PyObject *self)
{
PyObject *args=NULL, *bargs=NULL, *state=NULL, *getnewargs=NULL;
int l, i;
getnewargs = PyObject_GetAttr(self, py___getnewargs__);
if (getnewargs) {
bargs = PyObject_CallFunctionObjArgs(getnewargs, NULL);
Py_DECREF(getnewargs);
if (!bargs)
return NULL;
l = PyTuple_Size(bargs);
if (l < 0)
goto end;
}
else {
PyErr_Clear();
l = 0;
}
args = PyTuple_New(l+1);
if (args == NULL)
goto end;
Py_INCREF(self->ob_type);
PyTuple_SET_ITEM(args, 0, (PyObject*)(self->ob_type));
for (i = 0; i < l; i++) {
Py_INCREF(PyTuple_GET_ITEM(bargs, i));
PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i));
}
state = PyObject_CallMethodObjArgs(self, py___getstate__, NULL);
if (!state)
goto end;
state = Py_BuildValue("(OON)", __newobj__, args, state);
end:
Py_XDECREF(bargs);
Py_XDECREF(args);
return state;
}
/* Return the object's state, a dict or None.
If the object has no dict, it's state is None.
Otherwise, return a dict containing all the attributes that
don't start with "_v_".
The caller should not modify this dict, as it may be a reference to
the object's __dict__.
*/
static PyObject *
Per__getstate__(cPersistentObject *self)
{
/* TODO: Should it be an error to call __getstate__() on a ghost? */
if (unghostify(self) < 0)
return NULL;
/* TODO: should we increment stickyness? Tim doesn't understand that
question. S*/
return pickle___getstate__((PyObject*)self);
}
/* The Persistent base type provides a traverse function, but not a
clear function. An instance of a Persistent subclass will have
its dict cleared through subtype_clear().
There is always a cycle between a persistent object and its cache.
When the cycle becomes unreachable, the clear function for the
cache will break the cycle. Thus, the persistent object need not
have a clear function. It would be complex to write a clear function
for the objects, if we needed one, because of the reference count
tricks done by the cache.
*/
static void
Per_dealloc(cPersistentObject *self)
{
if (self->state >= 0)
unlink_from_ring(self);
if (self->cache)
cPersistenceCAPI->percachedel(self->cache, self->oid);
Py_XDECREF(self->cache);
Py_XDECREF(self->jar);
Py_XDECREF(self->oid);
self->ob_type->tp_free(self);
}
static int
Per_traverse(cPersistentObject *self, visitproc visit, void *arg)
{
int err;
#define VISIT(SLOT) \
if (SLOT) { \
err = visit((PyObject *)(SLOT), arg); \
if (err) \
return err; \
}
VISIT(self->jar);
VISIT(self->oid);
VISIT(self->cache);
#undef VISIT
return 0;
}
/* convert_name() returns a new reference to a string name
or sets an exception and returns NULL.
*/
static PyObject *
convert_name(PyObject *name)
{
#ifdef Py_USING_UNICODE
/* The Unicode to string conversion is done here because the
existing tp_setattro slots expect a string object as name
and we wouldn't want to break those. */
if (PyUnicode_Check(name)) {
name = PyUnicode_AsEncodedString(name, NULL, NULL);
}
else
#endif
if (!PyString_Check(name)) {
PyErr_SetString(PyExc_TypeError, "attribute name must be a string");
return NULL;
} else
Py_INCREF(name);
return name;
}
/* Returns true if the object requires unghostification.
There are several special attributes that we allow access to without
requiring that the object be unghostified:
__class__
__del__
__dict__
__of__
__setstate__
*/
static int
unghost_getattr(const char *s)
{
if (*s++ != '_')
return 1;
if (*s == 'p') {
s++;
if (*s == '_')
return 0; /* _p_ */
else
return 1;
}
else if (*s == '_') {
s++;
switch (*s) {
case 'c':
return strcmp(s, "class__");
case 'd':
s++;
if (!strcmp(s, "el__"))
return 0; /* __del__ */
if (!strcmp(s, "ict__"))
return 0; /* __dict__ */
return 1;
case 'o':
return strcmp(s, "of__");
case 's':
return strcmp(s, "setstate__");
default:
return 1;
}
}
return 1;
}
static PyObject*
Per_getattro(cPersistentObject *self, PyObject *name)
{
PyObject *result = NULL; /* guilty until proved innocent */
char *s;
name = convert_name(name);
if (!name)
goto Done;
s = PyString_AS_STRING(name);
if (unghost_getattr(s)) {
if (unghostify(self) < 0)
goto Done;
accessed(self);
}
result = PyObject_GenericGetAttr((PyObject *)self, name);
Done:
Py_XDECREF(name);
return result;
}
/* Exposed as _p_getattr method. Test whether base getattr should be used */
static PyObject *
Per__p_getattr(cPersistentObject *self, PyObject *name)
{
PyObject *result = NULL; /* guilty until proved innocent */
char *s;
name = convert_name(name);
if (!name)
goto Done;
s = PyString_AS_STRING(name);
if (*s != '_' || unghost_getattr(s)) {
if (unghostify(self) < 0)
goto Done;
accessed(self);
result = Py_False;
}
else
result = Py_True;
Py_INCREF(result);
Done:
Py_XDECREF(name);
return result;
}
/*
TODO: we should probably not allow assignment of __class__ and __dict__.
*/
static int
Per_setattro(cPersistentObject *self, PyObject *name, PyObject *v)
{
int result = -1; /* guilty until proved innocent */
char *s;
name = convert_name(name);
if (!name)
goto Done;
s = PyString_AS_STRING(name);
if (strncmp(s, "_p_", 3) != 0) {
if (unghostify(self) < 0)
goto Done;
accessed(self);
if (strncmp(s, "_v_", 3) != 0
&& self->state != cPersistent_CHANGED_STATE) {
if (changed(self) < 0)
goto Done;
}
}
result = PyObject_GenericSetAttr((PyObject *)self, name, v);
Done:
Py_XDECREF(name);
return result;
}
static int
Per_p_set_or_delattro(cPersistentObject *self, PyObject *name, PyObject *v)
{
int result = -1; /* guilty until proved innocent */
char *s;
name = convert_name(name);
if (!name)
goto Done;
s = PyString_AS_STRING(name);
if (strncmp(s, "_p_", 3)) {
if (unghostify(self) < 0)
goto Done;
accessed(self);
result = 0;
}
else {
if (PyObject_GenericSetAttr((PyObject *)self, name, v) < 0)
goto Done;
result = 1;
}
Done:
Py_XDECREF(name);
return result;
}
static PyObject *
Per__p_setattr(cPersistentObject *self, PyObject *args)
{
PyObject *name, *v, *result;
int r;
if (!PyArg_ParseTuple(args, "OO:_p_setattr", &name, &v))
return NULL;
r = Per_p_set_or_delattro(self, name, v);
if (r < 0)
return NULL;
result = r ? Py_True : Py_False;
Py_INCREF(result);
return result;
}
static PyObject *
Per__p_delattr(cPersistentObject *self, PyObject *name)
{
int r;
PyObject *result;
r = Per_p_set_or_delattro(self, name, NULL);
if (r < 0)
return NULL;
result = r ? Py_True : Py_False;
Py_INCREF(result);
return result;
}
static PyObject *
Per_get_changed(cPersistentObject *self)
{
if (self->state < 0) {
Py_INCREF(Py_None);
return Py_None;
}
return PyBool_FromLong(self->state == cPersistent_CHANGED_STATE);
}
static int
Per_set_changed(cPersistentObject *self, PyObject *v)
{
int deactivate = 0;
int true;
if (!v) {
/* delattr is used to invalidate an object even if it has changed. */
if (self->state != cPersistent_GHOST_STATE)
self->state = cPersistent_UPTODATE_STATE;
deactivate = 1;
}
else if (v == Py_None)
deactivate = 1;
if (deactivate) {
PyObject *res, *meth;
meth = PyObject_GetAttr((PyObject *)self, py__p_deactivate);
if (meth == NULL)
return -1;
res = PyObject_CallObject(meth, NULL);
if (res)
Py_DECREF(res);
else {
/* an error occured in _p_deactivate().
It's not clear what we should do here. The code is
obviously ignoring the exception, but it shouldn't return
0 for a getattr and set an exception. The simplest change
is to clear the exception, but that simply masks the
error.
This prints an error to stderr just like exceptions in
__del__(). It would probably be better to log it but that
would be painful from C.
*/
PyErr_WriteUnraisable(meth);
}
Py_DECREF(meth);
return 0;
}
/* !deactivate. If passed a true argument, mark self as changed (starting
* with ZODB 3.6, that includes activating the object if it's a ghost).
* If passed a false argument, and the object isn't a ghost, set the
* state as up-to-date.
*/
true = PyObject_IsTrue(v);
if (true == -1)
return -1;
if (true) {
if (self->state < 0) {
if (unghostify(self) < 0)
return -1;
}
return changed(self);
}
/* We were passed a false, non-None argument. If we're not a ghost,
* mark self as up-to-date.
*/
if (self->state >= 0)
self->state = cPersistent_UPTODATE_STATE;
return 0;
}
static PyObject *
Per_get_oid(cPersistentObject *self)
{
PyObject *oid = self->oid ? self->oid : Py_None;
Py_INCREF(oid);
return oid;
}
static int
Per_set_oid(cPersistentObject *self, PyObject *v)
{
if (self->cache) {
int result;
if (v == NULL) {
PyErr_SetString(PyExc_ValueError,
"can't delete _p_oid of cached object");
return -1;
}
if (PyObject_Cmp(self->oid, v, &result) < 0)
return -1;
if (result) {
PyErr_SetString(PyExc_ValueError,
"can not change _p_oid of cached object");
return -1;
}
}
Py_XDECREF(self->oid);
Py_XINCREF(v);
self->oid = v;
return 0;
}
static PyObject *
Per_get_jar(cPersistentObject *self)
{
PyObject *jar = self->jar ? self->jar : Py_None;
Py_INCREF(jar);
return jar;
}
static int
Per_set_jar(cPersistentObject *self, PyObject *v)
{
if (self->cache) {
int result;
if (v == NULL) {
PyErr_SetString(PyExc_ValueError,
"can't delete _p_jar of cached object");
return -1;
}
if (PyObject_Cmp(self->jar, v, &result) < 0)
return -1;
if (result) {
PyErr_SetString(PyExc_ValueError,
"can not change _p_jar of cached object");
return -1;
}
}
Py_XDECREF(self->jar);
Py_XINCREF(v);
self->jar = v;
return 0;
}
static PyObject *
Per_get_serial(cPersistentObject *self)
{
return PyString_FromStringAndSize(self->serial, 8);
}
static int
Per_set_serial(cPersistentObject *self, PyObject *v)
{
if (v) {
if (PyString_Check(v) && PyString_GET_SIZE(v) == 8)
memcpy(self->serial, PyString_AS_STRING(v), 8);
else {
PyErr_SetString(PyExc_ValueError,
"_p_serial must be an 8-character string");
return -1;
}
} else
memset(self->serial, 0, 8);
return 0;
}
static PyObject *
Per_get_mtime(cPersistentObject *self)
{
PyObject *t, *v;
if (unghostify(self) < 0)
return NULL;
accessed(self);
if (memcmp(self->serial, "\0\0\0\0\0\0\0\0", 8) == 0) {
Py_INCREF(Py_None);
return Py_None;
}
t = PyObject_CallFunction(TimeStamp, "s#", self->serial, 8);
if (!t)
return NULL;
v = PyObject_CallMethod(t, "timeTime", "");
Py_DECREF(t);
return v;
}
static PyObject *
Per_get_state(cPersistentObject *self)
{
return PyInt_FromLong(self->state);
}
static PyGetSetDef Per_getsets[] = {
{"_p_changed", (getter)Per_get_changed, (setter)Per_set_changed},
{"_p_jar", (getter)Per_get_jar, (setter)Per_set_jar},
{"_p_mtime", (getter)Per_get_mtime},
{"_p_oid", (getter)Per_get_oid, (setter)Per_set_oid},
{"_p_serial", (getter)Per_get_serial, (setter)Per_set_serial},
{"_p_state", (getter)Per_get_state},
{NULL}
};
static struct PyMethodDef Per_methods[] = {
{"_p_deactivate", (PyCFunction)Per__p_deactivate, METH_NOARGS,
"_p_deactivate() -- Deactivate the object"},
{"_p_activate", (PyCFunction)Per__p_activate, METH_NOARGS,
"_p_activate() -- Activate the object"},
{"_p_invalidate", (PyCFunction)Per__p_invalidate, METH_NOARGS,
"_p_invalidate() -- Invalidate the object"},
{"_p_getattr", (PyCFunction)Per__p_getattr, METH_O,
"_p_getattr(name) -- Test whether the base class must handle the name\n"
"\n"
"The method unghostifies the object, if necessary.\n"
"The method records the object access, if necessary.\n"
"\n"
"This method should be called by subclass __getattribute__\n"
"implementations before doing anything else. If the method\n"
"returns True, then __getattribute__ implementations must delegate\n"
"to the base class, Persistent.\n"
},
{"_p_setattr", (PyCFunction)Per__p_setattr, METH_VARARGS,
"_p_setattr(name, value) -- Save persistent meta data\n"
"\n"
"This method should be called by subclass __setattr__ implementations\n"
"before doing anything else. If it returns true, then the attribute\n"
"was handled by the base class.\n"
"\n"
"The method unghostifies the object, if necessary.\n"
"The method records the object access, if necessary.\n"
},
{"_p_delattr", (PyCFunction)Per__p_delattr, METH_O,
"_p_delattr(name) -- Delete persistent meta data\n"
"\n"
"This method should be called by subclass __delattr__ implementations\n"
"before doing anything else. If it returns true, then the attribute\n"
"was handled by the base class.\n"
"\n"
"The method unghostifies the object, if necessary.\n"
"The method records the object access, if necessary.\n"
},
{"__getstate__", (PyCFunction)Per__getstate__, METH_NOARGS,
pickle___getstate__doc },
{"__setstate__", (PyCFunction)pickle___setstate__, METH_O,
pickle___setstate__doc},
{"__reduce__", (PyCFunction)pickle___reduce__, METH_NOARGS,
pickle___reduce__doc},
{NULL, NULL} /* sentinel */
};
/* This module is compiled as a shared library. Some compilers don't
allow addresses of Python objects defined in other libraries to be
used in static initializers here. The DEFERRED_ADDRESS macro is
used to tag the slots where such addresses appear; the module init
function must fill in the tagged slots at runtime. The argument is
for documentation -- the macro ignores it.
*/
#define DEFERRED_ADDRESS(ADDR) 0
static PyTypeObject Pertype = {
PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyPersist_MetaType))
0, /* ob_size */
"persistent.Persistent", /* tp_name */
sizeof(cPersistentObject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)Per_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
(getattrofunc)Per_getattro, /* tp_getattro */
(setattrofunc)Per_setattro, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
/* tp_flags */
0, /* tp_doc */
(traverseproc)Per_traverse, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Per_methods, /* tp_methods */
0, /* tp_members */
Per_getsets, /* tp_getset */
};
/* End of code for Persistent objects */
/* -------------------------------------------------------- */
typedef int (*intfunctionwithpythonarg)(PyObject*);
/* Load the object's state if necessary and become sticky */
static int
Per_setstate(cPersistentObject *self)
{
if (unghostify(self) < 0)
return -1;
self->state = cPersistent_STICKY_STATE;
return 0;
}
static PyObject *
simple_new(PyObject *self, PyObject *type_object)
{
return PyType_GenericNew((PyTypeObject *)type_object, NULL, NULL);
}
static PyMethodDef cPersistence_methods[] = {
{"simple_new", simple_new, METH_O,
"Create an object by simply calling a class's __new__ method without "
"arguments."},
{NULL, NULL}
};
static cPersistenceCAPIstruct
truecPersistenceCAPI = {
&Pertype,
(getattrofunc)Per_getattro, /*tp_getattr with object key*/
(setattrofunc)Per_setattro, /*tp_setattr with object key*/
changed,
accessed,
ghostify,
(intfunctionwithpythonarg)Per_setstate,
NULL /* The percachedel slot is initialized in cPickleCache.c when
the module is loaded. It uses a function in a different
shared library. */
};
void
initcPersistence(void)
{
PyObject *m, *s;
PyObject *copy_reg;
if (init_strings() < 0)
return;
m = Py_InitModule3("cPersistence", cPersistence_methods,
cPersistence_doc_string);
Pertype.ob_type = &PyType_Type;
Pertype.tp_new = PyType_GenericNew;
if (PyType_Ready(&Pertype) < 0)
return;
if (PyModule_AddObject(m, "Persistent", (PyObject *)&Pertype) < 0)
return;
cPersistenceCAPI = &truecPersistenceCAPI;
s = PyCObject_FromVoidPtr(cPersistenceCAPI, NULL);
if (!s)
return;
if (PyModule_AddObject(m, "CAPI", s) < 0)
return;
if (PyModule_AddIntConstant(m, "GHOST", cPersistent_GHOST_STATE) < 0)
return;
if (PyModule_AddIntConstant(m, "UPTODATE", cPersistent_UPTODATE_STATE) < 0)
return;
if (PyModule_AddIntConstant(m, "CHANGED", cPersistent_CHANGED_STATE) < 0)
return;
if (PyModule_AddIntConstant(m, "STICKY", cPersistent_STICKY_STATE) < 0)
return;
py_simple_new = PyObject_GetAttrString(m, "simple_new");
if (!py_simple_new)
return;
copy_reg = PyImport_ImportModule("copy_reg");
if (!copy_reg)
return;
copy_reg_slotnames = PyObject_GetAttrString(copy_reg, "_slotnames");
if (!copy_reg_slotnames) {
Py_DECREF(copy_reg);
return;
}
__newobj__ = PyObject_GetAttrString(copy_reg, "__newobj__");
if (!__newobj__) {
Py_DECREF(copy_reg);
return;
}
if (!TimeStamp) {
m = PyImport_ImportModule("persistent.TimeStamp");
if (!m)
return;
TimeStamp = PyObject_GetAttrString(m, "TimeStamp");
Py_DECREF(m);
/* fall through to immediate return on error */
}
}
/*****************************************************************************
Copyright (c) 2001, 2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.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 "Python.h"
#include "ring.h"
#define CACHE_HEAD \
PyObject_HEAD \
CPersistentRing ring_home; \
int non_ghost_count;
struct ccobject_head_struct;
typedef struct ccobject_head_struct PerCache;
/* How big is a persistent object?
12 PyGC_Head is two pointers and an int
8 PyObject_HEAD is an int and a pointer
12 jar, oid, cache pointers
8 ring struct
8 serialno
4 state + extra
(52) so far
4 dict ptr
4 weaklist ptr
-------------------------
64 only need 62, but obmalloc rounds up to multiple of eight
Even a ghost requires 64 bytes. It's possible to make a persistent
instance with slots and no dict, which changes the storage needed.
*/
#define cPersistent_HEAD \
PyObject_HEAD \
PyObject *jar; \
PyObject *oid; \
PerCache *cache; \
CPersistentRing ring; \
char serial[8]; \
signed char state; \
unsigned char reserved[3];
#define cPersistent_GHOST_STATE -1
#define cPersistent_UPTODATE_STATE 0
#define cPersistent_CHANGED_STATE 1
#define cPersistent_STICKY_STATE 2
typedef struct {
cPersistent_HEAD
} cPersistentObject;
typedef void (*percachedelfunc)(PerCache *, PyObject *);
typedef struct {
PyTypeObject *pertype;
getattrofunc getattro;
setattrofunc setattro;
int (*changed)(cPersistentObject*);
void (*accessed)(cPersistentObject*);
void (*ghostify)(cPersistentObject*);
int (*setstate)(PyObject*);
percachedelfunc percachedel;
} cPersistenceCAPIstruct;
#define cPersistenceType cPersistenceCAPI->pertype
#ifndef DONT_USE_CPERSISTENCECAPI
static cPersistenceCAPIstruct *cPersistenceCAPI;
#endif
#define cPersistanceModuleName "cPersistence"
#define PER_TypeCheck(O) PyObject_TypeCheck((O), cPersistenceCAPI->pertype)
#define PER_USE_OR_RETURN(O,R) {if((O)->state==cPersistent_GHOST_STATE && cPersistenceCAPI->setstate((PyObject*)(O)) < 0) return (R); else if ((O)->state==cPersistent_UPTODATE_STATE) (O)->state=cPersistent_STICKY_STATE;}
#define PER_CHANGED(O) (cPersistenceCAPI->changed((cPersistentObject*)(O)))
#define PER_GHOSTIFY(O) (cPersistenceCAPI->ghostify((cPersistentObject*)(O)))
/* If the object is sticky, make it non-sticky, so that it can be ghostified.
The value is not meaningful
*/
#define PER_ALLOW_DEACTIVATION(O) ((O)->state==cPersistent_STICKY_STATE && ((O)->state=cPersistent_UPTODATE_STATE))
#define PER_PREVENT_DEACTIVATION(O) ((O)->state==cPersistent_UPTODATE_STATE && ((O)->state=cPersistent_STICKY_STATE))
/*
Make a persistent object usable from C by:
- Making sure it is not a ghost
- Making it sticky.
IMPORTANT: If you call this and don't call PER_ALLOW_DEACTIVATION,
your object will not be ghostified.
PER_USE returns a 1 on success and 0 failure, where failure means
error.
*/
#define PER_USE(O) \
(((O)->state != cPersistent_GHOST_STATE \
|| (cPersistenceCAPI->setstate((PyObject*)(O)) >= 0)) \
? (((O)->state==cPersistent_UPTODATE_STATE) \
? ((O)->state=cPersistent_STICKY_STATE) : 1) : 0)
#define PER_ACCESSED(O) (cPersistenceCAPI->accessed((cPersistentObject*)(O)))
#endif
/*****************************************************************************
Copyright (c) 2001, 2002 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.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
****************************************************************************/
/*
Objects are stored under three different regimes:
Regime 1: Persistent Classes
Persistent Classes are part of ZClasses. They are stored in the
self->data dictionary, and are never garbage collected.
The klass_items() method returns a sequence of (oid,object) tuples for
every Persistent Class, which should make it possible to implement
garbage collection in Python if necessary.
Regime 2: Ghost Objects
There is no benefit to keeping a ghost object which has no external
references, therefore a weak reference scheme is used to ensure that
ghost objects are removed from memory as soon as possible, when the
last external reference is lost.
Ghost objects are stored in the self->data dictionary. Normally a
dictionary keeps a strong reference on its values, however this
reference count is 'stolen'.
This weak reference scheme leaves a dangling reference, in the
dictionary, when the last external reference is lost. To clean up this
dangling reference the persistent object dealloc function calls
self->cache->_oid_unreferenced(self->oid). The cache looks up the oid
in the dictionary, ensures it points to an object whose reference
count is zero, then removes it from the dictionary. Before removing
the object from the dictionary it must temporarily resurrect the
object in much the same way that class instances are resurrected
before their __del__ is called.
Since ghost objects are stored under a different regime to non-ghost
objects, an extra ghostify function in cPersistenceAPI replaces
self->state=GHOST_STATE assignments that were common in other
persistent classes (such as BTrees).
Regime 3: Non-Ghost Objects
Non-ghost objects are stored in two data structures: the dictionary
mapping oids to objects and a doubly-linked list that encodes the
order in which the objects were accessed. The dictionary reference is
borrowed, as it is for ghosts. The list reference is a new reference;
the list stores recently used objects, even if they are otherwise
unreferenced, to avoid loading the object from the database again.
The doubly-link-list nodes contain next and previous pointers linking
together the cache and all non-ghost persistent objects.
The node embedded in the cache is the home position. On every
attribute access a non-ghost object will relink itself just behind the
home position in the ring. Objects accessed least recently will
eventually find themselves positioned after the home position.
Occasionally other nodes are temporarily inserted in the ring as
position markers. The cache contains a ring_lock flag which must be
set and unset before and after doing so. Only if the flag is unset can
the cache assume that all nodes are either his own home node, or nodes
from persistent objects. This assumption is useful during the garbage
collection process.
The number of non-ghost objects is counted in self->non_ghost_count.
The garbage collection process consists of traversing the ring, and
deactivating (that is, turning into a ghost) every object until
self->non_ghost_count is down to the target size, or until it
reaches the home position again.
Note that objects in the sticky or changed states are still kept in
the ring, however they can not be deactivated. The garbage collection
process must skip such objects, rather than deactivating them.
*/
static char cPickleCache_doc_string[] =
"Defines the PickleCache used by ZODB Connection objects.\n"
"\n"
"$Id: cPickleCache.c 29896 2005-04-07 04:48:06Z tim_one $\n";
#define DONT_USE_CPERSISTENCECAPI
#include "cPersistence.h"
#include "structmember.h"
#include <time.h>
#include <stddef.h>
#undef Py_FindMethod
/* Python string objects to speed lookups; set by module init. */
static PyObject *py__p_changed;
static PyObject *py__p_deactivate;
static PyObject *py__p_jar;
static PyObject *py__p_oid;
static cPersistenceCAPIstruct *capi;
/* This object is the pickle cache. The CACHE_HEAD macro guarantees
that layout of this struct is the same as the start of
ccobject_head in cPersistence.c */
typedef struct {
CACHE_HEAD
int klass_count; /* count of persistent classes */
PyObject *data; /* oid -> object dict */
PyObject *jar; /* Connection object */
int cache_size; /* target number of items in cache */
/* Most of the time the ring contains only:
* many nodes corresponding to persistent objects
* one 'home' node from the cache.
In some cases it is handy to temporarily add other types
of node into the ring as placeholders. 'ring_lock' is a boolean
indicating that someone has already done this. Currently this
is only used by the garbage collection code. */
int ring_lock;
/* 'cache_drain_resistance' controls how quickly the cache size will drop
when it is smaller than the configured size. A value of zero means it will
not drop below the configured size (suitable for most caches). Otherwise,
it will remove cache_non_ghost_count/cache_drain_resistance items from
the cache every time (suitable for rarely used caches, such as those
associated with Zope versions. */
int cache_drain_resistance;
} ccobject;
static int cc_ass_sub(ccobject *self, PyObject *key, PyObject *v);
/* ---------------------------------------------------------------- */
#define OBJECT_FROM_RING(SELF, HERE) \
((cPersistentObject *)(((char *)here) - offsetof(cPersistentObject, ring)))
/* Insert self into the ring, following after. */
static void
insert_after(CPersistentRing *self, CPersistentRing *after)
{
assert(self != NULL);
assert(after != NULL);
self->r_prev = after;
self->r_next = after->r_next;
after->r_next->r_prev = self;
after->r_next = self;
}
/* Remove self from the ring. */
static void
unlink_from_ring(CPersistentRing *self)
{
assert(self != NULL);
self->r_prev->r_next = self->r_next;
self->r_next->r_prev = self->r_prev;
}
static int
scan_gc_items(ccobject *self, int target)
{
/* This function must only be called with the ring lock held,
because it places non-object placeholders in the ring.
*/
cPersistentObject *object;
CPersistentRing *here;
CPersistentRing before_original_home;
int result = -1; /* guilty until proved innocent */
/* Scan the ring, from least to most recently used, deactivating
* up-to-date objects, until we either find the ring_home again or
* or we've ghosted enough objects to reach the target size.
* Tricky: __getattr__ and __del__ methods can do anything, and in
* particular if we ghostify an object with a __del__ method, that method
* can load the object again, putting it back into the MRU part of the
* ring. Waiting to find ring_home again can thus cause an infinite
* loop (Collector #1208). So before_original_home records the MRU
* position we start with, and we stop the scan when we reach that.
*/
insert_after(&before_original_home, self->ring_home.r_prev);
here = self->ring_home.r_next; /* least recently used object */
while (here != &before_original_home && self->non_ghost_count > target) {
assert(self->ring_lock);
assert(here != &self->ring_home);
/* At this point we know that the ring only contains nodes
from persistent objects, plus our own home node. We know
this because the ring lock is held. We can safely assume
the current ring node is a persistent object now we know it
is not the home */
object = OBJECT_FROM_RING(self, here);
if (object->state == cPersistent_UPTODATE_STATE) {
CPersistentRing placeholder;
PyObject *method;
PyObject *temp;
int error_occurred = 0;
/* deactivate it. This is the main memory saver. */
/* Add a placeholder, a dummy node in the ring. We need
to do this to mark our position in the ring. It is
possible that the PyObject_GetAttr() call below will
invoke a __getattr__() hook in Python. Also possible
that deactivation will lead to a __del__ method call.
So another thread might run, and mutate the ring as a side
effect of object accesses. There's no predicting then where
in the ring here->next will point after that. The
placeholder won't move as a side effect of calling Python
code.
*/
insert_after(&placeholder, here);
method = PyObject_GetAttr((PyObject *)object, py__p_deactivate);
if (method == NULL)
error_occurred = 1;
else {
temp = PyObject_CallObject(method, NULL);
Py_DECREF(method);
if (temp == NULL)
error_occurred = 1;
}
here = placeholder.r_next;
unlink_from_ring(&placeholder);
if (error_occurred)
goto Done;
}
else
here = here->r_next;
}
result = 0;
Done:
unlink_from_ring(&before_original_home);
return result;
}
static PyObject *
lockgc(ccobject *self, int target_size)
{
/* This is thread-safe because of the GIL, and there's nothing
* in between checking the ring_lock and acquiring it that calls back
* into Python.
*/
if (self->ring_lock) {
Py_INCREF(Py_None);
return Py_None;
}
self->ring_lock = 1;
if (scan_gc_items(self, target_size) < 0) {
self->ring_lock = 0;
return NULL;
}
self->ring_lock = 0;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
cc_incrgc(ccobject *self, PyObject *args)
{
int obsolete_arg = -999;
int starting_size = self->non_ghost_count;
int target_size = self->cache_size;
if (self->cache_drain_resistance >= 1) {
/* This cache will gradually drain down to a small size. Check
a (small) number of objects proportional to the current size */
int target_size_2 = (starting_size - 1
- starting_size / self->cache_drain_resistance);
if (target_size_2 < target_size)
target_size = target_size_2;
}
if (!PyArg_ParseTuple(args, "|i:incrgc", &obsolete_arg))
return NULL;
if (obsolete_arg != -999
&&
(PyErr_Warn(PyExc_DeprecationWarning,
"No argument expected")
< 0))
return NULL;
return lockgc(self, target_size);
}
static PyObject *
cc_full_sweep(ccobject *self, PyObject *args)
{
int dt = -999;
/* TODO: This should be deprecated; */
if (!PyArg_ParseTuple(args, "|i:full_sweep", &dt))
return NULL;
if (dt == -999)
return lockgc(self, 0);
else
return cc_incrgc(self, args);
}
static PyObject *
cc_minimize(ccobject *self, PyObject *args)
{
int ignored = -999;
if (!PyArg_ParseTuple(args, "|i:minimize", &ignored))
return NULL;
if (ignored != -999
&&
(PyErr_Warn(PyExc_DeprecationWarning,
"No argument expected")
< 0))
return NULL;
return lockgc(self, 0);
}
static int
_invalidate(ccobject *self, PyObject *key)
{
static PyObject *_p_invalidate = NULL;
PyObject *meth, *v;
v = PyDict_GetItem(self->data, key);
if (v == NULL)
return 0;
if (_p_invalidate == NULL)
{
_p_invalidate = PyString_InternFromString("_p_invalidate");
if (_p_invalidate == NULL)
{
/* It doesn't make any sense to ignore this error, but
the caller ignores all errors.
TODO: and why does it do that? This should be fixed
*/
return -1;
}
}
if (v->ob_refcnt <= 1 && PyType_Check(v)) {
/* This looks wrong, but it isn't. We use strong references to types
because they don't have the ring members.
The result is that we *never* remove classes unless
they are modified. We can fix this by using wekrefs uniformly.
*/
self->klass_count--;
return PyDict_DelItem(self->data, key);
}
meth = PyObject_GetAttr(v, _p_invalidate);
if (meth == NULL)
return -1;
v = PyObject_CallObject(meth, NULL);
Py_DECREF(meth);
return v == NULL ? -1 : 0;
}
static PyObject *
cc_invalidate(ccobject *self, PyObject *inv)
{
PyObject *key, *v;
int i = 0;
if (PyDict_Check(inv))
{
while (PyDict_Next(inv, &i, &key, &v))
{
if (_invalidate(self, key) < 0)
return NULL;
}
PyDict_Clear(inv);
}
else {
if (PyString_Check(inv))
{
if (_invalidate(self, inv) < 0)
return NULL;
}
else {
int l, r;
l = PyObject_Length(inv);
if (l < 0)
return NULL;
for (i=l; --i >= 0; ) {
key = PySequence_GetItem(inv, i);
if (!key)
return NULL;
r = _invalidate(self, key);
Py_DECREF(key);
if (r < 0)
return NULL;
}
/* Dubious: modifying the input may be an unexpected side effect. */
PySequence_DelSlice(inv, 0, l);
}
}
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
cc_get(ccobject *self, PyObject *args)
{
PyObject *r, *key, *d = NULL;
if (!PyArg_ParseTuple(args, "O|O:get", &key, &d))
return NULL;
r = PyDict_GetItem(self->data, key);
if (!r) {
if (d)
r = d;
else
r = Py_None;
}
Py_INCREF(r);
return r;
}
static PyObject *
cc_items(ccobject *self)
{
return PyObject_CallMethod(self->data, "items", "");
}
static PyObject *
cc_klass_items(ccobject *self)
{
PyObject *l,*k,*v;
int p = 0;
l = PyList_New(0);
if (l == NULL)
return NULL;
while (PyDict_Next(self->data, &p, &k, &v)) {
if(PyType_Check(v)) {
v = Py_BuildValue("OO", k, v);
if (v == NULL) {
Py_DECREF(l);
return NULL;
}
if (PyList_Append(l, v) < 0) {
Py_DECREF(v);
Py_DECREF(l);
return NULL;
}
Py_DECREF(v);
}
}
return l;
}
static PyObject *
cc_debug_info(ccobject *self)
{
PyObject *l,*k,*v;
int p = 0;
l = PyList_New(0);
if (l == NULL)
return NULL;
while (PyDict_Next(self->data, &p, &k, &v))
{
if (v->ob_refcnt <= 0)
v = Py_BuildValue("Oi", k, v->ob_refcnt);
else if (! PyType_Check(v) &&
(v->ob_type->tp_basicsize >= sizeof(cPersistentObject))
)
v = Py_BuildValue("Oisi",
k, v->ob_refcnt, v->ob_type->tp_name,
((cPersistentObject*)v)->state);
else
v = Py_BuildValue("Ois", k, v->ob_refcnt, v->ob_type->tp_name);
if (v == NULL)
goto err;
if (PyList_Append(l, v) < 0)
goto err;
}
return l;
err:
Py_DECREF(l);
return NULL;
}
static PyObject *
cc_lru_items(ccobject *self)
{
PyObject *l;
CPersistentRing *here;
if (self->ring_lock) {
/* When the ring lock is held, we have no way of know which
ring nodes belong to persistent objects, and which a
placeholders. */
PyErr_SetString(PyExc_ValueError,
".lru_items() is unavailable during garbage collection");
return NULL;
}
l = PyList_New(0);
if (l == NULL)
return NULL;
here = self->ring_home.r_next;
while (here != &self->ring_home) {
PyObject *v;
cPersistentObject *object = OBJECT_FROM_RING(self, here);
if (object == NULL) {
Py_DECREF(l);
return NULL;
}
v = Py_BuildValue("OO", object->oid, object);
if (v == NULL) {
Py_DECREF(l);
return NULL;
}
if (PyList_Append(l, v) < 0) {
Py_DECREF(v);
Py_DECREF(l);
return NULL;
}
Py_DECREF(v);
here = here->r_next;
}
return l;
}
static void
cc_oid_unreferenced(ccobject *self, PyObject *oid)
{
/* This is called by the persistent object deallocation function
when the reference count on a persistent object reaches
zero. We need to fix up our dictionary; its reference is now
dangling because we stole its reference count. Be careful to
not release the global interpreter lock until this is
complete. */
PyObject *v;
/* If the cache has been cleared by GC, data will be NULL. */
if (!self->data)
return;
v = PyDict_GetItem(self->data, oid);
assert(v);
assert(v->ob_refcnt == 0);
/* Need to be very hairy here because a dictionary is about
to decref an already deleted object.
*/
#ifdef Py_TRACE_REFS
/* This is called from the deallocation function after the
interpreter has untracked the reference. Track it again.
*/
_Py_NewReference(v);
/* Don't increment total refcount as a result of the
shenanigans played in this function. The _Py_NewReference()
call above creates artificial references to v.
*/
_Py_RefTotal--;
assert(v->ob_type);
#else
Py_INCREF(v);
#endif
assert(v->ob_refcnt == 1);
/* Incremement the refcount again, because delitem is going to
DECREF it. If it's refcount reached zero again, we'd call back to
the dealloc function that called us.
*/
Py_INCREF(v);
/* TODO: Should we call _Py_ForgetReference() on error exit? */
if (PyDict_DelItem(self->data, oid) < 0)
return;
Py_DECREF((ccobject *)((cPersistentObject *)v)->cache);
((cPersistentObject *)v)->cache = NULL;
assert(v->ob_refcnt == 1);
/* Undo the temporary resurrection.
Don't DECREF the object, because this function is called from
the object's dealloc function. If the refcnt reaches zero, it
will all be invoked recursively.
*/
_Py_ForgetReference(v);
}
static PyObject *
cc_ringlen(ccobject *self)
{
CPersistentRing *here;
int c = 0;
for (here = self->ring_home.r_next; here != &self->ring_home;
here = here->r_next)
c++;
return PyInt_FromLong(c);
}
static struct PyMethodDef cc_methods[] = {
{"items", (PyCFunction)cc_items, METH_NOARGS,
"Return list of oid, object pairs for all items in cache."},
{"lru_items", (PyCFunction)cc_lru_items, METH_NOARGS,
"List (oid, object) pairs from the lru list, as 2-tuples."},
{"klass_items", (PyCFunction)cc_klass_items, METH_NOARGS,
"List (oid, object) pairs of cached persistent classes."},
{"full_sweep", (PyCFunction)cc_full_sweep, METH_VARARGS,
"full_sweep() -- Perform a full sweep of the cache."},
{"minimize", (PyCFunction)cc_minimize, METH_VARARGS,
"minimize([ignored]) -- Remove as many objects as possible\n\n"
"Ghostify all objects that are not modified. Takes an optional\n"
"argument, but ignores it."},
{"incrgc", (PyCFunction)cc_incrgc, METH_VARARGS,
"incrgc() -- Perform incremental garbage collection\n\n"
"This method had been depricated!"
"Some other implementations support an optional parameter 'n' which\n"
"indicates a repetition count; this value is ignored."},
{"invalidate", (PyCFunction)cc_invalidate, METH_O,
"invalidate(oids) -- invalidate one, many, or all ids"},
{"get", (PyCFunction)cc_get, METH_VARARGS,
"get(key [, default]) -- get an item, or a default"},
{"ringlen", (PyCFunction)cc_ringlen, METH_NOARGS,
"ringlen() -- Returns number of non-ghost items in cache."},
{"debug_info", (PyCFunction)cc_debug_info, METH_NOARGS,
"debug_info() -- Returns debugging data about objects in the cache."},
{NULL, NULL} /* sentinel */
};
static int
cc_init(ccobject *self, PyObject *args, PyObject *kwds)
{
int cache_size = 100;
PyObject *jar;
if (!PyArg_ParseTuple(args, "O|i", &jar, &cache_size))
return -1;
self->jar = NULL;
self->data = PyDict_New();
if (self->data == NULL) {
Py_DECREF(self);
return -1;
}
/* Untrack the dict mapping oids to objects.
The dict contains uncounted references to ghost objects, so it
isn't safe for GC to visit it. If GC finds an object with more
referents that refcounts, it will die with an assertion failure.
When the cache participates in GC, it will need to traverse the
objects in the doubly-linked list, which will account for all the
non-ghost objects.
*/
PyObject_GC_UnTrack((void *)self->data);
self->jar = jar;
Py_INCREF(jar);
self->cache_size = cache_size;
self->non_ghost_count = 0;
self->klass_count = 0;
self->cache_drain_resistance = 0;
self->ring_lock = 0;
self->ring_home.r_next = &self->ring_home;
self->ring_home.r_prev = &self->ring_home;
return 0;
}
static void
cc_dealloc(ccobject *self)
{
Py_XDECREF(self->data);
Py_XDECREF(self->jar);
PyObject_GC_Del(self);
}
static int
cc_clear(ccobject *self)
{
int pos = 0;
PyObject *k, *v;
/* Clearing the cache is delicate.
A non-ghost object will show up in the ring and in the dict. If
we deallocating the dict before clearing the ring, the GC will
decref each object in the dict. Since the dict references are
uncounted, this will lead to objects having negative refcounts.
Freeing the non-ghost objects should eliminate many objects from
the cache, but there may still be ghost objects left. It's
not safe to decref the dict until it's empty, so we need to manually
clear those out of the dict, too. We accomplish that by replacing
all the ghost objects with None.
*/
/* We don't need to lock the ring, because the cache is unreachable.
It should be impossible for anyone to be modifying the cache.
*/
assert(! self->ring_lock);
while (self->ring_home.r_next != &self->ring_home) {
CPersistentRing *here = self->ring_home.r_next;
cPersistentObject *o = OBJECT_FROM_RING(self, here);
if (o->cache) {
Py_INCREF(o); /* account for uncounted reference */
if (PyDict_DelItem(self->data, o->oid) < 0)
return -1;
}
o->cache = NULL;
Py_DECREF(self);
self->ring_home.r_next = here->r_next;
o->ring.r_prev = NULL;
o->ring.r_next = NULL;
Py_DECREF(o);
here = here->r_next;
}
Py_XDECREF(self->jar);
while (PyDict_Next(self->data, &pos, &k, &v)) {
Py_INCREF(v);
if (PyDict_SetItem(self->data, k, Py_None) < 0)
return -1;
}
Py_XDECREF(self->data);
self->data = NULL;
self->jar = NULL;
return 0;
}
static int
cc_traverse(ccobject *self, visitproc visit, void *arg)
{
int err;
CPersistentRing *here;
/* If we're in the midst of cleaning up old objects, the ring contains
* assorted junk we must not pass on to the visit() callback. This
* should be rare (our cleanup code would need to have called back
* into Python, which in turn triggered Python's gc). When it happens,
* simply don't chase any pointers. The cache will appear to be a
* source of external references then, and at worst we miss cleaning
* up a dead cycle until the next time Python's gc runs.
*/
if (self->ring_lock)
return 0;
#define VISIT(SLOT) \
if (SLOT) { \
err = visit((PyObject *)(SLOT), arg); \
if (err) \
return err; \
}
VISIT(self->jar);
here = self->ring_home.r_next;
/* It is possible that an object is traversed after it is cleared.
In that case, there is no ring.
*/
if (!here)
return 0;
while (here != &self->ring_home) {
cPersistentObject *o = OBJECT_FROM_RING(self, here);
VISIT(o);
here = here->r_next;
}
#undef VISIT
return 0;
}
static int
cc_length(ccobject *self)
{
return PyObject_Length(self->data);
}
static PyObject *
cc_subscript(ccobject *self, PyObject *key)
{
PyObject *r;
r = PyDict_GetItem(self->data, key);
if (r == NULL) {
PyErr_SetObject(PyExc_KeyError, key);
return NULL;
}
Py_INCREF(r);
return r;
}
static int
cc_add_item(ccobject *self, PyObject *key, PyObject *v)
{
int result;
PyObject *oid, *object_again, *jar;
cPersistentObject *p;
/* Sanity check the value given to make sure it is allowed in the cache */
if (PyType_Check(v)) {
/* Its a persistent class, such as a ZClass. Thats ok. */
}
else if (v->ob_type->tp_basicsize < sizeof(cPersistentObject)) {
/* If it's not an instance of a persistent class, (ie Python
classes that derive from persistent.Persistent, BTrees,
etc), report an error.
TODO: checking sizeof() seems a poor test.
*/
PyErr_SetString(PyExc_TypeError,
"Cache values must be persistent objects.");
return -1;
}
/* Can't access v->oid directly because the object might be a
* persistent class.
*/
oid = PyObject_GetAttr(v, py__p_oid);
if (oid == NULL)
return -1;
if (! PyString_Check(oid)) {
PyErr_Format(PyExc_TypeError,
"Cached object oid must be a string, not a %s",
oid->ob_type->tp_name);
return -1;
}
/* we know they are both strings.
* now check if they are the same string.
*/
result = PyObject_Compare(key, oid);
if (PyErr_Occurred()) {
Py_DECREF(oid);
return -1;
}
Py_DECREF(oid);
if (result) {
PyErr_SetString(PyExc_ValueError, "Cache key does not match oid");
return -1;
}
/* useful sanity check, but not strictly an invariant of this class */
jar = PyObject_GetAttr(v, py__p_jar);
if (jar == NULL)
return -1;
if (jar==Py_None) {
Py_DECREF(jar);
PyErr_SetString(PyExc_ValueError,
"Cached object jar missing");
return -1;
}
Py_DECREF(jar);
object_again = PyDict_GetItem(self->data, key);
if (object_again) {
if (object_again != v) {
PyErr_SetString(PyExc_ValueError,
"A different object already has the same oid");
return -1;
} else {
/* re-register under the same oid - no work needed */
return 0;
}
}
if (PyType_Check(v)) {
if (PyDict_SetItem(self->data, key, v) < 0)
return -1;
self->klass_count++;
return 0;
} else {
PerCache *cache = ((cPersistentObject *)v)->cache;
if (cache) {
if (cache != (PerCache *)self)
/* This object is already in a different cache. */
PyErr_SetString(PyExc_ValueError,
"Cache values may only be in one cache.");
return -1;
}
/* else:
This object is already one of ours, which is ok. It
would be very strange if someone was trying to register
the same object under a different key.
*/
}
if (PyDict_SetItem(self->data, key, v) < 0)
return -1;
/* the dict should have a borrowed reference */
Py_DECREF(v);
p = (cPersistentObject *)v;
Py_INCREF(self);
p->cache = (PerCache *)self;
if (p->state >= 0) {
/* insert this non-ghost object into the ring just
behind the home position. */
self->non_ghost_count++;
ring_add(&self->ring_home, &p->ring);
/* this list should have a new reference to the object */
Py_INCREF(v);
}
return 0;
}
static int
cc_del_item(ccobject *self, PyObject *key)
{
PyObject *v;
cPersistentObject *p;
/* unlink this item from the ring */
v = PyDict_GetItem(self->data, key);
if (v == NULL) {
PyErr_SetObject(PyExc_KeyError, key);
return -1;
}
if (PyType_Check(v)) {
self->klass_count--;
} else {
p = (cPersistentObject *)v;
if (p->state >= 0) {
self->non_ghost_count--;
ring_del(&p->ring);
/* The DelItem below will account for the reference
held by the list. */
} else {
/* This is a ghost object, so we haven't kept a reference
count on it. For it have stayed alive this long
someone else must be keeping a reference to
it. Therefore we need to temporarily give it back a
reference count before calling DelItem below */
Py_INCREF(v);
}
Py_DECREF((PyObject *)p->cache);
p->cache = NULL;
}
if (PyDict_DelItem(self->data, key) < 0) {
PyErr_SetString(PyExc_RuntimeError,
"unexpectedly couldn't remove key in cc_ass_sub");
return -1;
}
return 0;
}
static int
cc_ass_sub(ccobject *self, PyObject *key, PyObject *v)
{
if (!PyString_Check(key)) {
PyErr_Format(PyExc_TypeError,
"cPickleCache key must be a string, not a %s",
key->ob_type->tp_name);
return -1;
}
if (v)
return cc_add_item(self, key, v);
else
return cc_del_item(self, key);
}
static PyMappingMethods cc_as_mapping = {
(inquiry)cc_length, /*mp_length*/
(binaryfunc)cc_subscript, /*mp_subscript*/
(objobjargproc)cc_ass_sub, /*mp_ass_subscript*/
};
static PyObject *
cc_cache_data(ccobject *self, void *context)
{
return PyDict_Copy(self->data);
}
static PyGetSetDef cc_getsets[] = {
{"cache_data", (getter)cc_cache_data},
{NULL}
};
static PyMemberDef cc_members[] = {
{"cache_size", T_INT, offsetof(ccobject, cache_size)},
{"cache_drain_resistance", T_INT,
offsetof(ccobject, cache_drain_resistance)},
{"cache_non_ghost_count", T_INT, offsetof(ccobject, non_ghost_count), RO},
{"cache_klass_count", T_INT, offsetof(ccobject, klass_count), RO},
{NULL}
};
/* This module is compiled as a shared library. Some compilers don't
allow addresses of Python objects defined in other libraries to be
used in static initializers here. The DEFERRED_ADDRESS macro is
used to tag the slots where such addresses appear; the module init
function must fill in the tagged slots at runtime. The argument is
for documentation -- the macro ignores it.
*/
#define DEFERRED_ADDRESS(ADDR) 0
static PyTypeObject Cctype = {
PyObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type))
0, /* ob_size */
"persistent.PickleCache", /* tp_name */
sizeof(ccobject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)cc_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
&cc_as_mapping, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
/* tp_flags */
0, /* tp_doc */
(traverseproc)cc_traverse, /* tp_traverse */
(inquiry)cc_clear, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
cc_methods, /* tp_methods */
cc_members, /* tp_members */
cc_getsets, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)cc_init, /* tp_init */
};
void
initcPickleCache(void)
{
PyObject *m;
Cctype.ob_type = &PyType_Type;
Cctype.tp_new = &PyType_GenericNew;
if (PyType_Ready(&Cctype) < 0) {
return;
}
m = Py_InitModule3("cPickleCache", NULL, cPickleCache_doc_string);
capi = (cPersistenceCAPIstruct *)PyCObject_Import(
"persistent.cPersistence", "CAPI");
if (!capi)
return;
capi->percachedel = (percachedelfunc)cc_oid_unreferenced;
py__p_changed = PyString_InternFromString("_p_changed");
if (!py__p_changed)
return;
py__p_deactivate = PyString_InternFromString("_p_deactivate");
if (!py__p_deactivate)
return;
py__p_jar = PyString_InternFromString("_p_jar");
if (!py__p_jar)
return;
py__p_oid = PyString_InternFromString("_p_oid");
if (!py__p_oid)
return;
if (PyModule_AddStringConstant(m, "cache_variant", "stiff/c") < 0)
return;
/* This leaks a reference to Cctype, but it doesn't matter. */
if (PyModule_AddObject(m, "PickleCache", (PyObject *)&Cctype) < 0)
return;
}
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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 container type
$Id: dict.py 40330 2005-11-22 20:55:47Z tlotze $
"""
import persistent
from UserDict import IterableUserDict
__metaclass__ = type
class PersistentDict(persistent.Persistent, IterableUserDict):
"""A persistent wrapper for mapping objects.
This class allows wrapping of mapping objects so that object
changes are registered. As a side effect, mapping objects may be
subclassed.
"""
# IterableUserDict provides all of the mapping behavior. The
# PersistentDict class is responsible marking the persistent
# state as changed when a method actually changes the state. At
# the mapping API evolves, we may need to add more methods here.
__super_delitem = IterableUserDict.__delitem__
__super_setitem = IterableUserDict.__setitem__
__super_clear = IterableUserDict.clear
__super_update = IterableUserDict.update
__super_setdefault = IterableUserDict.setdefault
__super_pop = IterableUserDict.pop
__super_popitem = IterableUserDict.popitem
__super_p_init = persistent.Persistent.__init__
__super_init = IterableUserDict.__init__
def __init__(self, dict=None):
self.__super_init(dict)
self.__super_p_init()
def __delitem__(self, key):
self.__super_delitem(key)
self._p_changed = True
def __setitem__(self, key, v):
self.__super_setitem(key, v)
self._p_changed = True
def clear(self):
self.__super_clear()
self._p_changed = True
def update(self, b):
self.__super_update(b)
self._p_changed = True
def setdefault(self, key, failobj=None):
# We could inline all of UserDict's implementation into the
# method here, but I'd rather not depend at all on the
# implementation in UserDict (simple as it is).
if not self.has_key(key):
self._p_changed = True
return self.__super_setdefault(key, failobj)
def pop(self, key, *args):
self._p_changed = True
return self.__super_pop(key, *args)
def popitem(self):
self._p_changed = True
return self.__super_popitem()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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
$Id: interfaces.py 40664 2005-12-09 16:19:03Z tim_one $
"""
from zope.interface import Interface
from zope.interface import Attribute
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.
"""
_p_jar = Attribute(
"""The data manager for the object.
The data manager implements the IPersistentDataManager interface.
If there is no data manager, then this is None.
""")
_p_oid = Attribute(
"""The object id.
It is up to the data manager to assign this.
The special value None is reserved to indicate that an object
id has not been assigned. Non-None object ids must be non-empty
strings. The 8-byte string '\0'*8 (8 NUL bytes) is reserved to
identify the database root object.
""")
_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).
""")
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 _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.
"""
class IPersistentNoReadConflicts(IPersistent):
def _p_independent():
"""Hook for subclasses to prevent read conflict errors.
A specific persistent object type can define this method and
have it return true if the data manager should ignore read
conflicts for this object.
"""
# 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.
"""
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().
## """
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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: list.py 25186 2004-06-02 15:07:33Z jim $"""
import persistent
from UserList import UserList
class PersistentList(UserList, persistent.Persistent):
__super_setitem = UserList.__setitem__
__super_delitem = UserList.__delitem__
__super_setslice = UserList.__setslice__
__super_delslice = UserList.__delslice__
__super_iadd = UserList.__iadd__
__super_imul = UserList.__imul__
__super_append = UserList.append
__super_insert = UserList.insert
__super_pop = UserList.pop
__super_remove = UserList.remove
__super_reverse = UserList.reverse
__super_sort = UserList.sort
__super_extend = UserList.extend
def __setitem__(self, i, item):
self.__super_setitem(i, item)
self._p_changed = 1
def __delitem__(self, i):
self.__super_delitem(i)
self._p_changed = 1
def __setslice__(self, i, j, other):
self.__super_setslice(i, j, other)
self._p_changed = 1
def __delslice__(self, i, j):
self.__super_delslice(i, j)
self._p_changed = 1
def __iadd__(self, other):
L = self.__super_iadd(other)
self._p_changed = 1
return L
def __imul__(self, n):
L = self.__super_imul(n)
self._p_changed = 1
return L
def append(self, item):
self.__super_append(item)
self._p_changed = 1
def insert(self, i, item):
self.__super_insert(i, item)
self._p_changed = 1
def pop(self, i=-1):
rtn = self.__super_pop(i)
self._p_changed = 1
return rtn
def remove(self, item):
self.__super_remove(item)
self._p_changed = 1
def reverse(self):
self.__super_reverse()
self._p_changed = 1
def sort(self, *args):
self.__super_sort(*args)
self._p_changed = 1
def extend(self, other):
self.__super_extend(other)
self._p_changed = 1
# This works around a bug in Python 2.1.x (up to 2.1.2 at least) where the
# __cmp__ bogusly raises a RuntimeError, and because this is an extension
# class, none of the rich comparison stuff works anyway.
def __cmp__(self, other):
return cmp(self.data, self._UserList__cast(other))
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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: mapping.py 66125 2006-03-22 15:43:22Z tseaver $"""
import persistent
from UserDict import UserDict
class PersistentMapping(UserDict, persistent.Persistent):
"""A persistent wrapper for mapping objects.
This class allows wrapping of mapping objects so that object
changes are registered. As a side effect, mapping objects may be
subclassed.
A subclass of PersistentMapping or any code that adds new
attributes should not create an attribute named _container. This
is reserved for backwards compatibility reasons.
"""
# UserDict provides all of the mapping behavior. The
# PersistentMapping class is responsible marking the persistent
# state as changed when a method actually changes the state. At
# the mapping API evolves, we may need to add more methods here.
__super_delitem = UserDict.__delitem__
__super_setitem = UserDict.__setitem__
__super_clear = UserDict.clear
__super_update = UserDict.update
__super_setdefault = UserDict.setdefault
__super_pop = UserDict.pop
__super_popitem = UserDict.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 self.has_key(key):
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()
# __iter__ was added in ZODB 3.4.2, but should have been added long
# before. We could inherit from Python's IterableUserDict instead
# (which just adds __iter__ to Python's UserDict), but that class isn't
# documented, and it would add another level of lookup for all the
# other methods.
def __iter__(self):
return iter(self.data)
# If the internal representation of PersistentMapping changes,
# it causes compatibility problems for pickles generated by
# different versions of the code. Compatibility works in both
# directions, because an application may want to share a database
# between applications using different versions of the code.
# Effectively, the original rep is part of the "API." To provide
# full compatibility, the getstate and setstate must read and
# write objects using the old rep.
# As a result, the PersistentMapping must save and restore the
# actual internal dictionary using the name _container.
def __getstate__(self):
state = dict([x for x in self.__dict__.items()
if not x[0].startswith('_v_')])
state['_container'] = state['data']
del state['data']
return state
def __setstate__(self, state):
if state.has_key('_container'):
self.data = state['_container']
del state['_container']
elif not state.has_key('data'):
self.data = {}
self.__dict__.update(state)
/*****************************************************************************
Copyright (c) 2003 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.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: ring.c 25186 2004-06-02 15:07:33Z jim $\n"
/* Support routines for the doubly-linked list of cached objects.
The cache stores a doubly-linked list of persistent objects, with
space for the pointers allocated in the objects themselves. The cache
stores the distinguished head of the list, which is not a valid
persistent object.
The next pointers traverse the ring in order starting with the least
recently used object. The prev pointers traverse the ring in order
starting with the most recently used object.
*/
#include "Python.h"
#include "ring.h"
void
ring_add(CPersistentRing *ring, CPersistentRing *elt)
{
assert(!elt->r_next);
elt->r_next = ring;
elt->r_prev = ring->r_prev;
ring->r_prev->r_next = elt;
ring->r_prev = elt;
}
void
ring_del(CPersistentRing *elt)
{
elt->r_next->r_prev = elt->r_prev;
elt->r_prev->r_next = elt->r_next;
elt->r_next = NULL;
elt->r_prev = NULL;
}
void
ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt)
{
elt->r_prev->r_next = elt->r_next;
elt->r_next->r_prev = elt->r_prev;
elt->r_next = ring;
elt->r_prev = ring->r_prev;
ring->r_prev->r_next = elt;
ring->r_prev = elt;
}
/*****************************************************************************
Copyright (c) 2003 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public License,
Version 2.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);
Tests for `persistent.Persistent`
=================================
This document is an extended doc test that covers the basics of the
Persistent base class. The test expects a class named `P` to be
provided in its globals. The `P` class implements the `Persistent`
interface.
Test framework
--------------
The class `P` needs to behave like `ExampleP`. (Note that the code below
is *not* part of the tests.)
::
class ExampleP(Persistent):
def __init__(self):
self.x = 0
def inc(self):
self.x += 1
The tests use stub data managers. A data manager is responsible for
loading and storing the state of a persistent object. It's stored in
the ``_p_jar`` attribute of a persistent object.
>>> class DM:
... def __init__(self):
... self.called = 0
... def register(self, ob):
... self.called += 1
... def setstate(self, ob):
... ob.__setstate__({'x': 42})
>>> class BrokenDM(DM):
... def register(self,ob):
... self.called += 1
... raise NotImplementedError
... def setstate(self,ob):
... raise NotImplementedError
>>> from persistent import Persistent
Test Persistent without Data Manager
------------------------------------
First do some simple tests of a Persistent instance that does not have
a data manager (``_p_jar``).
>>> p = P()
>>> p.x
0
>>> p._p_changed
False
>>> p._p_state
0
>>> p._p_jar
>>> p._p_oid
Verify that modifications have no effect on ``_p_state`` of ``_p_changed``.
>>> p.inc()
>>> p.inc()
>>> p.x
2
>>> p._p_changed
False
>>> p._p_state
0
Try all sorts of different ways to change the object's state.
>>> p._p_deactivate()
>>> p._p_state
0
>>> p._p_changed = True
>>> p._p_state
0
>>> del p._p_changed
>>> p._p_changed
False
>>> p._p_state
0
>>> p.x
2
Test Persistent with Data Manager
---------------------------------
Next try some tests of an object with a data manager. The `DM` class is
a simple testing stub.
>>> p = P()
>>> dm = DM()
>>> p._p_oid = "00000012"
>>> p._p_jar = dm
>>> p._p_changed
0
>>> dm.called
0
Modifying the object marks it as changed and registers it with the data
manager. Subsequent modifications don't have additional side-effects.
>>> p.inc()
>>> p._p_changed
1
>>> dm.called
1
>>> p.inc()
>>> p._p_changed
1
>>> dm.called
1
It's not possible to deactivate a modified object.
>>> p._p_deactivate()
>>> p._p_changed
1
It is possible to invalidate it. That's the key difference between
deactivation and invalidation.
>>> p._p_invalidate()
>>> p._p_state
-1
Now that the object is a ghost, any attempt to modify it will require that it
be unghosted first. The test data manager has the odd property that it sets
the object's ``x`` attribute to ``42`` when it is unghosted.
>>> p.inc()
>>> p.x
43
>>> dm.called
2
You can manually reset the changed field to ``False``, although it's not clear
why you would want to do that. The object changes to the ``UPTODATE`` state
but retains its modifications.
>>> p._p_changed = False
>>> p._p_state
0
>>> p._p_changed
False
>>> p.x
43
>>> p.inc()
>>> p._p_changed
True
>>> dm.called
3
``__getstate__()`` and ``__setstate__()``
-----------------------------------------
The next several tests cover the ``__getstate__()`` and ``__setstate__()``
implementations.
>>> p = P()
>>> state = p.__getstate__()
>>> isinstance(state, dict)
True
>>> state['x']
0
>>> p._p_state
0
Calling setstate always leaves the object in the uptodate state?
(I'm not entirely clear on this one.)
>>> p.__setstate__({'x': 5})
>>> p._p_state
0
Assigning to a volatile attribute has no effect on the object state.
>>> p._v_foo = 2
>>> p.__getstate__()
{'x': 5}
>>> p._p_state
0
The ``_p_serial`` attribute is not affected by calling setstate.
>>> p._p_serial = "00000012"
>>> p.__setstate__(p.__getstate__())
>>> p._p_serial
'00000012'
Change Ghost test
-----------------
If an object is a ghost and its ``_p_changed`` is set to ``True`` (any true
value), it should activate (unghostify) the object. This behavior is new in
ZODB 3.6; before then, an attempt to do ``ghost._p_changed = True`` was
ignored.
>>> p = P()
>>> p._p_jar = DM()
>>> p._p_oid = 1
>>> p._p_deactivate()
>>> p._p_changed # None
>>> p._p_state # ghost state
-1
>>> p._p_changed = True
>>> p._p_changed
1
>>> p._p_state # changed state
1
>>> p.x
42
Activate, deactivate, and invalidate
------------------------------------
Some of these tests are redundant, but are included to make sure there
are explicit and simple tests of ``_p_activate()``, ``_p_deactivate()``, and
``_p_invalidate()``.
>>> p = P()
>>> p._p_oid = 1
>>> p._p_jar = DM()
>>> p._p_deactivate()
>>> p._p_state
-1
>>> p._p_activate()
>>> p._p_state
0
>>> p.x
42
>>> p.inc()
>>> p.x
43
>>> p._p_state
1
>>> p._p_invalidate()
>>> p._p_state
-1
>>> p.x
42
Test failures
-------------
The following tests cover various errors cases.
When an object is modified, it registers with its data manager. If that
registration fails, the exception is propagated and the object stays in the
up-to-date state. It shouldn't change to the modified state, because it won't
be saved when the transaction commits.
>>> p = P()
>>> p._p_oid = 1
>>> p._p_jar = BrokenDM()
>>> p._p_state
0
>>> p._p_jar.called
0
>>> p._p_changed = 1
Traceback (most recent call last):
...
NotImplementedError
>>> p._p_jar.called
1
>>> p._p_state
0
Make sure that exceptions that occur inside the data manager's ``setstate()``
method propagate out to the caller.
>>> p = P()
>>> p._p_oid = 1
>>> p._p_jar = BrokenDM()
>>> p._p_deactivate()
>>> p._p_state
-1
>>> p._p_activate()
Traceback (most recent call last):
...
NotImplementedError
>>> p._p_state
-1
Special test to cover layout of ``__dict__``
--------------------------------------------
We once had a bug in the `Persistent` class that calculated an incorrect
offset for the ``__dict__`` attribute. It assigned ``__dict__`` and
``_p_jar`` to the same location in memory. This is a simple test to make sure
they have different locations.
>>> p = P()
>>> p.inc()
>>> p.inc()
>>> 'x' in p.__dict__
True
>>> p._p_jar
Inheritance and metaclasses
---------------------------
Simple tests to make sure it's possible to inherit from the `Persistent` base
class multiple times. There used to be metaclasses involved in `Persistent`
that probably made this a more interesting test.
>>> class A(Persistent):
... pass
>>> class B(Persistent):
... pass
>>> class C(A, B):
... pass
>>> class D(object):
... pass
>>> class E(D, B):
... pass
>>> a = A()
>>> b = B()
>>> c = C()
>>> d = D()
>>> e = E()
Also make sure that it's possible to define `Persistent` classes that have a
custom metaclass.
>>> class alternateMeta(type):
... type
>>> class alternate(object):
... __metaclass__ = alternateMeta
>>> class mixedMeta(alternateMeta, type):
... pass
>>> class mixed(alternate, Persistent):
... pass
>>> class mixed(Persistent, alternate):
... pass
Basic type structure
--------------------
>>> Persistent.__dictoffset__
0
>>> Persistent.__weakrefoffset__
0
>>> Persistent.__basicsize__ > object.__basicsize__
True
>>> P.__dictoffset__ > 0
True
>>> P.__weakrefoffset__ > 0
True
>>> P.__dictoffset__ < P.__weakrefoffset__
True
>>> P.__basicsize__ > Persistent.__basicsize__
True
Slots
-----
These are some simple tests of classes that have an ``__slots__``
attribute. Some of the classes should have slots, others shouldn't.
>>> class noDict(object):
... __slots__ = ['foo']
>>> class p_noDict(Persistent):
... __slots__ = ['foo']
>>> class p_shouldHaveDict(p_noDict):
... pass
>>> p_noDict.__dictoffset__
0
>>> x = p_noDict()
>>> x.foo = 1
>>> x.foo
1
>>> x.bar = 1
Traceback (most recent call last):
...
AttributeError: 'p_noDict' object has no attribute 'bar'
>>> x._v_bar = 1
Traceback (most recent call last):
...
AttributeError: 'p_noDict' object has no attribute '_v_bar'
>>> x.__dict__
Traceback (most recent call last):
...
AttributeError: 'p_noDict' object has no attribute '__dict__'
The various _p_ attributes are unaffected by slots.
>>> p._p_oid
>>> p._p_jar
>>> p._p_state
0
If the most-derived class does not specify
>>> p_shouldHaveDict.__dictoffset__ > 0
True
>>> x = p_shouldHaveDict()
>>> isinstance(x.__dict__, dict)
True
Pickling
--------
There's actually a substantial effort involved in making subclasses of
`Persistent` work with plain-old pickle. The ZODB serialization layer never
calls pickle on an object; it pickles the object's class description and its
state as two separate pickles.
>>> import pickle
>>> p = P()
>>> p.inc()
>>> p2 = pickle.loads(pickle.dumps(p))
>>> p2.__class__ is P
True
>>> p2.x == p.x
True
We should also test that pickle works with custom getstate and setstate.
Perhaps even reduce. The problem is that pickling depends on finding the
class in a particular module, and classes defined here won't appear in any
module. We could require each user of the tests to define a base class, but
that might be tedious.
Interfaces
----------
Some versions of Zope and ZODB have the `zope.interfaces` package available.
If it is available, then persistent will be associated with several
interfaces. It's hard to write a doctest test that runs the tests only if
`zope.interface` is available, so this test looks a little unusual. One
problem is that the assert statements won't do anything if you run with `-O`.
>>> try:
... import zope.interface
... except ImportError:
... pass
... else:
... from persistent.interfaces import IPersistent
... assert IPersistent.implementedBy(Persistent)
... p = Persistent()
... assert IPersistent.providedBy(p)
... assert IPersistent.implementedBy(P)
... p = P()
... assert IPersistent.providedBy(p)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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 persistent import Persistent
from persistent.interfaces import IPersistent
# Confusing: ZODB doesn't use this file. It appears to be used only
# by Zope3, where it's imported by zope/app/schema/tests/test_wrapper.py.
try:
import zope.interface
except ImportError:
interfaces = False
else:
interfaces = True
class Test(unittest.TestCase):
klass = None # override in subclass
def testSaved(self):
p = self.klass()
p._p_oid = '\0\0\0\0\0\0hi'
dm = DM()
p._p_jar = dm
self.assertEqual(p._p_changed, 0)
self.assertEqual(dm.called, 0)
p.inc()
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 1)
p.inc()
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 1)
p._p_deactivate()
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 1)
p._p_deactivate()
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 1)
del p._p_changed
# deal with current cPersistence implementation
if p._p_changed != 3:
self.assertEqual(p._p_changed, None)
self.assertEqual(dm.called, 1)
p.inc()
self.assertEqual(p.x, 43)
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 2)
p._p_changed = 0
self.assertEqual(p._p_changed, 0)
self.assertEqual(dm.called, 2)
self.assertEqual(p.x, 43)
p.inc()
self.assertEqual(p._p_changed, 1)
self.assertEqual(dm.called, 3)
def testUnsaved(self):
p = self.klass()
self.assertEqual(p.x, 0)
self.assertEqual(p._p_changed, 0)
self.assertEqual(p._p_jar, None)
self.assertEqual(p._p_oid, None)
p.inc()
p.inc()
self.assertEqual(p.x, 2)
self.assertEqual(p._p_changed, 0)
p._p_deactivate()
self.assertEqual(p._p_changed, 0)
p._p_changed = 1
self.assertEqual(p._p_changed, 0)
p._p_deactivate()
self.assertEqual(p._p_changed, 0)
del p._p_changed
self.assertEqual(p._p_changed, 0)
if self.has_dict:
self.failUnless(p.__dict__)
self.assertEqual(p.x, 2)
def testState(self):
p = self.klass()
self.assertEqual(p.__getstate__(), {'x': 0})
self.assertEqual(p._p_changed, 0)
p.__setstate__({'x':5})
self.assertEqual(p._p_changed, 0)
if self.has_dict:
p._v_foo = 2
self.assertEqual(p.__getstate__(), {'x': 5})
self.assertEqual(p._p_changed, 0)
def testSetStateSerial(self):
p = self.klass()
p._p_serial = '00000012'
p.__setstate__(p.__getstate__())
self.assertEqual(p._p_serial, '00000012')
def testDirectChanged(self):
p = self.klass()
p._p_oid = 1
dm = DM()
p._p_jar = dm
self.assertEqual(p._p_changed, 0)
self.assertEqual(dm.called, 0)
p._p_changed = 1
self.assertEqual(dm.called, 1)
def testGhostChanged(self):
# If an object is a ghost and its _p_changed is set to True (any
# true value), it should activate (unghostify) the object. This
# behavior is new in ZODB 3.6; before then, an attempt to do
# "ghost._p_changed = True" was ignored.
p = self.klass()
p._p_oid = 1
dm = DM()
p._p_jar = dm
p._p_deactivate()
self.assertEqual(p._p_changed, None)
p._p_changed = True
self.assertEqual(p._p_changed, 1)
def testRegistrationFailure(self):
p = self.klass()
p._p_oid = 1
dm = BrokenDM()
p._p_jar = dm
self.assertEqual(p._p_changed, 0)
self.assertEqual(dm.called, 0)
try:
p._p_changed = 1
except NotImplementedError:
pass
else:
raise AssertionError("Exception not propagated")
self.assertEqual(dm.called, 1)
self.assertEqual(p._p_changed, 0)
def testLoadFailure(self):
p = self.klass()
p._p_oid = 1
dm = BrokenDM()
p._p_jar = dm
p._p_deactivate() # make it a ghost
try:
p._p_activate()
except NotImplementedError:
pass
else:
raise AssertionError("Exception not propagated")
self.assertEqual(p._p_changed, None)
def testActivate(self):
p = self.klass()
dm = DM()
p._p_oid = 1
p._p_jar = dm
p._p_changed = 0
p._p_deactivate()
# Unsure: does this really test the activate method?
p._p_activate()
self.assertEqual(p._p_changed, 0)
self.assertEqual(p.x, 42)
def testDeactivate(self):
p = self.klass()
dm = DM()
p._p_oid = 1
p._p_deactivate() # this deactive has no effect
self.assertEqual(p._p_changed, 0)
p._p_jar = dm
p._p_changed = 0
p._p_deactivate()
self.assertEqual(p._p_changed, None)
p._p_activate()
self.assertEqual(p._p_changed, 0)
self.assertEqual(p.x, 42)
if interfaces:
def testInterface(self):
self.assert_(IPersistent.implementedBy(Persistent),
"%s does not implement IPersistent" % Persistent)
p = Persistent()
self.assert_(IPersistent.providedBy(p),
"%s does not implement IPersistent" % p)
self.assert_(IPersistent.implementedBy(P),
"%s does not implement IPersistent" % P)
p = self.klass()
self.assert_(IPersistent.providedBy(p),
"%s does not implement IPersistent" % p)
def testDataManagerAndAttributes(self):
# Test to cover an odd bug where the instance __dict__ was
# set at the same location as the data manager in the C type.
p = P()
p.inc()
p.inc()
self.assert_('x' in p.__dict__)
self.assert_(p._p_jar is None)
def testMultipleInheritance(self):
# make sure it is possible to inherit from two different
# subclasses of persistent.
class A(Persistent):
pass
class B(Persistent):
pass
class C(A, B):
pass
class D(object):
pass
class E(D, B):
pass
def testMultipleMeta(self):
# make sure it's possible to define persistent classes
# with a base whose metaclass is different
class alternateMeta(type):
pass
class alternate(object):
__metaclass__ = alternateMeta
class mixedMeta(alternateMeta, type):
pass
class mixed(alternate,Persistent):
__metaclass__ = mixedMeta
def testSlots(self):
# Verify that Persistent classes behave the same way
# as pure Python objects where '__slots__' and '__dict__'
# are concerned.
class noDict(object):
__slots__ = ['foo']
class shouldHaveDict(noDict):
pass
class p_noDict(Persistent):
__slots__ = ['foo']
class p_shouldHaveDict(p_noDict):
pass
self.assertEqual(noDict.__dictoffset__, 0)
self.assertEqual(p_noDict.__dictoffset__, 0)
self.assert_(shouldHaveDict.__dictoffset__ <> 0)
self.assert_(p_shouldHaveDict.__dictoffset__ <> 0)
def testBasicTypeStructure(self):
# test that a persistent class has a sane C type structure
# use P (defined below) as simplest example
self.assertEqual(Persistent.__dictoffset__, 0)
self.assertEqual(Persistent.__weakrefoffset__, 0)
self.assert_(Persistent.__basicsize__ > object.__basicsize__)
self.assert_(P.__dictoffset__)
self.assert_(P.__weakrefoffset__)
self.assert_(P.__dictoffset__ < P.__weakrefoffset__)
self.assert_(P.__basicsize__ > Persistent.__basicsize__)
# Unsure: Can anyone defend/explain the test below? The tests classes defined
# here don't define __call__, so this weird test will always pass, but to what
# end? If a klass is given that happens to define __call__, the test *may*
# mysteriously fail. Who cares?
## def testDeactivateErrors(self):
## p = self.klass()
## p._p_oid = '\0\0\0\0\0\0hi'
## dm = DM()
## p._p_jar = dm
## def typeerr(*args, **kwargs):
## self.assertRaises(TypeError, p, *args, **kwargs)
## typeerr(1)
## typeerr(1, 2)
## typeerr(spam=1)
## typeerr(spam=1, force=1)
## p._p_changed = True
## class Err(object):
## def __nonzero__(self):
## raise RuntimeError
## typeerr(force=Err())
class P(Persistent):
def __init__(self):
self.x = 0
def inc(self):
self.x += 1
class P2(P):
def __getstate__(self):
return 42
def __setstate__(self, v):
self.v = v
class B(Persistent):
__slots__ = ["x", "_p_serial"]
def __init__(self):
self.x = 0
def inc(self):
self.x += 1
def __getstate__(self):
return {'x': self.x}
def __setstate__(self, state):
self.x = state['x']
class DM:
def __init__(self):
self.called = 0
def register(self, ob):
self.called += 1
def setstate(self, ob):
ob.__setstate__({'x': 42})
class BrokenDM(DM):
def register(self,ob):
self.called += 1
raise NotImplementedError
def setstate(self,ob):
raise NotImplementedError
class PersistentTest(Test):
klass = P
has_dict = 1
def testPicklable(self):
import pickle
p = self.klass()
p.inc()
p2 = pickle.loads(pickle.dumps(p))
self.assertEqual(p2.__class__, self.klass)
# verify that the inc is reflected:
self.assertEqual(p2.x, p.x)
# This assertion would be invalid. Interfaces
# are compared by identity and copying doesn't
# preserve identity. We would get false negatives due
# to the differing identities of the original and copied
# PersistentInterface:
# self.assertEqual(p2.__dict__, p.__dict__)
def testPicklableWCustomState(self):
import pickle
p = P2()
p2 = pickle.loads(pickle.dumps(p))
self.assertEqual(p2.__class__, P2);
self.assertEqual(p2.__dict__, {'v': 42})
class BasePersistentTest(Test):
klass = B
has_dict = 0
#############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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 pickle
import time
import unittest
from persistent import Persistent, GHOST, UPTODATE, CHANGED, STICKY
from persistent.cPickleCache import PickleCache
from persistent.TimeStamp import TimeStamp
from ZODB.utils import p64
class Jar(object):
"""Testing stub for _p_jar attribute."""
def __init__(self):
self.cache = PickleCache(self)
self.oid = 1
self.registered = {}
def add(self, obj):
obj._p_oid = p64(self.oid)
self.oid += 1
obj._p_jar = self
self.cache[obj._p_oid] = obj
def close(self):
pass
# the following methods must be implemented to be a jar
def setklassstate(self):
# I don't know what this method does, but the pickle cache
# constructor calls it.
pass
def register(self, obj):
self.registered[obj] = 1
def setstate(self, obj):
# Trivial setstate() implementation that just re-initializes
# the object. This isn't what setstate() is supposed to do,
# but it suffices for the tests.
obj.__class__.__init__(obj)
class P(Persistent):
pass
class H1(Persistent):
def __init__(self):
self.n = 0
def __getattr__(self, attr):
self.n += 1
return self.n
class H2(Persistent):
def __init__(self):
self.n = 0
def __getattribute__(self, attr):
supergetattr = super(H2, self).__getattribute__
try:
return supergetattr(attr)
except AttributeError:
n = supergetattr("n")
self.n = n + 1
return n + 1
class PersistenceTest(unittest.TestCase):
def setUp(self):
self.jar = Jar()
def tearDown(self):
self.jar.close()
def testOidAndJarAttrs(self):
obj = P()
self.assertEqual(obj._p_oid, None)
obj._p_oid = 12
self.assertEqual(obj._p_oid, 12)
del obj._p_oid
self.jar.add(obj)
# Can't change oid of cache object.
def deloid():
del obj._p_oid
self.assertRaises(ValueError, deloid)
def setoid():
obj._p_oid = 12
self.assertRaises(ValueError, setoid)
def deloid():
del obj._p_jar
self.assertRaises(ValueError, deloid)
def setoid():
obj._p_jar = 12
self.assertRaises(ValueError, setoid)
def testChangedAndState(self):
obj = P()
self.jar.add(obj)
# The value returned for _p_changed can be one of:
# 0 -- it is not changed
# 1 -- it is changed
# None -- it is a ghost
obj.x = 1
self.assertEqual(obj._p_changed, 1)
self.assertEqual(obj._p_state, CHANGED)
self.assert_(obj in self.jar.registered)
obj._p_changed = 0
self.assertEqual(obj._p_changed, 0)
self.assertEqual(obj._p_state, UPTODATE)
self.jar.registered.clear()
obj._p_changed = 1
self.assertEqual(obj._p_changed, 1)
self.assertEqual(obj._p_state, CHANGED)
self.assert_(obj in self.jar.registered)
# setting obj._p_changed to None ghostifies if the
# object is in the up-to-date state, but not otherwise.
obj._p_changed = None
self.assertEqual(obj._p_changed, 1)
self.assertEqual(obj._p_state, CHANGED)
obj._p_changed = 0
# Now it's a ghost.
obj._p_changed = None
self.assertEqual(obj._p_changed, None)
self.assertEqual(obj._p_state, GHOST)
obj = P()
self.jar.add(obj)
obj._p_changed = 1
# You can transition directly from modified to ghost if
# you delete the _p_changed attribute.
del obj._p_changed
self.assertEqual(obj._p_changed, None)
self.assertEqual(obj._p_state, GHOST)
def testStateReadonly(self):
# make sure we can't write to _p_state; we don't want yet
# another way to change state!
obj = P()
def setstate(value):
obj._p_state = value
self.assertRaises(TypeError, setstate, GHOST)
self.assertRaises(TypeError, setstate, UPTODATE)
self.assertRaises(TypeError, setstate, CHANGED)
self.assertRaises(TypeError, setstate, STICKY)
def testInvalidate(self):
obj = P()
self.jar.add(obj)
self.assertEqual(obj._p_changed, 0)
self.assertEqual(obj._p_state, UPTODATE)
obj._p_invalidate()
self.assertEqual(obj._p_changed, None)
self.assertEqual(obj._p_state, GHOST)
obj._p_activate()
obj.x = 1
obj._p_invalidate()
self.assertEqual(obj._p_changed, None)
self.assertEqual(obj._p_state, GHOST)
def testSerial(self):
noserial = "\000" * 8
obj = P()
self.assertEqual(obj._p_serial, noserial)
def set(val):
obj._p_serial = val
self.assertRaises(ValueError, set, 1)
self.assertRaises(ValueError, set, "0123")
self.assertRaises(ValueError, set, "012345678")
self.assertRaises(ValueError, set, u"01234567")
obj._p_serial = "01234567"
del obj._p_serial
self.assertEqual(obj._p_serial, noserial)
def testMTime(self):
obj = P()
self.assertEqual(obj._p_mtime, None)
t = int(time.time())
ts = TimeStamp(*time.gmtime(t)[:6])
obj._p_serial = repr(ts)
self.assertEqual(obj._p_mtime, t)
self.assert_(isinstance(obj._p_mtime, float))
def testPicklable(self):
obj = P()
obj.attr = "test"
s = pickle.dumps(obj)
obj2 = pickle.loads(s)
self.assertEqual(obj.attr, obj2.attr)
def testGetattr(self):
obj = H1()
self.assertEqual(obj.larry, 1)
self.assertEqual(obj.curly, 2)
self.assertEqual(obj.moe, 3)
self.jar.add(obj)
obj._p_deactivate()
# The simple Jar used for testing re-initializes the object.
self.assertEqual(obj.larry, 1)
# The getattr hook modified the object, so it should now be
# in the changed state.
self.assertEqual(obj._p_changed, 1)
self.assertEqual(obj._p_state, CHANGED)
self.assertEqual(obj.curly, 2)
self.assertEqual(obj.moe, 3)
def testGetattribute(self):
obj = H2()
self.assertEqual(obj.larry, 1)
self.assertEqual(obj.curly, 2)
self.assertEqual(obj.moe, 3)
self.jar.add(obj)
obj._p_deactivate()
# The simple Jar used for testing re-initializes the object.
self.assertEqual(obj.larry, 1)
# The getattr hook modified the object, so it should now be
# in the changed state.
self.assertEqual(obj._p_changed, 1)
self.assertEqual(obj._p_state, CHANGED)
self.assertEqual(obj.curly, 2)
self.assertEqual(obj.moe, 3)
# TODO: Need to decide how __setattr__ and __delattr__ should work,
# then write tests.
def test_suite():
return unittest.makeSuite(PersistenceTest)
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Unit tests for PickleCache
$Id: test_PickleCache.py 29896 2005-04-07 04:48:06Z tim_one $
"""
class DummyConnection:
def setklassstate(self, obj):
"""Method used by PickleCache."""
def test_delitem():
"""
>>> from persistent import PickleCache
>>> conn = DummyConnection()
>>> cache = PickleCache(conn)
>>> del cache['']
Traceback (most recent call last):
...
KeyError: ''
>>> from persistent import Persistent
>>> p = Persistent()
>>> p._p_oid = 'foo'
>>> p._p_jar = conn
>>> cache['foo'] = p
>>> del cache['foo']
"""
from zope.testing.doctest import DocTestSuite
import unittest
def test_suite():
return unittest.TestSuite((
DocTestSuite(),
))
if __name__ == '__main__':
unittest.main()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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]
class TestPList(unittest.TestCase):
def _getTargetClass(self):
from persistent.list import PersistentList
return PersistentList
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.failUnless('foo' in state)
self.failIf('_v_baz' in state)
def testTheWorld(self):
# 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)
v = pl(tuple(u))
class OtherList:
def __init__(self, initlist):
self.__data = initlist
def __len__(self):
return len(self.__data)
def __getitem__(self, i):
return self.__data[i]
v0 = pl(OtherList(u0))
vv = 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)")
eq(`u2`, `l2`, "`u2` == `l2`")
# Test __cmp__ and __len__
def mycmp(a, b):
r = cmp(a, b)
if r < 0: return -1
if r > 0: return 1
return r
all = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2]
for a in all:
for b in all:
eq(mycmp(a, b), mycmp(len(a), len(b)),
"mycmp(a, b) == mycmp(len(a), len(b))")
# Test __getitem__
for i in range(len(u2)):
eq(u2[i], i, "u2[i] == i")
# Test __setitem__
uu2[0] = 0
uu2[1] = 100
try:
uu2[2] = 200
except IndexError:
pass
else:
raise TestFailed("uu2[2] shouldn't be assignable")
# Test __delitem__
del uu2[1]
del uu2[0]
try:
del uu2[0]
except IndexError:
pass
else:
raise TestFailed("uu2[0] shouldn't be deletable")
# Test __getslice__
for i in range(-3, 4):
eq(u2[:i], l2[:i], "u2[:i] == l2[:i]")
eq(u2[i:], l2[i:], "u2[i:] == l2[i:]")
for j in range(-3, 4):
eq(u2[i:j], l2[i:j], "u2[i:j] == l2[i:j]")
# Test __setslice__
for i in range(-3, 4):
u2[:i] = l2[:i]
eq(u2, l2, "u2 == l2")
u2[i:] = l2[i:]
eq(u2, l2, "u2 == l2")
for j in range(-3, 4):
u2[i:j] = l2[i:j]
eq(u2, l2, "u2 == l2")
uu2 = u2[:]
uu2[:0] = [-2, -1]
eq(uu2, [-2, -1, 0, 1], "uu2 == [-2, -1, 0, 1]")
uu2[0:] = []
eq(uu2, [], "uu2 == []")
# Test __contains__
for i in u2:
self.failUnless(i in u2, "i in u2")
for i in min(u2)-1, max(u2)+1:
self.failUnless(i not in u2, "i not in u2")
# Test __delslice__
uu2 = u2[:]
del uu2[1:2]
del uu2[0:1]
eq(uu2, [], "uu2 == []")
uu2 = u2[:]
del uu2[1:]
del uu2[:1]
eq(uu2, [], "uu2 == []")
# Test __add__, __radd__, __mul__ and __rmul__
#self.failUnless(u1 + [] == [] + u1 == u1, "u1 + [] == [] + u1 == u1")
self.failUnless(u1 + [1] == u2, "u1 + [1] == u2")
#self.failUnless([-1] + u1 == [-1, 0], "[-1] + u1 == [-1, 0]")
self.failUnless(u2 == u2*1 == 1*u2, "u2 == u2*1 == 1*u2")
self.failUnless(u2+u2 == u2*2 == 2*u2, "u2+u2 == u2*2 == 2*u2")
self.failUnless(u2+u2+u2 == u2*3 == 3*u2, "u2+u2+u2 == u2*3 == 3*u2")
# Test append
u = u1[:]
u.append(1)
eq(u, u2, "u == u2")
# Test insert
u = u2[:]
u.insert(0, -1)
eq(u, [-1, 0, 1], "u == [-1, 0, 1]")
# Test pop
u = 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")
try:
u2.index(2)
except ValueError:
pass
else:
raise TestFailed("expected ValueError")
# Test reverse
u = u2[:]
u.reverse()
eq(u, [1, 0], "u == [1, 0]")
u.reverse()
eq(u, u2, "u == u2")
# Test sort
u = pl([1, 0])
u.sort()
eq(u, u2, "u == u2")
# Test extend
u = u1[:]
u.extend(u2)
eq(u, u1 + u2, "u == u1 + u2")
# Test iadd
u = u1[:]
u += u2
eq(u, u1 + u2, "u == u1 + u2")
# Test imul
u = u1[:]
u *= 3
eq(u, u1 + u1 + u1, "u == u1 + u1 + u1")
def test_suite():
return unittest.makeSuite(TestPList)
if __name__ == "__main__":
loader = unittest.TestLoader()
unittest.main(testLoader=loader)
##############################################################################
#
# Copyright (c) 2006 Zope Corporation 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.
#
##############################################################################
"""Test PersistentMapping
"""
import unittest
l0 = {}
l1 = {0:0}
l2 = {0:0, 1:1}
class MappingTests(unittest.TestCase):
def _getTargetClass(self):
from persistent.mapping import PersistentMapping
return PersistentMapping
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.failUnless('foo' in state)
self.failIf('_v_baz' in state)
def testTheWorld(self):
# Test constructors
pm = self._getTargetClass()
u = pm()
u0 = pm(l0)
u1 = pm(l1)
u2 = pm(l2)
uu = pm(u)
uu0 = pm(u0)
uu1 = pm(u1)
uu2 = pm(u2)
class OtherMapping:
def __init__(self, initmapping):
self.__data = initmapping
def items(self):
return self.__data.items()
v0 = pm(OtherMapping(u0))
vv = pm([(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)")
eq(`u2`, `l2`, "`u2` == `l2`")
# Test __cmp__ and __len__
def mycmp(a, b):
r = cmp(a, b)
if r < 0: return -1
if r > 0: return 1
return r
all = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2]
for a in all:
for b in all:
eq(mycmp(a, b), mycmp(len(a), len(b)),
"mycmp(a, b) == mycmp(len(a), len(b))")
# Test __getitem__
for i in range(len(u2)):
eq(u2[i], i, "u2[i] == i")
# Test 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]
try:
del uu2[0]
except KeyError:
pass
else:
raise TestFailed("uu2[0] shouldn't be deletable")
# Test __contains__
for i in u2:
self.failUnless(i in u2, "i in u2")
for i in min(u2)-1, max(u2)+1:
self.failUnless(i not in u2, "i not in u2")
# Test update
l = {"a":"b"}
u = pm(l)
u.update(u2)
for i in u:
self.failUnless(i in l or i in u2, "i in l or i in u2")
for i in l:
self.failUnless(i in u, "i in u")
for i in u2:
self.failUnless(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.failUnless(5 in u2, "5 in u2")
# Test pop
x = u2.pop(1)
eq(x, 1, "u2.pop(1) == 1")
self.failUnless(1 not in u2, "1 not in u2")
try:
u2.pop(1)
except KeyError:
pass
else:
raise TestFailed("1 should not be poppable from u2")
x = u2.pop(1, 7)
eq(x, 7, "u2.pop(1, 7) == 7")
# Test popitem
items = u2.items()
key, value = u2.popitem()
self.failUnless((key, value) in items, "key, value in items")
self.failUnless(key not in u2, "key not in u2")
# Test clear
u2.clear()
eq(u2, {}, "u2 == {}")
def test_suite():
return unittest.makeSuite(MappingTests)
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
##############################################################################
#
# Copyright (c) 2004 Zope Corporation 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
This module tests and documents, through example, overriding attribute
access methods.
$Id: test_overriding_attrs.py 38214 2005-08-31 20:43:54Z mj $
"""
from persistent import Persistent
import transaction
from ZODB.tests.util import DB
class SampleOverridingGetattr(Persistent):
"""Example of overriding __getattr__
"""
def __getattr__(self, name):
"""Get attributes that can't be gotten the usual way
The __getattr__ method works pretty much the same for persistent
classes as it does for other classes. No special handling is
needed. If an object is a ghost, then it will be activated before
__getattr__ is called.
In this example, our objects returns a tuple with the attribute
name, converted to upper case and the value of _p_changed, for any
attribute that isn't handled by the default machinery.
>>> o = SampleOverridingGetattr()
>>> o._p_changed
False
>>> o._p_oid
>>> o._p_jar
>>> o.spam
('SPAM', False)
>>> o.spam = 1
>>> o.spam
1
We'll save the object, so it can be deactivated:
>>> db = DB()
>>> conn = db.open()
>>> conn.root()['o'] = o
>>> transaction.commit()
>>> o._p_deactivate()
>>> o._p_changed
And now, if we ask for an attribute it doesn't have,
>>> o.eggs
('EGGS', False)
And we see that the object was activated before calling the
__getattr__ method.
We always close databases after we use them:
>>> db.close()
"""
# Don't pretend we have any special attributes.
if name.startswith("__") and name.endswrith("__"):
raise AttributeError(name)
else:
return name.upper(), self._p_changed
class SampleOverridingGetattributeSetattrAndDelattr(Persistent):
"""Example of overriding __getattribute__, __setattr__, and __delattr__
In this example, we'll provide an example that shows how to
override the __getattribute__, __setattr__, and __delattr__
methods. We'll create a class that stores it's attributes in a
secret dictionary within it's instance dictionary.
The class will have the policy that variables with names starting
with 'tmp_' will be volatile.
"""
def __init__(self, **kw):
self.__dict__['__secret__'] = kw.copy()
def __getattribute__(self, name):
"""Get an attribute value
The __getattribute__ method is called for all attribute
accesses. It overrides the attribute access support inherited
from Persistent.
Our sample class let's us provide initial values as keyword
arguments to the constructor:
>>> o = SampleOverridingGetattributeSetattrAndDelattr(x=1)
>>> o._p_changed
0
>>> o._p_oid
>>> o._p_jar
>>> o.x
1
>>> o.y
Traceback (most recent call last):
...
AttributeError: y
Next, we'll save the object in a database so that we can
deactivate it:
>>> db = DB()
>>> conn = db.open()
>>> conn.root()['o'] = o
>>> transaction.commit()
>>> o._p_deactivate()
>>> o._p_changed
And we'll get some data:
>>> o.x
1
which activates the object:
>>> o._p_changed
0
It works for missing attribes too:
>>> o._p_deactivate()
>>> o._p_changed
>>> o.y
Traceback (most recent call last):
...
AttributeError: y
>>> o._p_changed
0
See the very important note in the comment below!
We always close databases after we use them:
>>> db.close()
"""
#################################################################
# IMPORTANT! READ THIS! 8->
#
# We *always* give Persistent a chance first.
# Persistent handles certain special attributes, like _p_
# attributes. In particular, the base class handles __dict__
# and __class__.
#
# We call _p_getattr. If it returns True, then we have to
# use Persistent.__getattribute__ to get the value.
#
#################################################################
if Persistent._p_getattr(self, name):
return Persistent.__getattribute__(self, name)
# Data should be in our secret dictionary:
secret = self.__dict__['__secret__']
if name in secret:
return secret[name]
# Maybe it's a method:
meth = getattr(self.__class__, name, None)
if meth is None:
raise AttributeError(name)
return meth.__get__(self, self.__class__)
def __setattr__(self, name, value):
"""Set an attribute value
The __setattr__ method is called for all attribute
assignments. It overrides the attribute assignment support
inherited from Persistent.
Implementors of __setattr__ methods:
1. Must call Persistent._p_setattr first to allow it
to handle some attributes and to make sure that the object
is activated if necessary, and
2. Must set _p_changed to mark objects as changed.
See the comments in the source below.
>>> o = SampleOverridingGetattributeSetattrAndDelattr()
>>> o._p_changed
0
>>> o._p_oid
>>> o._p_jar
>>> o.x
Traceback (most recent call last):
...
AttributeError: x
>>> o.x = 1
>>> o.x
1
Because the implementation doesn't store attributes directly
in the instance dictionary, we don't have a key for the attribute:
>>> 'x' in o.__dict__
False
Next, we'll save the object in a database so that we can
deactivate it:
>>> db = DB()
>>> conn = db.open()
>>> conn.root()['o'] = o
>>> transaction.commit()
>>> o._p_deactivate()
>>> o._p_changed
We'll modify an attribute
>>> o.y = 2
>>> o.y
2
which reactivates it, and markes it as modified, because our
implementation marked it as modified:
>>> o._p_changed
1
Now, if commit:
>>> transaction.commit()
>>> o._p_changed
0
And deactivate the object:
>>> o._p_deactivate()
>>> o._p_changed
and then set a variable with a name starting with 'tmp_',
The object will be activated, but not marked as modified,
because our __setattr__ implementation doesn't mark the
object as changed if the name starts with 'tmp_':
>>> o.tmp_foo = 3
>>> o._p_changed
0
>>> o.tmp_foo
3
We always close databases after we use them:
>>> db.close()
"""
#################################################################
# IMPORTANT! READ THIS! 8->
#
# We *always* give Persistent a chance first.
# Persistent handles certain special attributes, like _p_
# attributes.
#
# We call _p_setattr. If it returns True, then we are done.
# It has already set the attribute.
#
#################################################################
if Persistent._p_setattr(self, name, value):
return
self.__dict__['__secret__'][name] = value
if not name.startswith('tmp_'):
self._p_changed = 1
def __delattr__(self, name):
"""Delete an attribute value
The __delattr__ method is called for all attribute
deletions. It overrides the attribute deletion support
inherited from Persistent.
Implementors of __delattr__ methods:
1. Must call Persistent._p_delattr first to allow it
to handle some attributes and to make sure that the object
is activated if necessary, and
2. Must set _p_changed to mark objects as changed.
See the comments in the source below.
>>> o = SampleOverridingGetattributeSetattrAndDelattr(
... x=1, y=2, tmp_z=3)
>>> o._p_changed
0
>>> o._p_oid
>>> o._p_jar
>>> o.x
1
>>> del o.x
>>> o.x
Traceback (most recent call last):
...
AttributeError: x
Next, we'll save the object in a database so that we can
deactivate it:
>>> db = DB()
>>> conn = db.open()
>>> conn.root()['o'] = o
>>> transaction.commit()
>>> o._p_deactivate()
>>> o._p_changed
If we delete an attribute:
>>> del o.y
The object is activated. It is also marked as changed because
our implementation marked it as changed.
>>> o._p_changed
1
>>> o.y
Traceback (most recent call last):
...
AttributeError: y
>>> o.tmp_z
3
Now, if commit:
>>> transaction.commit()
>>> o._p_changed
0
And deactivate the object:
>>> o._p_deactivate()
>>> o._p_changed
and then delete a variable with a name starting with 'tmp_',
The object will be activated, but not marked as modified,
because our __delattr__ implementation doesn't mark the
object as changed if the name starts with 'tmp_':
>>> del o.tmp_z
>>> o._p_changed
0
>>> o.tmp_z
Traceback (most recent call last):
...
AttributeError: tmp_z
We always close databases after we use them:
>>> db.close()
"""
#################################################################
# IMPORTANT! READ THIS! 8->
#
# We *always* give Persistent a chance first.
# Persistent handles certain special attributes, like _p_
# attributes.
#
# We call _p_delattr. If it returns True, then we are done.
# It has already deleted the attribute.
#
#################################################################
if Persistent._p_delattr(self, name):
return
del self.__dict__['__secret__'][name]
if not name.startswith('tmp_'):
self._p_changed = 1
def test_suite():
from zope.testing.doctest import DocTestSuite
return DocTestSuite()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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 zope.testing import doctest
from persistent import Persistent
class P(Persistent):
def __init__(self):
self.x = 0
def inc(self):
self.x += 1
def test_suite():
return doctest.DocFileSuite("persistent.txt", globs={"P": P})
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Basic pickling tests
$Id: test_pickle.py 29896 2005-04-07 04:48:06Z tim_one $
"""
from persistent import Persistent
import pickle
def print_dict(d):
d = d.items()
d.sort()
print '{%s}' % (', '.join(
[('%r: %r' % (k, v)) for (k, v) in d]
))
def cmpattrs(self, other, *attrs):
for attr in attrs:
if attr[:3] in ('_v_', '_p_'):
continue
c = cmp(getattr(self, attr, None), getattr(other, attr, None))
if c:
return c
return 0
class Simple(Persistent):
def __init__(self, name, **kw):
self.__name__ = name
self.__dict__.update(kw)
self._v_favorite_color = 'blue'
self._p_foo = 'bar'
def __cmp__(self, other):
return cmpattrs(self, other, '__class__', *(self.__dict__.keys()))
def test_basic_pickling():
"""
>>> x = Simple('x', aaa=1, bbb='foo')
>>> print_dict(x.__getstate__())
{'__name__': 'x', 'aaa': 1, 'bbb': 'foo'}
>>> f, (c,), state = x.__reduce__()
>>> f.__name__
'__newobj__'
>>> f.__module__
'copy_reg'
>>> c.__name__
'Simple'
>>> print_dict(state)
{'__name__': 'x', 'aaa': 1, 'bbb': 'foo'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.__setstate__({'z': 1})
>>> x.__dict__
{'z': 1}
"""
class Custom(Simple):
def __new__(cls, x, y):
r = Persistent.__new__(cls)
r.x, r.y = x, y
return r
def __init__(self, x, y):
self.a = 42
def __getnewargs__(self):
return self.x, self.y
def __getstate__(self):
return self.a
def __setstate__(self, a):
self.a = a
def test_pickling_w_overrides():
"""
>>> x = Custom('x', 'y')
>>> x.a = 99
>>> (f, (c, ax, ay), a) = x.__reduce__()
>>> f.__name__
'__newobj__'
>>> f.__module__
'copy_reg'
>>> c.__name__
'Custom'
>>> ax, ay, a
('x', 'y', 99)
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
class Slotted(Persistent):
__slots__ = 's1', 's2', '_p_splat', '_v_eek'
def __init__(self, s1, s2):
self.s1, self.s2 = s1, s2
self._v_eek = 1
self._p_splat = 2
class SubSlotted(Slotted):
__slots__ = 's3', 's4'
def __init__(self, s1, s2, s3):
Slotted.__init__(self, s1, s2)
self.s3 = s3
def __cmp__(self, other):
return cmpattrs(self, other, '__class__', 's1', 's2', 's3', 's4')
def test_pickling_w_slots_only():
"""
>>> x = SubSlotted('x', 'y', 'z')
>>> d, s = x.__getstate__()
>>> d
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.s4 = 'spam'
>>> d, s = x.__getstate__()
>>> d
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
class SubSubSlotted(SubSlotted):
def __init__(self, s1, s2, s3, **kw):
SubSlotted.__init__(self, s1, s2, s3)
self.__dict__.update(kw)
self._v_favorite_color = 'blue'
self._p_foo = 'bar'
def __cmp__(self, other):
return cmpattrs(self, other,
'__class__', 's1', 's2', 's3', 's4',
*(self.__dict__.keys()))
def test_pickling_w_slots():
"""
>>> x = SubSubSlotted('x', 'y', 'z', aaa=1, bbb='foo')
>>> d, s = x.__getstate__()
>>> print_dict(d)
{'aaa': 1, 'bbb': 'foo'}
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.s4 = 'spam'
>>> d, s = x.__getstate__()
>>> print_dict(d)
{'aaa': 1, 'bbb': 'foo'}
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
def test_pickling_w_slots_w_empty_dict():
"""
>>> x = SubSubSlotted('x', 'y', 'z')
>>> d, s = x.__getstate__()
>>> print_dict(d)
{}
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
1
>>> x.s4 = 'spam'
>>> d, s = x.__getstate__()
>>> print_dict(d)
{}
>>> print_dict(s)
{'s1': 'x', 's2': 'y', 's3': 'z', 's4': 'spam'}
>>> pickle.loads(pickle.dumps(x)) == x
1
>>> pickle.loads(pickle.dumps(x, 0)) == x
1
>>> pickle.loads(pickle.dumps(x, 1)) == x
1
>>> pickle.loads(pickle.dumps(x, 2)) == x
1
"""
from zope.testing.doctest import DocTestSuite
import unittest
def test_suite():
return unittest.TestSuite((
DocTestSuite(),
))
if __name__ == '__main__': unittest.main()
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""
$Id: test_wref.py 29896 2005-04-07 04:48:06Z tim_one $
"""
import unittest
from zope.testing.doctest import DocTestSuite
def test_suite():
return DocTestSuite('persistent.wref')
if __name__ == '__main__':
unittest.main()
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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
$Id: wref.py 29450 2005-03-11 23:53:09Z tim_one $
"""
from persistent import Persistent
WeakRefMarker = object()
class WeakRef(object):
"""Persistent weak references
Persistent weak references are used much like Python weak
references. The major difference is that you can't specify an
object to be called when the object is removed from the database.
Here's an example. We'll start by creating a persistent object and
a refernce to it:
>>> import persistent.list
>>> import ZODB.tests.util
>>> ob = persistent.list.PersistentList()
>>> ref = WeakRef(ob)
>>> ref() is ob
True
The hash of the ref if the same as the hash of the referenced object:
>>> hash(ref) == hash(ob)
True
Two refs to the same object are equal:
>>> WeakRef(ob) == ref
True
>>> ob2 = persistent.list.PersistentList([1])
>>> WeakRef(ob2) == ref
False
Lets save the reference and the referenced object in a database:
>>> db = ZODB.tests.util.DB()
>>> conn1 = db.open()
>>> conn1.root()['ob'] = ob
>>> conn1.root()['ref'] = ref
>>> ZODB.tests.util.commit()
If we open a new connection, we can use the reference:
>>> conn2 = db.open()
>>> conn2.root()['ref']() is conn2.root()['ob']
True
>>> hash(conn2.root()['ref']) == hash(conn2.root()['ob'])
True
But if we delete the referenced object and pack:
>>> del conn2.root()['ob']
>>> ZODB.tests.util.commit()
>>> ZODB.tests.util.pack(db)
And then look in a new connection:
>>> conn3 = db.open()
>>> conn3.root()['ob']
Traceback (most recent call last):
...
KeyError: 'ob'
Trying to dereference the reference returns None:
>>> conn3.root()['ref']()
Trying to get a hash, raises a type error:
>>> hash(conn3.root()['ref'])
Traceback (most recent call last):
...
TypeError: Weakly-referenced object has gone away
Always explicitly close databases: :)
>>> db.close()
"""
# We set _p_oid to a marker so that the serialization system can
# provide special handling of weakrefs.
_p_oid = WeakRefMarker
def __init__(self, ob):
self._v_ob = ob
self.oid = ob._p_oid
self.dm = ob._p_jar
def __call__(self):
try:
return self._v_ob
except AttributeError:
try:
self._v_ob = self.dm[self.oid]
except KeyError:
return None
return self._v_ob
def __hash__(self):
self = self()
if self is None:
raise TypeError('Weakly-referenced object has gone away')
return hash(self)
def __eq__(self, other):
self = self()
if self is None:
raise TypeError('Weakly-referenced object has gone away')
other = other()
if other is None:
raise TypeError('Weakly-referenced object has gone away')
return self == other
class PersistentWeakKeyDictionary(Persistent):
"""Persistent weak key dictionary
This is akin to WeakKeyDictionaries. Note, however, that removal
of items is extremely lazy. See below.
We'll start by creating a PersistentWeakKeyDictionary and adding
some persistent objects to it.
>>> d = PersistentWeakKeyDictionary()
>>> import ZODB.tests.util
>>> p1 = ZODB.tests.util.P('p1')
>>> p2 = ZODB.tests.util.P('p2')
>>> p3 = ZODB.tests.util.P('p3')
>>> d[p1] = 1
>>> d[p2] = 2
>>> d[p3] = 3
We'll create an extra persistent object that's not in the dict:
>>> p4 = ZODB.tests.util.P('p4')
Now we'll excercise iteration and item access:
>>> l = [(str(k), d[k], d.get(k)) for k in d]
>>> l.sort()
>>> l
[('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)]
And the containment operator:
>>> [p in d for p in [p1, p2, p3, p4]]
[True, True, True, False]
We can add the dict and the referenced objects to a database:
>>> db = ZODB.tests.util.DB()
>>> conn1 = db.open()
>>> conn1.root()['p1'] = p1
>>> conn1.root()['d'] = d
>>> conn1.root()['p2'] = p2
>>> conn1.root()['p3'] = p3
>>> ZODB.tests.util.commit()
And things still work, as before:
>>> l = [(str(k), d[k], d.get(k)) for k in d]
>>> l.sort()
>>> l
[('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)]
>>> [p in d for p in [p1, p2, p3, p4]]
[True, True, True, False]
Likewise, we can read the objects from another connection and
things still work.
>>> conn2 = db.open()
>>> d = conn2.root()['d']
>>> p1 = conn2.root()['p1']
>>> p2 = conn2.root()['p2']
>>> p3 = conn2.root()['p3']
>>> l = [(str(k), d[k], d.get(k)) for k in d]
>>> l.sort()
>>> l
[('P(p1)', 1, 1), ('P(p2)', 2, 2), ('P(p3)', 3, 3)]
>>> [p in d for p in [p1, p2, p3, p4]]
[True, True, True, False]
Now, we'll delete one of the objects from the database, but *not*
from the dictionary:
>>> del conn2.root()['p2']
>>> ZODB.tests.util.commit()
And pack the database, so that the no-longer referenced p2 is
actually removed from the database.
>>> ZODB.tests.util.pack(db)
Now if we access the dictionary in a new connection, it no longer
has p2:
>>> conn3 = db.open()
>>> d = conn3.root()['d']
>>> l = [(str(k), d[k], d.get(k)) for k in d]
>>> l.sort()
>>> l
[('P(p1)', 1, 1), ('P(p3)', 3, 3)]
It's worth nothing that that the versions of the dictionary in
conn1 and conn2 still have p2, because p2 is still in the caches
for those connections.
Always explicitly close databases: :)
>>> db.close()
"""
# 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)
if kwargs:
self.update(kwargs)
def __getstate__(self):
state = Persistent.__getstate__(self)
state['data'] = state['data'].items()
return state
def __setstate__(self, state):
state['data'] = dict([
(k, v) for (k, v) in state['data']
if k() is not None
])
Persistent.__setstate__(self, state)
def __setitem__(self, key, value):
self.data[WeakRef(key)] = value
def __getitem__(self, key):
return self.data[WeakRef(key)]
def __delitem__(self, key):
del self.data[WeakRef(key)]
def get(self, key, default=None):
"""D.get(k[, d]) -> D[k] if k in D, else d.
>>> import ZODB.tests.util
>>> key = ZODB.tests.util.P("key")
>>> missing = ZODB.tests.util.P("missing")
>>> d = PersistentWeakKeyDictionary([(key, 1)])
>>> d.get(key)
1
>>> d.get(missing)
>>> d.get(missing, 12)
12
"""
return self.data.get(WeakRef(key), default)
def __contains__(self, key):
return WeakRef(key) in self.data
def __iter__(self):
for k in self.data:
yield k()
def update(self, adict):
if isinstance(adict, PersistentWeakKeyDictionary):
self.data.update(adict.update)
else:
for k, v in adict.items():
self.data[WeakRef(k)] = v
# TODO: May need more methods, and tests.
##############################################################################
#
# 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.
#
##############################################################################
"""More convenience functions for dealing with proxies.
"""
from zope.interface import moduleProvides
from zope.proxy.interfaces import IProxyIntrospection
from zope.proxy._zope_proxy_proxy import *
from zope.proxy._zope_proxy_proxy import _CAPI
moduleProvides(IProxyIntrospection)
__all__ = tuple(IProxyIntrospection)
def ProxyIterator(p):
yield p
while isProxy(p):
p = getProxiedObject(p)
yield p
def non_overridable(func):
return property(lambda self: func.__get__(self))
/*############################################################################
#
# 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.
#
############################################################################*/
/*
* This file is also used as a really extensive macro in
* ../container/_zope_container_contained.c. If you need to
* change this file, you need to "svn copy" it to ../container/.
*
* This approach is taken to allow the sources for the two packages
* to be compilable when the relative locations of these aren't
* related in the same way as they are in a checkout.
*
* This will be revisited in the future, but works for now.
*/
#include "Python.h"
#include "modsupport.h"
#define PROXY_MODULE
#include "proxy.h"
static PyTypeObject ProxyType;
#define Proxy_Check(wrapper) (PyObject_TypeCheck((wrapper), &ProxyType))
static PyObject *
empty_tuple = NULL;
/*
* Slot methods.
*/
static PyObject *
wrap_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *result = NULL;
PyObject *object;
if (PyArg_UnpackTuple(args, "__new__", 1, 1, &object)) {
if (kwds != NULL && PyDict_Size(kwds) != 0) {
PyErr_SetString(PyExc_TypeError,
"proxy.__new__ does not accept keyword args");
return NULL;
}
result = PyType_GenericNew(type, args, kwds);
if (result != NULL) {
ProxyObject *wrapper = (ProxyObject *) result;
Py_INCREF(object);
wrapper->proxy_object = object;
}
}
return result;
}
static int
wrap_init(PyObject *self, PyObject *args, PyObject *kwds)
{
int result = -1;
PyObject *object;
if (PyArg_UnpackTuple(args, "__init__", 1, 1, &object)) {
ProxyObject *wrapper = (ProxyObject *)self;
if (kwds != NULL && PyDict_Size(kwds) != 0) {
PyErr_SetString(PyExc_TypeError,
"proxy.__init__ does not accept keyword args");
return -1;
}
/* If the object in this proxy is not the one we
* received in args, replace it with the new one.
*/
if (wrapper->proxy_object != object) {
PyObject *temp = wrapper->proxy_object;
Py_INCREF(object);
wrapper->proxy_object = object;
Py_DECREF(temp);
}
result = 0;
}
return result;
}
static int
wrap_traverse(PyObject *self, visitproc visit, void *arg)
{
PyObject *ob = Proxy_GET_OBJECT(self);
if (ob != NULL)
return visit(ob, arg);
else
return 0;
}
static int
wrap_clear(PyObject *self)
{
ProxyObject *proxy = (ProxyObject *)self;
PyObject *temp = proxy->proxy_object;
if (temp != NULL) {
proxy->proxy_object = NULL;
Py_DECREF(temp);
}
return 0;
}
static PyObject *
wrap_richcompare(PyObject* self, PyObject* other, int op)
{
if (Proxy_Check(self)) {
self = Proxy_GET_OBJECT(self);
}
else {
other = Proxy_GET_OBJECT(other);
}
return PyObject_RichCompare(self, other, op);
}
static PyObject *
wrap_iter(PyObject *self)
{
return PyObject_GetIter(Proxy_GET_OBJECT(self));
}
static PyObject *
wrap_iternext(PyObject *self)
{
return PyIter_Next(Proxy_GET_OBJECT(self));
}
static void
wrap_dealloc(PyObject *self)
{
(void) wrap_clear(self);
self->ob_type->tp_free(self);
}
/* A variant of _PyType_Lookup that doesn't look in ProxyType.
*
* If argument search_wrappertype is nonzero, we can look in WrapperType.
*/
PyObject *
WrapperType_Lookup(PyTypeObject *type, PyObject *name)
{
int i, n;
PyObject *mro, *res, *base, *dict;
/* Look in tp_dict of types in MRO */
mro = type->tp_mro;
/* If mro is NULL, the type is either not yet initialized
by PyType_Ready(), or already cleared by type_clear().
Either way the safest thing to do is to return NULL. */
if (mro == NULL)
return NULL;
assert(PyTuple_Check(mro));
n = PyTuple_GET_SIZE(mro)
- 1; /* We don't want to look at the last item, which is object. */
for (i = 0; i < n; i++) {
base = PyTuple_GET_ITEM(mro, i);
if (((PyTypeObject *)base) != &ProxyType) {
if (PyClass_Check(base))
dict = ((PyClassObject *)base)->cl_dict;
else {
assert(PyType_Check(base));
dict = ((PyTypeObject *)base)->tp_dict;
}
assert(dict && PyDict_Check(dict));
res = PyDict_GetItem(dict, name);
if (res != NULL)
return res;
}
}
return NULL;
}
static PyObject *
wrap_getattro(PyObject *self, PyObject *name)
{
PyObject *wrapped;
PyObject *descriptor;
PyObject *res = NULL;
char *name_as_string;
int maybe_special_name;
#ifdef Py_USING_UNICODE
/* The Unicode to string conversion is done here because the
existing tp_getattro slots expect a string object as name
and we wouldn't want to break those. */
if (PyUnicode_Check(name)) {
name = PyUnicode_AsEncodedString(name, NULL, NULL);
if (name == NULL)
return NULL;
}
else
#endif
if (!PyString_Check(name)){
PyErr_SetString(PyExc_TypeError, "attribute name must be string");
return NULL;
}
else
Py_INCREF(name);
name_as_string = PyString_AS_STRING(name);
wrapped = Proxy_GET_OBJECT(self);
if (wrapped == NULL) {
PyErr_Format(PyExc_RuntimeError,
"object is NULL; requested to get attribute '%s'",
name_as_string);
goto finally;
}
maybe_special_name = name_as_string[0] == '_' && name_as_string[1] == '_';
if (!(maybe_special_name && strcmp(name_as_string, "__class__") == 0)) {
descriptor = WrapperType_Lookup(self->ob_type, name);
if (descriptor != NULL) {
if (PyType_HasFeature(descriptor->ob_type, Py_TPFLAGS_HAVE_CLASS)
&& descriptor->ob_type->tp_descr_get != NULL) {
if (descriptor->ob_type->tp_descr_set == NULL)
{
res = PyObject_GetAttr(wrapped, name);
if (res != NULL)
goto finally;
if (PyErr_ExceptionMatches(PyExc_AttributeError))
PyErr_Clear();
else
goto finally;
}
res = descriptor->ob_type->tp_descr_get(
descriptor,
self,
(PyObject *)self->ob_type);
} else {
Py_INCREF(descriptor);
res = descriptor;
}
goto finally;
}
}
res = PyObject_GetAttr(wrapped, name);
finally:
Py_DECREF(name);
return res;
}
static int
wrap_setattro(PyObject *self, PyObject *name, PyObject *value)
{
PyObject *wrapped;
PyObject *descriptor;
int res = -1;
#ifdef Py_USING_UNICODE
/* The Unicode to string conversion is done here because the
existing tp_setattro slots expect a string object as name
and we wouldn't want to break those. */
if (PyUnicode_Check(name)) {
name = PyUnicode_AsEncodedString(name, NULL, NULL);
if (name == NULL)
return -1;
}
else
#endif
if (!PyString_Check(name)){
PyErr_SetString(PyExc_TypeError, "attribute name must be string");
return -1;
}
else
Py_INCREF(name);
descriptor = WrapperType_Lookup(self->ob_type, name);
if (descriptor != NULL
&& PyType_HasFeature(descriptor->ob_type, Py_TPFLAGS_HAVE_CLASS)
&& descriptor->ob_type->tp_descr_set != NULL)
{
res = descriptor->ob_type->tp_descr_set(descriptor, self, value);
goto finally;
}
wrapped = Proxy_GET_OBJECT(self);
if (wrapped == NULL) {
PyErr_Format(PyExc_RuntimeError,
"object is NULL; requested to set attribute '%s'",
PyString_AS_STRING(name));
goto finally;
}
res = PyObject_SetAttr(wrapped, name, value);
finally:
Py_DECREF(name);
return res;
}
static int
wrap_print(PyObject *wrapper, FILE *fp, int flags)
{
return PyObject_Print(Proxy_GET_OBJECT(wrapper), fp, flags);
}
static PyObject *
wrap_str(PyObject *wrapper) {
return PyObject_Str(Proxy_GET_OBJECT(wrapper));
}
static PyObject *
wrap_repr(PyObject *wrapper)
{
return PyObject_Repr(Proxy_GET_OBJECT(wrapper));
}
static int
wrap_compare(PyObject *wrapper, PyObject *v)
{
return PyObject_Compare(Proxy_GET_OBJECT(wrapper), v);
}
static long
wrap_hash(PyObject *self)
{
return PyObject_Hash(Proxy_GET_OBJECT(self));
}
static PyObject *
wrap_call(PyObject *self, PyObject *args, PyObject *kw)
{
if (kw)
return PyEval_CallObjectWithKeywords(Proxy_GET_OBJECT(self),
args, kw);
else
return PyObject_CallObject(Proxy_GET_OBJECT(self), args);
}
/*
* Number methods
*/
/*
* Number methods.
*/
static PyObject *
call_int(PyObject *self)
{
PyNumberMethods *nb = self->ob_type->tp_as_number;
if (nb == NULL || nb->nb_int == NULL) {
PyErr_SetString(PyExc_TypeError,
"object can't be converted to int");
return NULL;
}
return nb->nb_int(self);
}
static PyObject *
call_long(PyObject *self)
{
PyNumberMethods *nb = self->ob_type->tp_as_number;
if (nb == NULL || nb->nb_long == NULL) {
PyErr_SetString(PyExc_TypeError,
"object can't be converted to long");
return NULL;
}
return nb->nb_long(self);
}
static PyObject *
call_float(PyObject *self)
{
PyNumberMethods *nb = self->ob_type->tp_as_number;
if (nb == NULL || nb->nb_float== NULL) {
PyErr_SetString(PyExc_TypeError,
"object can't be converted to float");
return NULL;
}
return nb->nb_float(self);
}
static PyObject *
call_oct(PyObject *self)
{
PyNumberMethods *nb = self->ob_type->tp_as_number;
if (nb == NULL || nb->nb_oct== NULL) {
PyErr_SetString(PyExc_TypeError,
"object can't be converted to oct");
return NULL;
}
return nb->nb_oct(self);
}
static PyObject *
call_hex(PyObject *self)
{
PyNumberMethods *nb = self->ob_type->tp_as_number;
if (nb == NULL || nb->nb_hex == NULL) {
PyErr_SetString(PyExc_TypeError,
"object can't be converted to hex");
return NULL;
}
return nb->nb_hex(self);
}
static PyObject *
call_ipow(PyObject *self, PyObject *other)
{
/* PyNumber_InPlacePower has three args. How silly. :-) */
return PyNumber_InPlacePower(self, other, Py_None);
}
typedef PyObject *(*function1)(PyObject *);
static PyObject *
check1(ProxyObject *self, char *opname, function1 operation)
{
PyObject *result = NULL;
result = operation(Proxy_GET_OBJECT(self));
#if 0
if (result != NULL)
/* ??? create proxy for result? */
;
#endif
return result;
}
static PyObject *
check2(PyObject *self, PyObject *other,
char *opname, char *ropname, binaryfunc operation)
{
PyObject *result = NULL;
PyObject *object;
if (Proxy_Check(self)) {
object = Proxy_GET_OBJECT(self);
result = operation(object, other);
}
else if (Proxy_Check(other)) {
object = Proxy_GET_OBJECT(other);
result = operation(self, object);
}
else {
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
#if 0
if (result != NULL)
/* ??? create proxy for result? */
;
#endif
return result;
}
static PyObject *
check2i(ProxyObject *self, PyObject *other,
char *opname, binaryfunc operation)
{
PyObject *result = NULL;
PyObject *object = Proxy_GET_OBJECT(self);
result = operation(object, other);
if (result == object) {
/* If the operation was really carried out inplace,
don't create a new proxy, but use the old one. */
Py_INCREF(self);
Py_DECREF(object);
result = (PyObject *)self;
}
#if 0
else if (result != NULL)
/* ??? create proxy for result? */
;
#endif
return result;
}
#define UNOP(NAME, CALL) \
static PyObject *wrap_##NAME(PyObject *self) \
{ return check1((ProxyObject *)self, "__"#NAME"__", CALL); }
#define BINOP(NAME, CALL) \
static PyObject *wrap_##NAME(PyObject *self, PyObject *other) \
{ return check2(self, other, "__"#NAME"__", "__r"#NAME"__", CALL); }
#define INPLACE(NAME, CALL) \
static PyObject *wrap_i##NAME(PyObject *self, PyObject *other) \
{ return check2i((ProxyObject *)self, other, "__i"#NAME"__", CALL); }
BINOP(add, PyNumber_Add)
BINOP(sub, PyNumber_Subtract)
BINOP(mul, PyNumber_Multiply)
BINOP(div, PyNumber_Divide)
BINOP(mod, PyNumber_Remainder)
BINOP(divmod, PyNumber_Divmod)
static PyObject *
wrap_pow(PyObject *self, PyObject *other, PyObject *modulus)
{
PyObject *result = NULL;
PyObject *object;
if (Proxy_Check(self)) {
object = Proxy_GET_OBJECT(self);
result = PyNumber_Power(object, other, modulus);
}
else if (Proxy_Check(other)) {
object = Proxy_GET_OBJECT(other);
result = PyNumber_Power(self, object, modulus);
}
else if (modulus != NULL && Proxy_Check(modulus)) {
object = Proxy_GET_OBJECT(modulus);
result = PyNumber_Power(self, other, modulus);
}
else {
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
return result;
}
BINOP(lshift, PyNumber_Lshift)
BINOP(rshift, PyNumber_Rshift)
BINOP(and, PyNumber_And)
BINOP(xor, PyNumber_Xor)
BINOP(or, PyNumber_Or)
static int
wrap_coerce(PyObject **p_self, PyObject **p_other)
{
PyObject *self = *p_self;
PyObject *other = *p_other;
PyObject *object;
PyObject *left;
PyObject *right;
int r;
assert(Proxy_Check(self));
object = Proxy_GET_OBJECT(self);
left = object;
right = other;
r = PyNumber_CoerceEx(&left, &right);
if (r != 0)
return r;
/* Now left and right have been INCREF'ed. Any new value that
comes out is proxied; any unchanged value is left unchanged. */
if (left == object) {
/* Keep the old proxy */
Py_INCREF(self);
Py_DECREF(left);
left = self;
}
#if 0
else {
/* ??? create proxy for left? */
}
if (right != other) {
/* ??? create proxy for right? */
}
#endif
*p_self = left;
*p_other = right;
return 0;
}
UNOP(neg, PyNumber_Negative)
UNOP(pos, PyNumber_Positive)
UNOP(abs, PyNumber_Absolute)
UNOP(invert, PyNumber_Invert)
UNOP(int, call_int)
UNOP(long, call_long)
UNOP(float, call_float)
UNOP(oct, call_oct)
UNOP(hex, call_hex)
INPLACE(add, PyNumber_InPlaceAdd)
INPLACE(sub, PyNumber_InPlaceSubtract)
INPLACE(mul, PyNumber_InPlaceMultiply)
INPLACE(div, PyNumber_InPlaceDivide)
INPLACE(mod, PyNumber_InPlaceRemainder)
INPLACE(pow, call_ipow)
INPLACE(lshift, PyNumber_InPlaceLshift)
INPLACE(rshift, PyNumber_InPlaceRshift)
INPLACE(and, PyNumber_InPlaceAnd)
INPLACE(xor, PyNumber_InPlaceXor)
INPLACE(or, PyNumber_InPlaceOr)
BINOP(floordiv, PyNumber_FloorDivide)
BINOP(truediv, PyNumber_TrueDivide)
INPLACE(floordiv, PyNumber_InPlaceFloorDivide)
INPLACE(truediv, PyNumber_InPlaceTrueDivide)
static int
wrap_nonzero(PyObject *self)
{
return PyObject_IsTrue(Proxy_GET_OBJECT(self));
}
/*
* Sequence methods
*/
static Py_ssize_t
wrap_length(PyObject *self)
{
return PyObject_Length(Proxy_GET_OBJECT(self));
}
static PyObject *
wrap_slice(PyObject *self, Py_ssize_t start, Py_ssize_t end)
{
PyObject *obj = Proxy_GET_OBJECT(self);
if (PyList_Check(obj)) {
return PyList_GetSlice(obj, start, end);
}
else if (PyTuple_Check(obj)) {
return PyTuple_GetSlice(obj, start, end);
}
else {
return PySequence_GetSlice(obj, start, end);
}
}
static int
wrap_ass_slice(PyObject *self, Py_ssize_t i, Py_ssize_t j, PyObject *value)
{
PyObject *obj = Proxy_GET_OBJECT(self);
if (PyList_Check(obj)) {
return PyList_SetSlice(obj, i, j, value);
}
else {
return PySequence_SetSlice(obj, i, j, value);
}
}
static int
wrap_contains(PyObject *self, PyObject *value)
{
return PySequence_Contains(Proxy_GET_OBJECT(self), value);
}
/*
* Mapping methods
*/
static PyObject *
wrap_getitem(PyObject *wrapper, PyObject *v) {
return PyObject_GetItem(Proxy_GET_OBJECT(wrapper), v);
}
static int
wrap_setitem(PyObject *self, PyObject *key, PyObject *value)
{
if (value == NULL)
return PyObject_DelItem(Proxy_GET_OBJECT(self), key);
else
return PyObject_SetItem(Proxy_GET_OBJECT(self), key, value);
}
/*
* Normal methods
*/
static char
reduce__doc__[] =
"__reduce__()\n"
"Raise an exception; this prevents proxies from being picklable by\n"
"default, even if the underlying object is picklable.";
static PyObject *
wrap_reduce(PyObject *self)
{
PyObject *pickle_error = NULL;
PyObject *pickle = PyImport_ImportModule("pickle");
if (pickle == NULL)
PyErr_Clear();
else {
pickle_error = PyObject_GetAttrString(pickle, "PicklingError");
if (pickle_error == NULL)
PyErr_Clear();
}
if (pickle_error == NULL) {
pickle_error = PyExc_RuntimeError;
Py_INCREF(pickle_error);
}
PyErr_SetString(pickle_error,
"proxy instances cannot be pickled");
Py_DECREF(pickle_error);
return NULL;
}
static PyNumberMethods
wrap_as_number = {
wrap_add, /* nb_add */
wrap_sub, /* nb_subtract */
wrap_mul, /* nb_multiply */
wrap_div, /* nb_divide */
wrap_mod, /* nb_remainder */
wrap_divmod, /* nb_divmod */
wrap_pow, /* nb_power */
wrap_neg, /* nb_negative */
wrap_pos, /* nb_positive */
wrap_abs, /* nb_absolute */
wrap_nonzero, /* nb_nonzero */
wrap_invert, /* nb_invert */
wrap_lshift, /* nb_lshift */
wrap_rshift, /* nb_rshift */
wrap_and, /* nb_and */
wrap_xor, /* nb_xor */
wrap_or, /* nb_or */
wrap_coerce, /* nb_coerce */
wrap_int, /* nb_int */
wrap_long, /* nb_long */
wrap_float, /* nb_float */
wrap_oct, /* nb_oct */
wrap_hex, /* nb_hex */
/* Added in release 2.0 */
/* These require the Py_TPFLAGS_HAVE_INPLACEOPS flag */
wrap_iadd, /* nb_inplace_add */
wrap_isub, /* nb_inplace_subtract */
wrap_imul, /* nb_inplace_multiply */
wrap_idiv, /* nb_inplace_divide */
wrap_imod, /* nb_inplace_remainder */
(ternaryfunc)wrap_ipow, /* nb_inplace_power */
wrap_ilshift, /* nb_inplace_lshift */
wrap_irshift, /* nb_inplace_rshift */
wrap_iand, /* nb_inplace_and */
wrap_ixor, /* nb_inplace_xor */
wrap_ior, /* nb_inplace_or */
/* Added in release 2.2 */
/* These require the Py_TPFLAGS_HAVE_CLASS flag */
wrap_floordiv, /* nb_floor_divide */
wrap_truediv, /* nb_true_divide */
wrap_ifloordiv, /* nb_inplace_floor_divide */
wrap_itruediv, /* nb_inplace_true_divide */
};
static PySequenceMethods
wrap_as_sequence = {
wrap_length, /* sq_length */
0, /* sq_concat */
0, /* sq_repeat */
0, /* sq_item */
wrap_slice, /* sq_slice */
0, /* sq_ass_item */
wrap_ass_slice, /* sq_ass_slice */
wrap_contains, /* sq_contains */
};
static PyMappingMethods
wrap_as_mapping = {
wrap_length, /* mp_length */
wrap_getitem, /* mp_subscript */
wrap_setitem, /* mp_ass_subscript */
};
static PyMethodDef
wrap_methods[] = {
{"__reduce__", (PyCFunction)wrap_reduce, METH_NOARGS, reduce__doc__},
{NULL, NULL},
};
/*
* Note that the numeric methods are not supported. This is primarily
* because of the way coercion-less operations are performed with
* new-style numbers; since we can't tell which side of the operation
* is 'self', we can't ensure we'd unwrap the right thing to perform
* the actual operation. We also can't afford to just unwrap both
* sides the way weakrefs do, since we don't know what semantics will
* be associated with the wrapper itself.
*/
statichere PyTypeObject
ProxyType = {
PyObject_HEAD_INIT(NULL) /* PyObject_HEAD_INIT(&PyType_Type) */
0,
"zope.proxy.ProxyBase",
sizeof(ProxyObject),
0,
wrap_dealloc, /* tp_dealloc */
wrap_print, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
wrap_compare, /* tp_compare */
wrap_repr, /* tp_repr */
&wrap_as_number, /* tp_as_number */
&wrap_as_sequence, /* tp_as_sequence */
&wrap_as_mapping, /* tp_as_mapping */
wrap_hash, /* tp_hash */
wrap_call, /* tp_call */
wrap_str, /* tp_str */
wrap_getattro, /* tp_getattro */
wrap_setattro, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC
| Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_BASETYPE, /* tp_flags */
0, /* tp_doc */
wrap_traverse, /* tp_traverse */
wrap_clear, /* tp_clear */
wrap_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
wrap_iter, /* tp_iter */
wrap_iternext, /* tp_iternext */
wrap_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
wrap_init, /* tp_init */
0, /* tp_alloc */
wrap_new, /* tp_new */
0, /*_PyObject_GC_Del,*/ /* tp_free */
};
static PyObject *
create_proxy(PyObject *object)
{
PyObject *result = NULL;
PyObject *args;
args = PyTuple_New(1);
if (args != NULL) {
Py_INCREF(object);
PyTuple_SET_ITEM(args, 0, object);
result = PyObject_CallObject((PyObject *)&ProxyType, args);
Py_DECREF(args);
}
return result;
}
static int
api_check(PyObject *obj)
{
return obj ? Proxy_Check(obj) : 0;
}
static PyObject *
api_create(PyObject *object)
{
if (object == NULL) {
PyErr_SetString(PyExc_ValueError,
"cannot create proxy around NULL");
return NULL;
}
return create_proxy(object);
}
static PyObject *
api_getobject(PyObject *proxy)
{
if (proxy == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"cannot pass NULL to ProxyAPI.getobject()");
return NULL;
}
if (Proxy_Check(proxy))
return Proxy_GET_OBJECT(proxy);
else {
PyErr_Format(PyExc_TypeError, "expected proxy object, got %s",
proxy->ob_type->tp_name);
return NULL;
}
}
static ProxyInterface
wrapper_capi = {
&ProxyType,
api_check,
api_create,
api_getobject,
};
static PyObject *api_object = NULL;
static char
getobject__doc__[] =
"getProxiedObject(proxy) --> object\n"
"\n"
"Get the underlying object for proxy, or the object itself, if it is\n"
"not a proxy.";
static PyObject *
wrapper_getobject(PyObject *unused, PyObject *obj)
{
if (Proxy_Check(obj))
obj = Proxy_GET_OBJECT(obj);
if (obj == NULL)
obj = Py_None;
Py_INCREF(obj);
return obj;
}
static char
setobject__doc__[] =
"setProxiedObject(proxy, object) --> object\n"
"\n"
"Set the underlying object for proxy, returning the old proxied object.\n"
"Raises TypeError if proxy is not a proxy.\n";
static PyObject *
wrapper_setobject(PyObject *unused, PyObject *args)
{
PyObject *proxy;
PyObject *object;
PyObject *result = NULL;
if (PyArg_ParseTuple(args, "O!O:setProxiedObject",
&ProxyType, &proxy, &object)) {
result = Proxy_GET_OBJECT(proxy);
Py_INCREF(object);
((ProxyObject *) proxy)->proxy_object = object;
}
return result;
}
static char
isProxy__doc__[] =
"Check whether the given object is a proxy\n"
"\n"
"If proxytype is not None, checkes whether the object is\n"
"proxied by the given proxytype.\n"
;
static PyObject *
wrapper_isProxy(PyObject *unused, PyObject *args)
{
PyObject *obj, *result;
PyTypeObject *proxytype=&ProxyType;
if (! PyArg_ParseTuple(args, "O|O!:isProxy",
&obj, &PyType_Type, &proxytype)
)
return NULL;
while (obj && Proxy_Check(obj))
{
if (PyObject_TypeCheck(obj, proxytype))
{
result = Py_True;
Py_INCREF(result);
return result;
}
obj = Proxy_GET_OBJECT(obj);
}
result = Py_False;
Py_INCREF(result);
return result;
}
static char
removeAllProxies__doc__[] =
"removeAllProxies(proxy) --> object\n"
"\n"
"Get the proxied object with no proxies\n"
"\n"
"If obj is not a proxied object, return obj.\n"
"\n"
"The returned object has no proxies.\n"
;
static PyObject *
wrapper_removeAllProxies(PyObject *unused, PyObject *obj)
{
while (obj && Proxy_Check(obj))
obj = Proxy_GET_OBJECT(obj);
if (obj == NULL)
obj = Py_None;
Py_INCREF(obj);
return obj;
}
static char
sameProxiedObjects__doc__[] =
"Check whether two objects are the same or proxies of the same object";
static PyObject *
wrapper_sameProxiedObjects(PyObject *unused, PyObject *args)
{
PyObject *ob1, *ob2;
if (! PyArg_ParseTuple(args, "OO:sameProxiedObjects", &ob1, &ob2))
return NULL;
while (ob1 && Proxy_Check(ob1))
ob1 = Proxy_GET_OBJECT(ob1);
while (ob2 && Proxy_Check(ob2))
ob2 = Proxy_GET_OBJECT(ob2);
if (ob1 == ob2)
ob1 = Py_True;
else
ob1 = Py_False;
Py_INCREF(ob1);
return ob1;
}
static char
queryProxy__doc__[] =
"Look for a proxy of the given type around the object\n"
"\n"
"If no such proxy can be found, return the default.\n"
;
static PyObject *
wrapper_queryProxy(PyObject *unused, PyObject *args)
{
PyObject *obj, *result=Py_None;
PyTypeObject *proxytype=&ProxyType;
if (! PyArg_ParseTuple(args, "O|O!O:queryProxy",
&obj, &PyType_Type, &proxytype, &result)
)
return NULL;
while (obj && Proxy_Check(obj))
{
if (PyObject_TypeCheck(obj, proxytype))
{
Py_INCREF(obj);
return obj;
}
obj = Proxy_GET_OBJECT(obj);
}
Py_INCREF(result);
return result;
}
static char
queryInnerProxy__doc__[] =
"Look for the inner-most proxy of the given type around the object\n"
"\n"
"If no such proxy can be found, return the default.\n"
"\n"
"If there is such a proxy, return the inner-most one.\n"
;
static PyObject *
wrapper_queryInnerProxy(PyObject *unused, PyObject *args)
{
PyObject *obj, *result=Py_None;
PyTypeObject *proxytype=&ProxyType;
if (! PyArg_ParseTuple(args, "O|O!O:queryInnerProxy",
&obj, &PyType_Type, &proxytype, &result)
)
return NULL;
while (obj && Proxy_Check(obj))
{
if (PyObject_TypeCheck(obj, proxytype))
result = obj;
obj = Proxy_GET_OBJECT(obj);
}
Py_INCREF(result);
return result;
}
static char
module___doc__[] =
"Association between an object, a context object, and a dictionary.\n\
\n\
The context object and dictionary give additional context information\n\
associated with a reference to the basic object. The wrapper objects\n\
act as proxies for the original object.";
static PyMethodDef
module_functions[] = {
{"getProxiedObject", wrapper_getobject, METH_O, getobject__doc__},
{"setProxiedObject", wrapper_setobject, METH_VARARGS, setobject__doc__},
{"isProxy", wrapper_isProxy, METH_VARARGS, isProxy__doc__},
{"sameProxiedObjects", wrapper_sameProxiedObjects, METH_VARARGS,
sameProxiedObjects__doc__},
{"queryProxy", wrapper_queryProxy, METH_VARARGS, queryProxy__doc__},
{"queryInnerProxy", wrapper_queryInnerProxy, METH_VARARGS,
queryInnerProxy__doc__},
{"removeAllProxies", wrapper_removeAllProxies, METH_O,
removeAllProxies__doc__},
{NULL}
};
void
init_zope_proxy_proxy(void)
{
PyObject *m = Py_InitModule3("_zope_proxy_proxy",
module_functions, module___doc__);
if (m == NULL)
return;
if (empty_tuple == NULL)
empty_tuple = PyTuple_New(0);
ProxyType.tp_free = _PyObject_GC_Del;
if (PyType_Ready(&ProxyType) < 0)
return;
Py_INCREF(&ProxyType);
PyModule_AddObject(m, "ProxyBase", (PyObject *)&ProxyType);
if (api_object == NULL) {
api_object = PyCObject_FromVoidPtr(&wrapper_capi, NULL);
if (api_object == NULL)
return;
}
Py_INCREF(api_object);
PyModule_AddObject(m, "_CAPI", api_object);
}
##############################################################################
#
# 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.
#
##############################################################################
"""Decorator support
Decorators are proxies that are mostly transparent but that may provide
additional features.
"""
__docformat__ = "reStructuredText"
from zope.proxy import getProxiedObject, ProxyBase
from zope.interface.declarations import ObjectSpecificationDescriptor
from zope.interface.declarations import getObjectSpecification
from zope.interface.declarations import ObjectSpecification
from zope.interface import providedBy
class DecoratorSpecificationDescriptor(ObjectSpecificationDescriptor):
"""Support for interface declarations on decorators
>>> from zope.interface import *
>>> class I1(Interface):
... pass
>>> class I2(Interface):
... pass
>>> class I3(Interface):
... pass
>>> class I4(Interface):
... pass
>>> class D1(SpecificationDecoratorBase):
... implements(I1)
>>> class D2(SpecificationDecoratorBase):
... implements(I2)
>>> class X(object):
... implements(I3)
>>> x = X()
>>> directlyProvides(x, I4)
Interfaces of X are ordered with the directly-provided interfaces first
>>> [interface.getName() for interface in list(providedBy(x))]
['I4', 'I3']
When we decorate objects, what order should the interfaces come
in? One could argue that decorators are less specific, so they
should come last.
>>> [interface.getName() for interface in list(providedBy(D1(x)))]
['I4', 'I3', 'I1']
>>> [interface.getName() for interface in list(providedBy(D2(D1(x))))]
['I4', 'I3', 'I1', 'I2']
SpecificationDecorators also work with old-style classes:
>>> class X:
... implements(I3)
>>> x = X()
>>> directlyProvides(x, I4)
>>> [interface.getName() for interface in list(providedBy(x))]
['I4', 'I3']
>>> [interface.getName() for interface in list(providedBy(D1(x)))]
['I4', 'I3', 'I1']
>>> [interface.getName() for interface in list(providedBy(D2(D1(x))))]
['I4', 'I3', 'I1', 'I2']
"""
def __get__(self, inst, cls=None):
if inst is None:
return getObjectSpecification(cls)
else:
provided = providedBy(getProxiedObject(inst))
# Use type rather than __class__ because inst is a proxy and
# will return the proxied object's class.
cls = type(inst)
return ObjectSpecification(provided, cls)
def __set__(self, inst, value):
raise TypeError("Can't set __providedBy__ on a decorated object")
class SpecificationDecoratorBase(ProxyBase):
"""Base class for a proxy that provides additional interfaces."""
__providedBy__ = DecoratorSpecificationDescriptor()
##############################################################################
#
# 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
#
##############################################################################
"""Proxy-related interfaces.
"""
from zope.interface import Interface
class IProxyIntrospection(Interface):
"""Provides methods for indentifying proxies and extracting proxied objects
"""
def isProxy(obj, proxytype=None):
"""Check whether the given object is a proxy
If proxytype is not None, checkes whether the object is
proxied by the given proxytype.
"""
def sameProxiedObjects(ob1, ob2):
"""Check whether ob1 and ob2 are the same or proxies of the same object
"""
def getProxiedObject(obj):
"""Get the proxied Object
If the object isn't proxied, then just return the object.
"""
def setProxiedObject(ob1, ob2):
"""Set the underlying object for ob1 to ob2, returning the old object.
Raises TypeError if ob1 is not a proxy.
"""
def removeAllProxies(obj):
"""Get the proxied object with no proxies
If obj is not a proxied object, return obj.
The returned object has no proxies.
"""
def queryProxy(obj, proxytype, default=None):
"""Look for a proxy of the given type around the object
If no such proxy can be found, return the default.
"""
def queryInnerProxy(obj, proxytype, default=None):
"""Look for the inner-most proxy of the given type around the object
If no such proxy can be found, return the default.
If there is such a proxy, return the inner-most one.
"""
#ifndef _proxy_H_
#define _proxy_H_ 1
#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
typedef int Py_ssize_t;
#define PY_SSIZE_T_MAX INT_MAX
#define PY_SSIZE_T_MIN INT_MIN
typedef Py_ssize_t (*lenfunc)(PyObject *);
typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t);
typedef PyObject *(*ssizessizeargfunc)(PyObject *, Py_ssize_t, Py_ssize_t);
typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *);
typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *);
#endif
typedef struct {
PyObject_HEAD
PyObject *proxy_object;
} ProxyObject;
#define Proxy_GET_OBJECT(ob) (((ProxyObject *)(ob))->proxy_object)
typedef struct {
PyTypeObject *proxytype;
int (*check)(PyObject *obj);
PyObject *(*create)(PyObject *obj);
PyObject *(*getobject)(PyObject *proxy);
} ProxyInterface;
#ifndef PROXY_MODULE
/* These are only defined in the public interface, and are not
* available within the module implementation. There we use the
* classic Python/C API only.
*/
static ProxyInterface *_proxy_api = NULL;
static int
Proxy_Import(void)
{
if (_proxy_api == NULL) {
PyObject *m = PyImport_ImportModule("zope.proxy");
if (m != NULL) {
PyObject *tmp = PyObject_GetAttrString(m, "_CAPI");
if (tmp != NULL) {
if (PyCObject_Check(tmp))
_proxy_api = (ProxyInterface *)
PyCObject_AsVoidPtr(tmp);
Py_DECREF(tmp);
}
}
}
return (_proxy_api == NULL) ? -1 : 0;
}
#define ProxyType (*_proxy_api->proxytype)
#define Proxy_Check(obj) (_proxy_api->check((obj)))
#define Proxy_CheckExact(obj) ((obj)->ob_type == ProxyType)
#define Proxy_New(obj) (_proxy_api->create((obj)))
#define Proxy_GetObject(proxy) (_proxy_api->getobject((proxy)))
#endif /* PROXY_MODULE */
#endif /* _proxy_H_ */
#
# This file is necessary to make this directory a package.
##############################################################################
#
# 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.
#
##############################################################################
"""Test Harness
"""
from doctest import DocTestSuite
def test_suite():
suite = DocTestSuite()
suite.addTest(DocTestSuite('zope.proxy.decorator'))
return suite
##############################################################################
#
# 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.
#
##############################################################################
"""Test base proxy class.
"""
from doctest import DocTestSuite
import pickle
import sys
import unittest
from zope.proxy import ProxyBase
import zope.proxy
class Thing:
"""This class is expected to be a classic class."""
class Comparable(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
if hasattr(other, "value"):
other = other.value
return self.value == other
def __ne__(self, other):
return not self.__eq__(other)
def __lt__(self, other):
if hasattr(other, "value"):
other = other.value
return self.value < other
def __ge__(self, other):
return not self.__lt__(other)
def __le__(self, other):
if hasattr(other, "value"):
other = other.value
return self.value <= other
def __gt__(self, other):
return not self.__le__(other)
def __repr__(self):
return "<Comparable: %r>" % self.value
class ProxyTestCase(unittest.TestCase):
proxy_class = ProxyBase
def setUp(self):
self.x = Thing()
self.p = self.new_proxy(self.x)
def new_proxy(self, o):
return self.proxy_class(o)
def test_constructor(self):
o = object()
self.assertRaises(TypeError, self.proxy_class, o, o)
self.assertRaises(TypeError, self.proxy_class, o, key='value')
self.assertRaises(TypeError, self.proxy_class, key='value')
def test_subclass_constructor(self):
class MyProxy(self.proxy_class):
def __new__(cls, *args, **kwds):
return super(MyProxy, cls).__new__(cls, *args, **kwds)
def __init__(self, *args, **kwds):
super(MyProxy, self).__init__(*args, **kwds)
o1 = object()
o2 = object()
o = MyProxy((o1, o2))
self.assertEquals(o1, o[0])
self.assertEquals(o2, o[1])
self.assertRaises(TypeError, MyProxy, o1, o2)
self.assertRaises(TypeError, MyProxy, o1, key='value')
self.assertRaises(TypeError, MyProxy, key='value')
# Check that are passed to __init__() overrides what's passed
# to __new__().
class MyProxy2(self.proxy_class):
def __new__(cls, *args, **kwds):
return super(MyProxy2, cls).__new__(cls, 'value')
p = MyProxy2('splat!')
self.assertEquals(list(p), list('splat!'))
class MyProxy3(MyProxy2):
def __init__(self, arg):
if list(self) != list('value'):
raise AssertionError("list(self) != list('value')")
super(MyProxy3, self).__init__('another')
p = MyProxy3('notused')
self.assertEquals(list(p), list('another'))
def test_proxy_attributes(self):
o = Thing()
o.foo = 1
w = self.new_proxy(o)
self.assert_(w.foo == 1)
def test___class__(self):
o = object()
w = self.new_proxy(o)
self.assert_(w.__class__ is o.__class__)
def test_pickle_prevention(self):
w = self.new_proxy(Thing())
self.assertRaises(pickle.PicklingError,
pickle.dumps, w)
def test_proxy_equality(self):
w = self.new_proxy('foo')
self.assertEquals(w, 'foo')
o1 = Comparable(1)
o2 = Comparable(1.0)
o3 = Comparable("splat!")
w1 = self.new_proxy(o1)
w2 = self.new_proxy(o2)
w3 = self.new_proxy(o3)
self.assertEquals(o1, w1)
self.assertEquals(o1, w2)
self.assertEquals(o2, w1)
self.assertEquals(w1, o2)
self.assertEquals(w2, o1)
self.assertNotEquals(o3, w1)
self.assertNotEquals(w1, o3)
self.assertNotEquals(w3, o1)
self.assertNotEquals(o1, w3)
def test_proxy_ordering_lt(self):
o1 = Comparable(1)
o2 = Comparable(2.0)
w1 = self.new_proxy(o1)
w2 = self.new_proxy(o2)
self.assert_(w1 < w2)
self.assert_(w1 <= w2)
self.assert_(o1 < w2)
self.assert_(o1 <= w2)
self.assert_(w1 < o2)
self.assert_(w2 <= o2)
def test_proxy_callable(self):
w = self.new_proxy({}.get)
self.assert_(callable(w))
def test_proxy_item_protocol(self):
w = self.new_proxy({})
self.assertRaises(KeyError, lambda: w[1])
w[1] = 'a'
self.assertEquals(w[1], 'a')
del w[1]
self.assertRaises(KeyError, lambda: w[1])
def del_w_1():
del w[1]
self.assertRaises(KeyError, del_w_1)
def test_wrapped_iterable(self):
a = [1, 2, 3]
b = []
for x in self.new_proxy(a):
b.append(x)
self.assertEquals(a, b)
def test_iteration_over_proxy(self):
# Wrap an iterator before starting iteration.
# PyObject_GetIter() will still be called on the proxy.
a = [1, 2, 3]
b = []
for x in self.new_proxy(iter(a)):
b.append(x)
self.assertEquals(a, b)
t = tuple(self.new_proxy(iter(a)))
self.assertEquals(t, (1, 2, 3))
def test_iteration_using_proxy(self):
# Wrap an iterator within the iteration protocol, expecting it
# still to work. PyObject_GetIter() will not be called on the
# proxy, so the tp_iter slot won't unwrap it.
class Iterable(object):
def __init__(self, test, data):
self.test = test
self.data = data
def __iter__(self):
return self.test.new_proxy(iter(self.data))
a = [1, 2, 3]
b = []
for x in Iterable(self, a):
b.append(x)
self.assertEquals(a, b)
def test_bool_wrapped_None(self):
w = self.new_proxy(None)
self.assertEquals(not w, 1)
# Numeric ops.
unops = [
"-x", "+x", "abs(x)", "~x",
"int(x)", "long(x)", "float(x)",
]
def test_unops(self):
P = self.new_proxy
for expr in self.unops:
x = 1
y = eval(expr)
x = P(1)
z = eval(expr)
self.assertEqual(z, y,
"x=%r; expr=%r" % (x, expr))
def test_odd_unops(self):
# unops that don't return a proxy
P = self.new_proxy
for func in hex, oct, lambda x: not x:
self.assertEqual(func(P(100)), func(100))
binops = [
"x+y", "x-y", "x*y", "x/y", "divmod(x, y)", "x**y", "x//y",
"x<<y", "x>>y", "x&y", "x|y", "x^y",
]
def test_binops(self):
P = self.new_proxy
for expr in self.binops:
first = 1
for x in [1, P(1)]:
for y in [2, P(2)]:
if first:
z = eval(expr)
first = 0
else:
self.assertEqual(eval(expr), z,
"x=%r; y=%r; expr=%r" % (x, y, expr))
def test_inplace(self):
# TODO: should test all inplace operators...
P = self.new_proxy
pa = P(1)
pa += 2
self.assertEqual(pa, 3)
a = [1, 2, 3]
pa = qa = P(a)
pa += [4, 5, 6]
self.failUnless(pa is qa)
self.assertEqual(a, [1, 2, 3, 4, 5, 6])
pa = P(2)
pa **= 2
self.assertEqual(pa, 4)
def test_coerce(self):
P = self.new_proxy
# Before 2.3, coerce() of two proxies returns them unchanged
fixed_coerce = sys.version_info >= (2, 3, 0)
x = P(1)
y = P(2)
a, b = coerce(x, y)
self.failUnless(a is x and b is y)
x = P(1)
y = P(2.1)
a, b = coerce(x, y)
self.failUnless(a == 1.0)
self.failUnless(b is y)
if fixed_coerce:
self.failUnless(a.__class__ is float, a.__class__)
x = P(1.1)
y = P(2)
a, b = coerce(x, y)
self.failUnless(a is x)
self.failUnless(b == 2.0)
if fixed_coerce:
self.failUnless(b.__class__ is float, b.__class__)
x = P(1)
y = 2
a, b = coerce(x, y)
self.failUnless(a is x)
self.failUnless(b is y)
x = P(1)
y = 2.1
a, b = coerce(x, y)
self.failUnless(a.__class__ is float, a.__class__)
self.failUnless(b is y)
x = P(1.1)
y = 2
a, b = coerce(x, y)
self.failUnless(a is x)
self.failUnless(b.__class__ is float, b.__class__)
x = 1
y = P(2)
a, b = coerce(x, y)
self.failUnless(a is x)
self.failUnless(b is y)
x = 1.1
y = P(2)
a, b = coerce(x, y)
self.failUnless(a is x)
self.failUnless(b.__class__ is float, b.__class__)
x = 1
y = P(2.1)
a, b = coerce(x, y)
self.failUnless(a.__class__ is float, a.__class__)
self.failUnless(b is y)
def test_getslice(self):
# Lists have special slicing bahvior.
pList = self.new_proxy([1, 2])
self.assertEqual(pList[-1:], [2])
self.assertEqual(pList[-2:], [1, 2])
self.assertEqual(pList[-3:], [1, 2])
# Tuples also have special slicing behavior.
pTuple = self.new_proxy((1, 2))
self.assertEqual(pTuple[-1:], (2,))
self.assertEqual(pTuple[-2:], (1, 2))
self.assertEqual(pTuple[-3:], (1, 2))
# This behavior should be true for all list- and tuple-derived classes.
class DerivedList(list):
def __getslice__(self, start, end, step=None):
return (start, end, step)
pList = self.new_proxy(DerivedList([1, 2]))
self.assertEqual(pList[-1:], [2])
self.assertEqual(pList[-2:], [1, 2])
self.assertEqual(pList[-3:], [1, 2])
# Another sort of sequence has a different slicing interpretation.
class Slicer(object):
def __len__(self):
return 2
def __getslice__(self, start, end, step=None):
return (start, end, step)
pSlicer = self.new_proxy(Slicer())
self.assertEqual(pSlicer[-1:][0], 1)
self.assertEqual(pSlicer[-2:][0], 0)
# Note that for non-lists and non-tuples the slice is computed
# differently
self.assertEqual(pSlicer[-3:][0], 1)
def test_setslice(self):
# Lists have special slicing bahvior for assignment as well.
pList = self.new_proxy([1, 2])
pList[-1:] = [3, 4]
self.assertEqual(pList, [1, 3, 4])
pList = self.new_proxy([1, 2])
pList[-2:] = [3, 4]
self.assertEqual(pList, [3, 4])
pList = self.new_proxy([1, 2])
pList[-3:] = [3, 4]
self.assertEqual(pList, [3, 4])
# This behavior should be true for all list-derived classes.
class DerivedList(list):
pass
pList = self.new_proxy(DerivedList([1, 2]))
pList[-1:] = [3, 4]
self.assertEqual(pList, [1, 3, 4])
pList = self.new_proxy(DerivedList([1, 2]))
pList[-2:] = [3, 4]
self.assertEqual(pList, [3, 4])
pList = self.new_proxy(DerivedList([1, 2]))
pList[-3:] = [3, 4]
self.assertEqual(pList, [3, 4])
def test_isProxy():
"""
>>> from zope.proxy import ProxyBase, isProxy
>>> class P1(ProxyBase):
... pass
>>> class P2(ProxyBase):
... pass
>>> class C(object):
... pass
>>> c = C()
>>> int(isProxy(c))
0
>>> p = P1(c)
>>> int(isProxy(p))
1
>>> int(isProxy(p, P1))
1
>>> int(isProxy(p, P2))
0
>>> p = P2(p)
>>> int(isProxy(p, P1))
1
>>> int(isProxy(p, P2))
1
"""
def test_getProxiedObject():
"""
>>> from zope.proxy import ProxyBase, getProxiedObject
>>> class C(object):
... pass
>>> c = C()
>>> int(getProxiedObject(c) is c)
1
>>> p = ProxyBase(c)
>>> int(getProxiedObject(p) is c)
1
>>> p2 = ProxyBase(p)
>>> int(getProxiedObject(p2) is p)
1
"""
def test_ProxyIterator():
"""
>>> from zope.proxy import ProxyBase, ProxyIterator
>>> class C(object):
... pass
>>> c = C()
>>> p1 = ProxyBase(c)
>>> class P(ProxyBase):
... pass
>>> p2 = P(p1)
>>> p3 = ProxyBase(p2)
>>> list(ProxyIterator(p3)) == [p3, p2, p1, c]
1
"""
def test_removeAllProxies():
"""
>>> from zope.proxy import ProxyBase, removeAllProxies
>>> class C(object):
... pass
>>> c = C()
>>> int(removeAllProxies(c) is c)
1
>>> p = ProxyBase(c)
>>> int(removeAllProxies(p) is c)
1
>>> p2 = ProxyBase(p)
>>> int(removeAllProxies(p2) is c)
1
"""
def test_queryProxy():
"""
>>> from zope.proxy import ProxyBase, queryProxy
>>> class P1(ProxyBase):
... pass
>>> class P2(ProxyBase):
... pass
>>> class C(object):
... pass
>>> c = C()
>>> queryProxy(c, P1)
>>> queryProxy(c, P1, 42)
42
>>> p1 = P1(c)
>>> int(queryProxy(p1, P1) is p1)
1
>>> queryProxy(c, P2)
>>> queryProxy(c, P2, 42)
42
>>> p2 = P2(p1)
>>> int(queryProxy(p2, P1) is p1)
1
>>> int(queryProxy(p2, P2) is p2)
1
>>> int(queryProxy(p2, ProxyBase) is p2)
1
"""
def test_queryInnerProxy():
"""
>>> from zope.proxy import ProxyBase, queryProxy, queryInnerProxy
>>> class P1(ProxyBase):
... pass
>>> class P2(ProxyBase):
... pass
>>> class C(object):
... pass
>>> c = C()
>>> queryInnerProxy(c, P1)
>>> queryInnerProxy(c, P1, 42)
42
>>> p1 = P1(c)
>>> int(queryProxy(p1, P1) is p1)
1
>>> queryInnerProxy(c, P2)
>>> queryInnerProxy(c, P2, 42)
42
>>> p2 = P2(p1)
>>> int(queryInnerProxy(p2, P1) is p1)
1
>>> int(queryInnerProxy(p2, P2) is p2)
1
>>> int(queryInnerProxy(p2, ProxyBase) is p1)
1
>>> p3 = P1(p2)
>>> int(queryProxy(p3, P1) is p3)
1
>>> int(queryInnerProxy(p3, P1) is p1)
1
>>> int(queryInnerProxy(p3, P2) is p2)
1
"""
def test_sameProxiedObjects():
"""
>>> from zope.proxy import ProxyBase, sameProxiedObjects
>>> class C(object):
... pass
>>> c1 = C()
>>> c2 = C()
>>> int(sameProxiedObjects(c1, c1))
1
>>> int(sameProxiedObjects(ProxyBase(c1), c1))
1
>>> int(sameProxiedObjects(ProxyBase(c1), ProxyBase(c1)))
1
>>> int(sameProxiedObjects(ProxyBase(ProxyBase(c1)), c1))
1
>>> int(sameProxiedObjects(c1, ProxyBase(c1)))
1
>>> int(sameProxiedObjects(c1, ProxyBase(ProxyBase(c1))))
1
>>> int(sameProxiedObjects(c1, c2))
0
>>> int(sameProxiedObjects(ProxyBase(c1), c2))
0
>>> int(sameProxiedObjects(ProxyBase(c1), ProxyBase(c2)))
0
>>> int(sameProxiedObjects(ProxyBase(ProxyBase(c1)), c2))
0
>>> int(sameProxiedObjects(c1, ProxyBase(c2)))
0
>>> int(sameProxiedObjects(c1, ProxyBase(ProxyBase(c2))))
0
"""
def test_subclassing_proxies():
"""You can subclass ProxyBase
If you subclass a proxy, instances of the subclass have access to
data defined in the class, including descriptors.
Your subclass instances don't get instance dictionaries, but they
can have slots.
>>> class MyProxy(ProxyBase):
... __slots__ = 'x', 'y'
...
... def f(self):
... return self.x
>>> l = [1, 2, 3]
>>> p = MyProxy(l)
I can use attributes defined by the class, including slots:
>>> p.x = 'x'
>>> p.x
'x'
>>> p.f()
'x'
I can also use attributes of the proxied object:
>>> p
[1, 2, 3]
>>> p.pop()
3
>>> p
[1, 2]
"""
def test_get_descriptors_in_proxy_class():
"""
A non-data descriptor in a proxy class doesn't hide an attribute on
a proxied object or prevent writing the attribute.
>>> class ReadDescr(object):
... def __get__(self, i, c):
... return 'read'
>>> class MyProxy(ProxyBase):
... __slots__ = ()
...
... z = ReadDescr()
... q = ReadDescr()
>>> class MyOb:
... q = 1
>>> o = MyOb()
>>> p = MyProxy(o)
>>> p.q
1
>>> p.z
'read'
>>> p.z = 1
>>> o.z, p.z
(1, 1)
"""
def test_non_overridable():
"""
Normally, methods defined in proxies are overridden by
methods of proxied objects. This applies to all non-data
descriptors. The non_overridable function can be used to
convert a non-data descriptor to a data descriptor that disallows
writes. This function can be used as a decorator to make functions
defined in proxy classes take precedence over functions defined
in proxied objects.
>>> class MyProxy(ProxyBase):
... __slots__ = ()
...
... @zope.proxy.non_overridable
... def foo(self):
... return 'MyProxy foo'
>>> class MyOb:
... def foo(self):
... return 'MyOb foo'
>>> o = MyOb()
>>> p = MyProxy(o)
>>> p.foo()
'MyProxy foo'
"""
def test_setProxiedObject():
"""
>>> from zope.proxy import ProxyBase
>>> from zope.proxy import setProxiedObject, getProxiedObject
>>> class C(object):
... pass
>>> c1 = C()
>>> c2 = C()
>>> p = ProxyBase(c1)
`setProxiedObject()` allows us to change the object a proxy refers to,
returning the previous referent:
>>> old = setProxiedObject(p, c2)
>>> old is c1
True
>>> getProxiedObject(p) is c2
True
The first argument to `setProxiedObject()` must be a proxy; other objects
cause it to raise an exception:
>>> try:
... setProxiedObject(c1, None)
... except TypeError:
... print "TypeError raised"
... else:
... print "Expected TypeError not raised"
TypeError raised
"""
def test_suite():
suite = unittest.makeSuite(ProxyTestCase)
suite.addTest(DocTestSuite())
return suite
if __name__ == "__main__":
runner = unittest.TextTestRunner(sys.stdout)
result = runner.run(test_suite())
newerrs = len(result.errors) + len(result.failures)
sys.exit(newerrs and 1 or 0)
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
##############################################################################
#
# Copyright (c) 2006 Zope Corporation 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.
#
##############################################################################
# This package is developed by the Zope Toolkit project, documented here:
# http://docs.zope.org/zopetoolkit
# When developing and releasing this package, please follow the documented
# Zope Toolkit policies as described by this documentation.
##############################################################################
"""Setup for zope.container package"""
import os
from setuptools import setup, find_packages, Extension
def read(*rnames):
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
setup(name='zope.container',
version='3.11.2',
author='Zope Corporation and Contributors',
author_email='zope-dev@zope.org',
description='Zope Container',
long_description=(
read('README.txt')
+ '\n\n' +
'.. contents::\n'
+ '\n\n' +
read('src', 'zope', 'container', 'constraints.txt')
+ '\n\n' +
read('CHANGES.txt')
),
keywords = "zope container",
classifiers = [
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: Zope Public License',
'Programming Language :: Python',
'Natural Language :: English',
'Operating System :: OS Independent',
'Topic :: Internet :: WWW/HTTP',
'Framework :: Zope3'],
url='http://pypi.python.org/pypi/zope.container',
license='ZPL 2.1',
packages=find_packages('src'),
package_dir = {'': 'src'},
namespace_packages=['zope'],
ext_modules=[Extension("zope.container._zope_container_contained",
[os.path.join("src", "zope", "container",
"_zope_container_contained.c")
], include_dirs=['include']),
],
extras_require=dict(
test=[
'zope.configuration',
'zope.security',
'zope.testing',
]),
install_requires=['setuptools',
'zope.interface',
'zope.dottedname',
'zope.schema',
'zope.component',
'zope.event',
'zope.location>=3.5.4',
'zope.security',
'zope.lifecycleevent>=3.5.2',
'zope.i18nmessageid',
'zope.filerepresentation',
'zope.size',
'zope.traversing',
'zope.publisher',
'zope.broken',
'ZODB3',
],
include_package_data = True,
zip_safe = False,
)
Metadata-Version: 1.0
Name: zope.container
Version: 3.11.2
Summary: Zope Container
Home-page: http://pypi.python.org/pypi/zope.container
Author: Zope Corporation and Contributors
Author-email: zope-dev@zope.org
License: ZPL 2.1
Description: This package define interfaces of container components, and provides
container implementations such as a BTreeContainer and
OrderedContainer, as well as the base class used by ``zope.site.folder``
for the Folder implementation.
.. contents::
=========================
Containment constraints
=========================
Containment constraints allow us to express restrictions on the types
of items that can be placed in containers or on the types of
containers an item can be placed in. We express these constraints in
interfaces. Let's define some container and item interfaces:
>>> from zope.container.interfaces import IContainer
>>> from zope.location.interfaces import IContained
>>> from zope.container.constraints import containers, contains
>>> class IBuddyFolder(IContainer):
... contains('.IBuddy')
In this example, we used the contains function to declare that objects
that provide IBuddyFolder can only contain items that provide IBuddy.
Note that we used a string containing a dotted name for the IBuddy
interface. This is because IBuddy hasn't been defined yet. When we
define IBuddy, we can use IBuddyFolder directly:
>>> class IBuddy(IContained):
... containers(IBuddyFolder)
Now, with these interfaces in place, we can define Buddy and
BuddyFolder classes and verify that we can put buddies in buddy
folders:
>>> from zope import interface
>>> class Buddy:
... interface.implements(IBuddy)
>>> class BuddyFolder:
... interface.implements(IBuddyFolder)
>>> from zope.container.constraints import checkObject, checkFactory
>>> from zope.component.factory import Factory
>>> checkObject(BuddyFolder(), 'x', Buddy())
>>> checkFactory(BuddyFolder(), 'x', Factory(Buddy))
True
If we try to use other containers or folders, we'll get errors:
>>> class Container:
... interface.implements(IContainer)
>>> class Contained:
... interface.implements(IContained)
>>> checkObject(Container(), 'x', Buddy())
... # doctest: +ELLIPSIS
Traceback (most recent call last):
InvalidContainerType: ...
>>> checkFactory(Container(), 'x', Factory(Buddy))
False
>>> checkObject(BuddyFolder(), 'x', Contained())
... # doctest: +ELLIPSIS
Traceback (most recent call last):
InvalidItemType: ...
>>> checkFactory(BuddyFolder(), 'x', Factory(Contained))
False
In the example, we defined the container first and then the items. We
could have defined these in the opposite order:
>>> class IContact(IContained):
... containers('.IContacts')
>>> class IContacts(IContainer):
... contains(IContact)
>>> class Contact:
... interface.implements(IContact)
>>> class Contacts:
... interface.implements(IContacts)
>>> checkObject(Contacts(), 'x', Contact())
>>> checkFactory(Contacts(), 'x', Factory(Contact))
True
>>> checkObject(Contacts(), 'x', Buddy())
... # doctest: +ELLIPSIS
Traceback (most recent call last):
InvalidItemType: ...
>>> checkFactory(Contacts(), 'x', Factory(Buddy))
False
=======
CHANGES
=======
3.11.2 (2010-09-25)
-------------------
- Added not declared, but needed test dependency on `zope.testing`.
3.11.1 (2010-04-30)
-------------------
- Prefer the standard libraries doctest module to the one from zope.testing.
- Added compatibility with ZODB3 3.10 by importing the IBroken interface from
it directly. Once we can rely on the new ZODB3 version exclusively, we can
remove the dependency onto the zope.broken distribution.
- Never fail if the suggested name is in a wrong type (#227617)
- ``checkName`` first checks the parameter type before the emptiness.
3.11.0 (2009-12-31)
-------------------
- Copy two trivial classes from zope.cachedescriptors into this package, which
allows us to remove that dependency. We didn't actually use any caching
properties as the dependency suggested.
3.10.1 (2009-12-29)
-------------------
- Moved zope.copypastemove related tests into that package.
- Removed no longer used zcml prefix from the configure file.
- Stop importing DocTestSuite from zope.testing.doctestunit. Fixes
compatibility problems with zope.testing 3.8.4.
3.10.0 (2009-12-15)
-------------------
- Break testing dependency on zope.app.testing.
- Break testing dependency on zope.app.dependable by moving the code and tests
into that package.
- Import ISite from zope.component after it was moved there from
zope.location.
3.9.1 (2009-10-18)
------------------
- Rerelease 3.9.0 as it had a broken Windows 2.6 egg.
- Marked as part of the ZTK.
3.9.0 (2009-08-28)
------------------
- Previous releases should be versioned 3.9.0 as they are not pure bugfix
releases and worth a "feature" release, increasing feature version.
Packages that depend on any changes introduced in version 3.8.2 or 3.8.3
should depend on version 3.9 or greater.
3.8.3 (2009-08-27)
------------------
- Move IXMLRPCPublisher ZCML registrations for containers from
zope.app.publisher.xmlrpc to zope.container for now.
3.8.2 (2009-05-17)
------------------
- Rid ourselves of ``IContained`` interface. This interface was moved
to ``zope.location.interfaces``. A b/w compat import still exists
to keep old code running. Depend on ``zope.location``>=3.5.4.
- Rid ourselves of the implementations of ``IObjectMovedEvent``,
``IObjectAddedEvent``, ``IObjectRemovedEvent`` interfaces and
``ObjectMovedEvent``, ``ObjectAddedEvent`` and
``ObjectRemovedEvent`` classes. B/w compat imports still exist.
All of these were moved to ``zope.lifecycleevent``. Depend on
``zope.lifecycleevent``>=3.5.2.
- Fix a bug in OrderedContainer where trying to set the value for a
key that already exists (duplication error) would actually delete the
key from the order, leaving a dangling reference.
- Partially break dependency on ``zope.traversing`` by disusing
zope.traversing.api.getPath in favor of using
ILocationInfo(object).getPath(). The rest of the runtime
dependencies on zope.traversing are currently interface
dependencies.
- Break runtime dependency on ``zope.app.dependable`` by using a zcml
condition on the qsubscriber ZCML directive that registers the
CheckDependency handler for IObjectRemovedEvent. If
``zope.app.dependable`` is not installed, this subscriber will never
be registered. ``zope.app.dependable`` is now a testing dependency
only.
3.8.1 (2009-04-03)
------------------
- Fixed misspackaged 3.8.0
3.8.0 (2009-04-03)
------------------
- Change configure.zcml to not depend on zope.app.component.
Fixes: https://bugs.launchpad.net/bugs/348329
- Moved the declaration of ``IOrderedContainer.updateOrder`` to a new, basic
``IOrdered`` interface and let ``IOrderedContainer`` inherit it. This allows
easier reuse of the declaration.
3.7.2 (2009-03-12)
------------------
- Fix: added missing ComponentLookupError, missing since revision 95429 and
missing in last release.
- Adapt to the move of IDefaultViewName from zope.component.interfaces
to zope.publisher.interfaces.
- Add support for reserved names for containers. To specify reserved
names for some container, you need to provide an adapter from the
container to the ``zope.container.interfaces.IReservedNames`` interface.
The default NameChooser is now also aware of reserved names.
3.7.1 (2009-02-05)
------------------
- Raise more "Pythonic" errors from ``__setitem__``, losing the dependency
on ``zope.exceptions``:
o ``zope.exceptions.DuplicationError`` -> ``KeyError``
o ``zope.exceptions.UserError`` -> ``ValueError``
- Moved import of ``IBroken`` interface to use new ``zope.broken``
package, which has no dependencies beyond ``zope.interface``.
- Made ``test`` part pull in the extra test requirements of this package.
- Split the ``z3c.recipe.compattest`` configuration out into a new file,
``compat.cfg``, to reduce the burden of doing standard unit tests.
- Stripped out bogus develop eggs from ``buildout.cfg``.
3.7.0 (2009-01-31)
------------------
- Split this package off ``zope.app.container``. This package is
intended to have far less dependencies than ``zope.app.container``.
- This package also contains the container implementation that
used to be in ``zope.app.folder``.
Keywords: zope container
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Zope Public License
Classifier: Programming Language :: Python
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Framework :: Zope3
CHANGES.txt
README.txt
bootstrap.py
buildout.cfg
compat.cfg
setup.py
include/persistent/DEPENDENCIES.cfg
include/persistent/README.txt
include/persistent/SETUP.cfg
include/persistent/TimeStamp.c
include/persistent/__init__.py
include/persistent/cPersistence.c
include/persistent/cPersistence.h
include/persistent/cPickleCache.c
include/persistent/dict.py
include/persistent/interfaces.py
include/persistent/list.py
include/persistent/mapping.py
include/persistent/ring.c
include/persistent/ring.h
include/persistent/wref.py
include/persistent/tests/__init__.py
include/persistent/tests/persistent.txt
include/persistent/tests/persistenttestbase.py
include/persistent/tests/testPersistent.py
include/persistent/tests/test_PickleCache.py
include/persistent/tests/test_list.py
include/persistent/tests/test_mapping.py
include/persistent/tests/test_overriding_attrs.py
include/persistent/tests/test_persistent.py
include/persistent/tests/test_pickle.py
include/persistent/tests/test_wref.py
include/zope.proxy/__init__.py
include/zope.proxy/_zope_proxy_proxy.c
include/zope.proxy/decorator.py
include/zope.proxy/interfaces.py
include/zope.proxy/proxy.h
include/zope.proxy/tests/__init__.py
include/zope.proxy/tests/test_decorator.py
include/zope.proxy/tests/test_proxy.py
src/zope/__init__.py
src/zope.container.egg-info/PKG-INFO
src/zope.container.egg-info/SOURCES.txt
src/zope.container.egg-info/dependency_links.txt
src/zope.container.egg-info/namespace_packages.txt
src/zope.container.egg-info/not-zip-safe
src/zope.container.egg-info/requires.txt
src/zope.container.egg-info/top_level.txt
src/zope/container/__init__.py
src/zope/container/_zope_container_contained.c
src/zope/container/_zope_proxy_proxy.c
src/zope/container/btree.py
src/zope/container/configure.zcml
src/zope/container/constraints.py
src/zope/container/constraints.txt
src/zope/container/contained.py
src/zope/container/dependency.py
src/zope/container/directory.py
src/zope/container/find.py
src/zope/container/folder.py
src/zope/container/i18n.py
src/zope/container/interfaces.py
src/zope/container/ordered.py
src/zope/container/sample.py
src/zope/container/size.py
src/zope/container/testing.py
src/zope/container/traversal.py
src/zope/container/tests/__init__.py
src/zope/container/tests/directory.txt
src/zope/container/tests/ftest_zcml_dependencies.zcml
src/zope/container/tests/test_btree.py
src/zope/container/tests/test_constraints.py
src/zope/container/tests/test_contained.py
src/zope/container/tests/test_containertraversable.py
src/zope/container/tests/test_containertraverser.py
src/zope/container/tests/test_dependencies.py
src/zope/container/tests/test_directory.py
src/zope/container/tests/test_find.py
src/zope/container/tests/test_folder.py
src/zope/container/tests/test_icontainer.py
src/zope/container/tests/test_ordered.py
src/zope/container/tests/test_size.py
\ No newline at end of file
setuptools
zope.interface
zope.dottedname
zope.schema
zope.component
zope.event
zope.location>=3.5.4
zope.security
zope.lifecycleevent>=3.5.2
zope.i18nmessageid
zope.filerepresentation
zope.size
zope.traversing
zope.publisher
zope.broken
ZODB3
[test]
zope.configuration
zope.security
zope.testing
\ No newline at end of file
__import__('pkg_resources').declare_namespace(__name__)
#
# This file is necessary to make this directory a package.
/*############################################################################
#
# Copyright (c) 2003 Zope Corporation 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 _ZOPE_CONTAINER_CONTAINED_C "$Id: _zope_container_contained.c 95350 2009-01-28 17:01:17Z wosc $\n"
/* Contained Proxy Base class
Contained proxies provide __parent__ and __name__ attributes for
objects without them.
There is something strange and, possibly cool, going on here, wrt
persistence. To reuse the base proxy implementation we don't treat
the proxied object as part of the persistent state of the proxy.
This means that the proxy still operates as a proxy even if it is a
ghost.
The proxy will only be unghostified if you need to access one of the
attributes provided by the proxy.
*/
#include "Python.h"
#include "persistent/cPersistence.h"
static PyObject *str_p_deactivate;
#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
typedef int Py_ssize_t;
#define PY_SSIZE_T_MAX INT_MAX
#define PY_SSIZE_T_MIN INT_MIN
typedef Py_ssize_t (*lenfunc)(PyObject *);
typedef PyObject *(*ssizeargfunc)(PyObject *, Py_ssize_t);
typedef PyObject *(*ssizessizeargfunc)(PyObject *, Py_ssize_t, Py_ssize_t);
typedef int(*ssizeobjargproc)(PyObject *, Py_ssize_t, PyObject *);
typedef int(*ssizessizeobjargproc)(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *);
#endif
typedef struct {
cPersistent_HEAD
PyObject *po_weaklist;
PyObject *proxy_object;
PyObject *__parent__;
PyObject *__name__;
} ProxyObject;
typedef struct {
PyTypeObject *proxytype;
int (*check)(PyObject *obj);
PyObject *(*create)(PyObject *obj);
PyObject *(*getobject)(PyObject *proxy);
} ProxyInterface;
#define OBJECT(O) ((PyObject*)(O))
#define Proxy_GET_OBJECT(ob) (((ProxyObject *)(ob))->proxy_object)
#define CLEAR(O) \
if (O) {PyObject *clr__tmp = O; O = NULL; Py_DECREF(clr__tmp); }
/* Supress inclusion of the original proxy.h */
#define _proxy_H_ 1
/* Incude the proxy C source */
#include "_zope_proxy_proxy.c"
#define SPECIAL(NAME) ( \
*(NAME) == '_' && \
(((NAME)[1] == 'p' && (NAME)[2] == '_') \
|| \
((NAME)[1] == '_' && ( \
strcmp((NAME), "__parent__") == 0 \
|| \
strcmp((NAME), "__name__") == 0 \
|| \
strcmp((NAME), "__getstate__") == 0 \
|| \
strcmp((NAME), "__setstate__") == 0 \
|| \
strcmp((NAME), "__getnewargs__") == 0 \
|| \
strcmp((NAME), "__reduce__") == 0 \
|| \
strcmp((NAME), "__reduce_ex__") == 0 \
)) \
))
static PyObject *
CP_getattro(PyObject *self, PyObject *name)
{
char *cname;
cname = PyString_AsString(name);
if (cname == NULL)
return NULL;
if (SPECIAL(cname))
/* delegate to persistent */
return cPersistenceCAPI->pertype->tp_getattro(self, name);
/* Use the wrapper version to delegate */
return wrap_getattro(self, name);
}
static int
CP_setattro(PyObject *self, PyObject *name, PyObject *v)
{
char *cname;
cname = PyString_AsString(name);
if (cname == NULL)
return -1;
if (SPECIAL(cname))
/* delegate to persistent */
return cPersistenceCAPI->pertype->tp_setattro(self, name, v);
/* Use the wrapper version to delegate */
return wrap_setattro(self, name, v);
}
static PyObject *
CP_getstate(ProxyObject *self)
{
return Py_BuildValue("OO",
self->__parent__ ? self->__parent__ : Py_None,
self->__name__ ? self->__name__ : Py_None
);
}
static PyObject *
CP_getnewargs(ProxyObject *self)
{
return Py_BuildValue("(O)", self->proxy_object);
}
static PyObject *
CP_setstate(ProxyObject *self, PyObject *state)
{
PyObject *parent, *name;
if(! PyArg_ParseTuple(state, "OO", &parent, &name))
return NULL;
CLEAR(self->__parent__);
CLEAR(self->__name__);
Py_INCREF(parent);
Py_INCREF(name);
self->__parent__ = parent;
self->__name__ = name;
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *
CP_reduce(ProxyObject *self)
{
PyObject *result;
if (! PER_USE(self))
return NULL;
result = Py_BuildValue("O(O)(OO)",
self->ob_type,
self->proxy_object,
self->__parent__ ? self->__parent__ : Py_None,
self->__name__ ? self->__name__ : Py_None
);
PER_ALLOW_DEACTIVATION(self);
return result;
}
static PyObject *
CP_reduce_ex(ProxyObject *self, PyObject *proto)
{
return CP_reduce(self);
}
static PyObject *
CP__p_deactivate(ProxyObject *self)
{
PyObject *result;
result = PyObject_CallMethodObjArgs(OBJECT(cPersistenceCAPI->pertype),
str_p_deactivate,
self, NULL);
if (result == NULL)
return NULL;
if (self->jar && self->oid && self->state == cPersistent_UPTODATE_STATE)
{
Py_XDECREF(self->__parent__);
self->__parent__ = NULL;
Py_XDECREF(self->__name__);
self->__name__ = NULL;
}
return result;
}
static PyMethodDef
CP_methods[] = {
{"__getstate__", (PyCFunction)CP_getstate, METH_NOARGS,
"Get the object state"},
{"__setstate__", (PyCFunction)CP_setstate, METH_O,
"Set the object state"},
{"__getnewargs__", (PyCFunction)CP_getnewargs, METH_NOARGS,
"Get the arguments that must be passed to __new__"},
{"__reduce__", (PyCFunction)CP_reduce, METH_NOARGS,
"Reduce the object to constituent parts."},
{"__reduce_ex__", (PyCFunction)CP_reduce_ex, METH_O,
"Reduce the object to constituent parts."},
{"_p_deactivate", (PyCFunction)CP__p_deactivate, METH_NOARGS,
"Deactivate the object."},
{NULL, NULL},
};
/* Code to access structure members by accessing attributes */
#include "structmember.h"
static PyMemberDef CP_members[] = {
{"__parent__", T_OBJECT, offsetof(ProxyObject, __parent__)},
{"__name__", T_OBJECT, offsetof(ProxyObject, __name__)},
{NULL} /* Sentinel */
};
static int
CP_traverse(ProxyObject *self, visitproc visit, void *arg)
{
if (cPersistenceCAPI->pertype->tp_traverse((PyObject *)self, visit, arg) < 0)
return -1;
if (self->proxy_object != NULL && visit(self->proxy_object, arg) < 0)
return -1;
if (self->__parent__ != NULL && visit(self->__parent__, arg) < 0)
return -1;
if (self->__name__ != NULL && visit(self->__name__, arg) < 0)
return -1;
return 0;
}
static int
CP_clear(ProxyObject *self)
{
/* Drop references that may have created reference
cycles. Immutable objects do not have to define this method
since they can never directly create reference cycles. Note
that the object must still be valid after calling this
method (don't just call Py_DECREF() on a reference). The
collector will call this method if it detects that this
object is involved in a reference cycle.
*/
if (cPersistenceCAPI->pertype->tp_clear != NULL)
cPersistenceCAPI->pertype->tp_clear((PyObject*)self);
CLEAR(self->proxy_object);
CLEAR(self->__parent__);
CLEAR(self->__name__);
return 0;
}
static void
CP_dealloc(ProxyObject *self)
{
if (self->po_weaklist != NULL)
PyObject_ClearWeakRefs((PyObject *)self);
CLEAR(self->proxy_object);
CLEAR(self->__parent__);
CLEAR(self->__name__);
cPersistenceCAPI->pertype->tp_dealloc((PyObject*)self);
}
#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
init_zope_container_contained(void)
{
PyObject *m;
str_p_deactivate = PyString_FromString("_p_deactivate");
if (str_p_deactivate == NULL)
return;
/* Try to fake out compiler nag function */
if (0) init_zope_proxy_proxy();
m = Py_InitModule3("_zope_container_contained",
module_functions, module___doc__);
if (m == NULL)
return;
if (empty_tuple == NULL)
empty_tuple = PyTuple_New(0);
/* Initialize the PyPersist_C_API and the type objects. */
cPersistenceCAPI = PyCObject_Import("persistent.cPersistence", "CAPI");
if (cPersistenceCAPI == NULL)
return;
ProxyType.tp_name = "zope.container.contained.ContainedProxyBase";
ProxyType.ob_type = &PyType_Type;
ProxyType.tp_base = cPersistenceCAPI->pertype;
ProxyType.tp_getattro = CP_getattro;
ProxyType.tp_setattro = CP_setattro;
ProxyType.tp_members = CP_members;
ProxyType.tp_methods = CP_methods;
ProxyType.tp_traverse = (traverseproc) CP_traverse;
ProxyType.tp_clear = (inquiry) CP_clear;
ProxyType.tp_dealloc = (destructor) CP_dealloc;
ProxyType.tp_weaklistoffset = offsetof(ProxyObject, po_weaklist);
if (PyType_Ready(&ProxyType) < 0)
return;
Py_INCREF(&ProxyType);
PyModule_AddObject(m, "ContainedProxyBase", (PyObject *)&ProxyType);
}
/*############################################################################
#
# Copyright (c) 2004 Zope Corporation 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.
#
############################################################################*/
/*
* This file is also used as a really extensive macro in
* ../app/container/_zope_container_contained.c. If you need to
* change this file, you need to "svn copy" it to ../container/.
*
* This approach is taken to allow the sources for the two packages
* to be compilable when the relative locations of these aren't
* related in the same way as they are in a checkout.
*
* This will be revisited in the future, but works for now.
*/
#include "Python.h"
#include "modsupport.h"
#define PROXY_MODULE
#include "zope.proxy/proxy.h"
static PyTypeObject ProxyType;
#define Proxy_Check(wrapper) (PyObject_TypeCheck((wrapper), &ProxyType))
static PyObject *
empty_tuple = NULL;
/*
* Slot methods.
*/
static PyObject *
wrap_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *result = NULL;
PyObject *object;
if (PyArg_UnpackTuple(args, "__new__", 1, 1, &object)) {
if (kwds != NULL && PyDict_Size(kwds) != 0) {
PyErr_SetString(PyExc_TypeError,
"proxy.__new__ does not accept keyword args");
return NULL;
}
result = PyType_GenericNew(type, args, kwds);
if (result != NULL) {
ProxyObject *wrapper = (ProxyObject *) result;
Py_INCREF(object);
wrapper->proxy_object = object;
}
}
return result;
}
static int
wrap_init(PyObject *self, PyObject *args, PyObject *kwds)
{
int result = -1;
PyObject *object;
if (PyArg_UnpackTuple(args, "__init__", 1, 1, &object)) {
ProxyObject *wrapper = (ProxyObject *)self;
if (kwds != NULL && PyDict_Size(kwds) != 0) {
PyErr_SetString(PyExc_TypeError,
"proxy.__init__ does not accept keyword args");
return -1;
}
/* If the object in this proxy is not the one we
* received in args, replace it with the new one.
*/
if (wrapper->proxy_object != object) {
PyObject *temp = wrapper->proxy_object;
Py_INCREF(object);
wrapper->proxy_object = object;
Py_DECREF(temp);
}
result = 0;
}
return result;
}
static int
wrap_traverse(PyObject *self, visitproc visit, void *arg)
{
PyObject *ob = Proxy_GET_OBJECT(self);
if (ob != NULL)
return visit(ob, arg);
else
return 0;
}
static int
wrap_clear(PyObject *self)
{
ProxyObject *proxy = (ProxyObject *)self;
PyObject *temp = proxy->proxy_object;
if (temp != NULL) {
proxy->proxy_object = NULL;
Py_DECREF(temp);
}
return 0;
}
static PyObject *
wrap_richcompare(PyObject* self, PyObject* other, int op)
{
if (Proxy_Check(self)) {
self = Proxy_GET_OBJECT(self);
}
else {
other = Proxy_GET_OBJECT(other);
}
return PyObject_RichCompare(self, other, op);
}
static PyObject *
wrap_iter(PyObject *self)
{
return PyObject_GetIter(Proxy_GET_OBJECT(self));
}
static PyObject *
wrap_iternext(PyObject *self)
{
return PyIter_Next(Proxy_GET_OBJECT(self));
}
static void
wrap_dealloc(PyObject *self)
{
(void) wrap_clear(self);
self->ob_type->tp_free(self);
}
/* A variant of _PyType_Lookup that doesn't look in ProxyType.
*
* If argument search_wrappertype is nonzero, we can look in WrapperType.
*/
PyObject *
WrapperType_Lookup(PyTypeObject *type, PyObject *name)
{
int i, n;
PyObject *mro, *res, *base, *dict;
/* Look in tp_dict of types in MRO */
mro = type->tp_mro;
/* If mro is NULL, the type is either not yet initialized
by PyType_Ready(), or already cleared by type_clear().
Either way the safest thing to do is to return NULL. */
if (mro == NULL)
return NULL;
assert(PyTuple_Check(mro));
n = PyTuple_GET_SIZE(mro)
- 1; /* We don't want to look at the last item, which is object. */
for (i = 0; i < n; i++) {
base = PyTuple_GET_ITEM(mro, i);
if (((PyTypeObject *)base) != &ProxyType) {
if (PyClass_Check(base))
dict = ((PyClassObject *)base)->cl_dict;
else {
assert(PyType_Check(base));
dict = ((PyTypeObject *)base)->tp_dict;
}
assert(dict && PyDict_Check(dict));
res = PyDict_GetItem(dict, name);
if (res != NULL)
return res;
}
}
return NULL;
}
static PyObject *
wrap_getattro(PyObject *self, PyObject *name)
{
PyObject *wrapped;
PyObject *descriptor;
PyObject *res = NULL;
char *name_as_string;
int maybe_special_name;
#ifdef Py_USING_UNICODE
/* The Unicode to string conversion is done here because the
existing tp_getattro slots expect a string object as name
and we wouldn't want to break those. */
if (PyUnicode_Check(name)) {
name = PyUnicode_AsEncodedString(name, NULL, NULL);
if (name == NULL)
return NULL;
}
else
#endif
if (!PyString_Check(name)){
PyErr_SetString(PyExc_TypeError, "attribute name must be string");
return NULL;
}
else
Py_INCREF(name);
name_as_string = PyString_AS_STRING(name);
wrapped = Proxy_GET_OBJECT(self);
if (wrapped == NULL) {
PyErr_Format(PyExc_RuntimeError,
"object is NULL; requested to get attribute '%s'",
name_as_string);
goto finally;
}
maybe_special_name = name_as_string[0] == '_' && name_as_string[1] == '_';
if (!(maybe_special_name && strcmp(name_as_string, "__class__") == 0)) {
descriptor = WrapperType_Lookup(self->ob_type, name);
if (descriptor != NULL) {
if (PyType_HasFeature(descriptor->ob_type, Py_TPFLAGS_HAVE_CLASS)
&& descriptor->ob_type->tp_descr_get != NULL) {
res = descriptor->ob_type->tp_descr_get(
descriptor,
self,
(PyObject *)self->ob_type);
} else {
Py_INCREF(descriptor);
res = descriptor;
}
goto finally;
}
}
res = PyObject_GetAttr(wrapped, name);
finally:
Py_DECREF(name);
return res;
}
static int
wrap_setattro(PyObject *self, PyObject *name, PyObject *value)
{
PyObject *wrapped;
PyObject *descriptor;
int res = -1;
#ifdef Py_USING_UNICODE
/* The Unicode to string conversion is done here because the
existing tp_setattro slots expect a string object as name
and we wouldn't want to break those. */
if (PyUnicode_Check(name)) {
name = PyUnicode_AsEncodedString(name, NULL, NULL);
if (name == NULL)
return -1;
}
else
#endif
if (!PyString_Check(name)){
PyErr_SetString(PyExc_TypeError, "attribute name must be string");
return -1;
}
else
Py_INCREF(name);
descriptor = WrapperType_Lookup(self->ob_type, name);
if (descriptor != NULL) {
if (PyType_HasFeature(descriptor->ob_type, Py_TPFLAGS_HAVE_CLASS) &&
descriptor->ob_type->tp_descr_set != NULL) {
res = descriptor->ob_type->tp_descr_set(descriptor, self, value);
} else {
PyErr_Format(PyExc_TypeError,
"Tried to set attribute '%s' on wrapper, but it is not"
" a data descriptor", PyString_AS_STRING(name));
}
goto finally;
}
wrapped = Proxy_GET_OBJECT(self);
if (wrapped == NULL) {
PyErr_Format(PyExc_RuntimeError,
"object is NULL; requested to set attribute '%s'",
PyString_AS_STRING(name));
goto finally;
}
res = PyObject_SetAttr(wrapped, name, value);
finally:
Py_DECREF(name);
return res;
}
static int
wrap_print(PyObject *wrapper, FILE *fp, int flags)
{
return PyObject_Print(Proxy_GET_OBJECT(wrapper), fp, flags);
}
static PyObject *
wrap_str(PyObject *wrapper) {
return PyObject_Str(Proxy_GET_OBJECT(wrapper));
}
static PyObject *
wrap_repr(PyObject *wrapper)
{
return PyObject_Repr(Proxy_GET_OBJECT(wrapper));
}
static int
wrap_compare(PyObject *wrapper, PyObject *v)
{
return PyObject_Compare(Proxy_GET_OBJECT(wrapper), v);
}
static long
wrap_hash(PyObject *self)
{
return PyObject_Hash(Proxy_GET_OBJECT(self));
}
static PyObject *
wrap_call(PyObject *self, PyObject *args, PyObject *kw)
{
if (kw)
return PyEval_CallObjectWithKeywords(Proxy_GET_OBJECT(self),
args, kw);
else
return PyObject_CallObject(Proxy_GET_OBJECT(self), args);
}
/*
* Number methods
*/
/*
* Number methods.
*/
static PyObject *
call_int(PyObject *self)
{
PyNumberMethods *nb = self->ob_type->tp_as_number;
if (nb == NULL || nb->nb_int == NULL) {
PyErr_SetString(PyExc_TypeError,
"object can't be converted to int");
return NULL;
}
return nb->nb_int(self);
}
static PyObject *
call_long(PyObject *self)
{
PyNumberMethods *nb = self->ob_type->tp_as_number;
if (nb == NULL || nb->nb_long == NULL) {
PyErr_SetString(PyExc_TypeError,
"object can't be converted to long");
return NULL;
}
return nb->nb_long(self);
}
static PyObject *
call_float(PyObject *self)
{
PyNumberMethods *nb = self->ob_type->tp_as_number;
if (nb == NULL || nb->nb_float== NULL) {
PyErr_SetString(PyExc_TypeError,
"object can't be converted to float");
return NULL;
}
return nb->nb_float(self);
}
static PyObject *
call_oct(PyObject *self)
{
PyNumberMethods *nb = self->ob_type->tp_as_number;
if (nb == NULL || nb->nb_oct== NULL) {
PyErr_SetString(PyExc_TypeError,
"object can't be converted to oct");
return NULL;
}
return nb->nb_oct(self);
}
static PyObject *
call_hex(PyObject *self)
{
PyNumberMethods *nb = self->ob_type->tp_as_number;
if (nb == NULL || nb->nb_hex == NULL) {
PyErr_SetString(PyExc_TypeError,
"object can't be converted to hex");
return NULL;
}
return nb->nb_hex(self);
}
static PyObject *
call_ipow(PyObject *self, PyObject *other)
{
/* PyNumber_InPlacePower has three args. How silly. :-) */
return PyNumber_InPlacePower(self, other, Py_None);
}
typedef PyObject *(*function1)(PyObject *);
static PyObject *
check1(ProxyObject *self, char *opname, function1 operation)
{
PyObject *result = NULL;
result = operation(Proxy_GET_OBJECT(self));
#if 0
if (result != NULL)
/* ??? create proxy for result? */
;
#endif
return result;
}
static PyObject *
check2(PyObject *self, PyObject *other,
char *opname, char *ropname, binaryfunc operation)
{
PyObject *result = NULL;
PyObject *object;
if (Proxy_Check(self)) {
object = Proxy_GET_OBJECT(self);
result = operation(object, other);
}
else if (Proxy_Check(other)) {
object = Proxy_GET_OBJECT(other);
result = operation(self, object);
}
else {
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
#if 0
if (result != NULL)
/* ??? create proxy for result? */
;
#endif
return result;
}
static PyObject *
check2i(ProxyObject *self, PyObject *other,
char *opname, binaryfunc operation)
{
PyObject *result = NULL;
PyObject *object = Proxy_GET_OBJECT(self);
result = operation(object, other);
if (result == object) {
/* If the operation was really carried out inplace,
don't create a new proxy, but use the old one. */
Py_INCREF(self);
Py_DECREF(object);
result = (PyObject *)self;
}
#if 0
else if (result != NULL)
/* ??? create proxy for result? */
;
#endif
return result;
}
#define UNOP(NAME, CALL) \
static PyObject *wrap_##NAME(PyObject *self) \
{ return check1((ProxyObject *)self, "__"#NAME"__", CALL); }
#define BINOP(NAME, CALL) \
static PyObject *wrap_##NAME(PyObject *self, PyObject *other) \
{ return check2(self, other, "__"#NAME"__", "__r"#NAME"__", CALL); }
#define INPLACE(NAME, CALL) \
static PyObject *wrap_i##NAME(PyObject *self, PyObject *other) \
{ return check2i((ProxyObject *)self, other, "__i"#NAME"__", CALL); }
BINOP(add, PyNumber_Add)
BINOP(sub, PyNumber_Subtract)
BINOP(mul, PyNumber_Multiply)
BINOP(div, PyNumber_Divide)
BINOP(mod, PyNumber_Remainder)
BINOP(divmod, PyNumber_Divmod)
static PyObject *
wrap_pow(PyObject *self, PyObject *other, PyObject *modulus)
{
PyObject *result = NULL;
PyObject *object;
if (Proxy_Check(self)) {
object = Proxy_GET_OBJECT(self);
result = PyNumber_Power(object, other, modulus);
}
else if (Proxy_Check(other)) {
object = Proxy_GET_OBJECT(other);
result = PyNumber_Power(self, object, modulus);
}
else if (modulus != NULL && Proxy_Check(modulus)) {
object = Proxy_GET_OBJECT(modulus);
result = PyNumber_Power(self, other, modulus);
}
else {
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
return result;
}
BINOP(lshift, PyNumber_Lshift)
BINOP(rshift, PyNumber_Rshift)
BINOP(and, PyNumber_And)
BINOP(xor, PyNumber_Xor)
BINOP(or, PyNumber_Or)
static int
wrap_coerce(PyObject **p_self, PyObject **p_other)
{
PyObject *self = *p_self;
PyObject *other = *p_other;
PyObject *object;
PyObject *left;
PyObject *right;
int r;
assert(Proxy_Check(self));
object = Proxy_GET_OBJECT(self);
left = object;
right = other;
r = PyNumber_CoerceEx(&left, &right);
if (r != 0)
return r;
/* Now left and right have been INCREF'ed. Any new value that
comes out is proxied; any unchanged value is left unchanged. */
if (left == object) {
/* Keep the old proxy */
Py_INCREF(self);
Py_DECREF(left);
left = self;
}
#if 0
else {
/* ??? create proxy for left? */
}
if (right != other) {
/* ??? create proxy for right? */
}
#endif
*p_self = left;
*p_other = right;
return 0;
}
UNOP(neg, PyNumber_Negative)
UNOP(pos, PyNumber_Positive)
UNOP(abs, PyNumber_Absolute)
UNOP(invert, PyNumber_Invert)
UNOP(int, call_int)
UNOP(long, call_long)
UNOP(float, call_float)
UNOP(oct, call_oct)
UNOP(hex, call_hex)
INPLACE(add, PyNumber_InPlaceAdd)
INPLACE(sub, PyNumber_InPlaceSubtract)
INPLACE(mul, PyNumber_InPlaceMultiply)
INPLACE(div, PyNumber_InPlaceDivide)
INPLACE(mod, PyNumber_InPlaceRemainder)
INPLACE(pow, call_ipow)
INPLACE(lshift, PyNumber_InPlaceLshift)
INPLACE(rshift, PyNumber_InPlaceRshift)
INPLACE(and, PyNumber_InPlaceAnd)
INPLACE(xor, PyNumber_InPlaceXor)
INPLACE(or, PyNumber_InPlaceOr)
BINOP(floordiv, PyNumber_FloorDivide)
BINOP(truediv, PyNumber_TrueDivide)
INPLACE(floordiv, PyNumber_InPlaceFloorDivide)
INPLACE(truediv, PyNumber_InPlaceTrueDivide)
static int
wrap_nonzero(PyObject *self)
{
return PyObject_IsTrue(Proxy_GET_OBJECT(self));
}
/*
* Sequence methods
*/
static Py_ssize_t
wrap_length(PyObject *self)
{
return PyObject_Length(Proxy_GET_OBJECT(self));
}
static PyObject *
wrap_slice(PyObject *self, Py_ssize_t start, Py_ssize_t end)
{
return PySequence_GetSlice(Proxy_GET_OBJECT(self), start, end);
}
static int
wrap_ass_slice(PyObject *self, Py_ssize_t i, Py_ssize_t j, PyObject *value)
{
return PySequence_SetSlice(Proxy_GET_OBJECT(self), i, j, value);
}
static int
wrap_contains(PyObject *self, PyObject *value)
{
return PySequence_Contains(Proxy_GET_OBJECT(self), value);
}
/*
* Mapping methods
*/
static PyObject *
wrap_getitem(PyObject *wrapper, PyObject *v) {
return PyObject_GetItem(Proxy_GET_OBJECT(wrapper), v);
}
static int
wrap_setitem(PyObject *self, PyObject *key, PyObject *value)
{
if (value == NULL)
return PyObject_DelItem(Proxy_GET_OBJECT(self), key);
else
return PyObject_SetItem(Proxy_GET_OBJECT(self), key, value);
}
/*
* Normal methods
*/
static char
reduce__doc__[] =
"__reduce__()\n"
"Raise an exception; this prevents proxies from being picklable by\n"
"default, even if the underlying object is picklable.";
static PyObject *
wrap_reduce(PyObject *self)
{
PyObject *pickle_error = NULL;
PyObject *pickle = PyImport_ImportModule("pickle");
if (pickle == NULL)
PyErr_Clear();
else {
pickle_error = PyObject_GetAttrString(pickle, "PicklingError");
if (pickle_error == NULL)
PyErr_Clear();
}
if (pickle_error == NULL) {
pickle_error = PyExc_RuntimeError;
Py_INCREF(pickle_error);
}
PyErr_SetString(pickle_error,
"proxy instances cannot be pickled");
Py_DECREF(pickle_error);
return NULL;
}
static PyNumberMethods
wrap_as_number = {
wrap_add, /* nb_add */
wrap_sub, /* nb_subtract */
wrap_mul, /* nb_multiply */
wrap_div, /* nb_divide */
wrap_mod, /* nb_remainder */
wrap_divmod, /* nb_divmod */
wrap_pow, /* nb_power */
wrap_neg, /* nb_negative */
wrap_pos, /* nb_positive */
wrap_abs, /* nb_absolute */
wrap_nonzero, /* nb_nonzero */
wrap_invert, /* nb_invert */
wrap_lshift, /* nb_lshift */
wrap_rshift, /* nb_rshift */
wrap_and, /* nb_and */
wrap_xor, /* nb_xor */
wrap_or, /* nb_or */
wrap_coerce, /* nb_coerce */
wrap_int, /* nb_int */
wrap_long, /* nb_long */
wrap_float, /* nb_float */
wrap_oct, /* nb_oct */
wrap_hex, /* nb_hex */
/* Added in release 2.0 */
/* These require the Py_TPFLAGS_HAVE_INPLACEOPS flag */
wrap_iadd, /* nb_inplace_add */
wrap_isub, /* nb_inplace_subtract */
wrap_imul, /* nb_inplace_multiply */
wrap_idiv, /* nb_inplace_divide */
wrap_imod, /* nb_inplace_remainder */
(ternaryfunc)wrap_ipow, /* nb_inplace_power */
wrap_ilshift, /* nb_inplace_lshift */
wrap_irshift, /* nb_inplace_rshift */
wrap_iand, /* nb_inplace_and */
wrap_ixor, /* nb_inplace_xor */
wrap_ior, /* nb_inplace_or */
/* Added in release 2.2 */
/* These require the Py_TPFLAGS_HAVE_CLASS flag */
wrap_floordiv, /* nb_floor_divide */
wrap_truediv, /* nb_true_divide */
wrap_ifloordiv, /* nb_inplace_floor_divide */
wrap_itruediv, /* nb_inplace_true_divide */
};
static PySequenceMethods
wrap_as_sequence = {
wrap_length, /* sq_length */
0, /* sq_concat */
0, /* sq_repeat */
0, /* sq_item */
wrap_slice, /* sq_slice */
0, /* sq_ass_item */
wrap_ass_slice, /* sq_ass_slice */
wrap_contains, /* sq_contains */
};
static PyMappingMethods
wrap_as_mapping = {
wrap_length, /* mp_length */
wrap_getitem, /* mp_subscript */
wrap_setitem, /* mp_ass_subscript */
};
static PyMethodDef
wrap_methods[] = {
{"__reduce__", (PyCFunction)wrap_reduce, METH_NOARGS, reduce__doc__},
{NULL, NULL},
};
/*
* Note that the numeric methods are not supported. This is primarily
* because of the way coercion-less operations are performed with
* new-style numbers; since we can't tell which side of the operation
* is 'self', we can't ensure we'd unwrap the right thing to perform
* the actual operation. We also can't afford to just unwrap both
* sides the way weakrefs do, since we don't know what semantics will
* be associated with the wrapper itself.
*/
statichere PyTypeObject
ProxyType = {
PyObject_HEAD_INIT(NULL) /* PyObject_HEAD_INIT(&PyType_Type) */
0,
"zope.proxy.ProxyBase",
sizeof(ProxyObject),
0,
wrap_dealloc, /* tp_dealloc */
wrap_print, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
wrap_compare, /* tp_compare */
wrap_repr, /* tp_repr */
&wrap_as_number, /* tp_as_number */
&wrap_as_sequence, /* tp_as_sequence */
&wrap_as_mapping, /* tp_as_mapping */
wrap_hash, /* tp_hash */
wrap_call, /* tp_call */
wrap_str, /* tp_str */
wrap_getattro, /* tp_getattro */
wrap_setattro, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC
| Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_BASETYPE, /* tp_flags */
0, /* tp_doc */
wrap_traverse, /* tp_traverse */
wrap_clear, /* tp_clear */
wrap_richcompare, /* tp_richcompare */
0, /* tp_weaklistoffset */
wrap_iter, /* tp_iter */
wrap_iternext, /* tp_iternext */
wrap_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
wrap_init, /* tp_init */
0, /* tp_alloc */
wrap_new, /* tp_new */
0, /*_PyObject_GC_Del,*/ /* tp_free */
};
static PyObject *
create_proxy(PyObject *object)
{
PyObject *result = NULL;
PyObject *args;
args = PyTuple_New(1);
if (args != NULL) {
Py_INCREF(object);
PyTuple_SET_ITEM(args, 0, object);
result = PyObject_CallObject((PyObject *)&ProxyType, args);
Py_DECREF(args);
}
return result;
}
static int
api_check(PyObject *obj)
{
return obj ? Proxy_Check(obj) : 0;
}
static PyObject *
api_create(PyObject *object)
{
if (object == NULL) {
PyErr_SetString(PyExc_ValueError,
"cannot create proxy around NULL");
return NULL;
}
return create_proxy(object);
}
static PyObject *
api_getobject(PyObject *proxy)
{
if (proxy == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"cannot pass NULL to ProxyAPI.getobject()");
return NULL;
}
if (Proxy_Check(proxy))
return Proxy_GET_OBJECT(proxy);
else {
PyErr_Format(PyExc_TypeError, "expected proxy object, got %s",
proxy->ob_type->tp_name);
return NULL;
}
}
static ProxyInterface
wrapper_capi = {
&ProxyType,
api_check,
api_create,
api_getobject,
};
static PyObject *api_object = NULL;
static char
getobject__doc__[] =
"getProxiedObject(proxy) --> object\n"
"\n"
"Get the underlying object for proxy, or the object itself, if it is\n"
"not a proxy.";
static PyObject *
wrapper_getobject(PyObject *unused, PyObject *obj)
{
if (Proxy_Check(obj))
obj = Proxy_GET_OBJECT(obj);
if (obj == NULL)
obj = Py_None;
Py_INCREF(obj);
return obj;
}
static char
isProxy__doc__[] =
"Check whether the given object is a proxy\n"
"\n"
"If proxytype is not None, checkes whether the object is\n"
"proxied by the given proxytype.\n"
;
static PyObject *
wrapper_isProxy(PyObject *unused, PyObject *args)
{
PyObject *obj, *result;
PyTypeObject *proxytype=&ProxyType;
if (! PyArg_ParseTuple(args, "O|O!:isProxy",
&obj, &PyType_Type, &proxytype)
)
return NULL;
while (obj && Proxy_Check(obj))
{
if (PyObject_TypeCheck(obj, proxytype))
{
result = Py_True;
Py_INCREF(result);
return result;
}
obj = Proxy_GET_OBJECT(obj);
}
result = Py_False;
Py_INCREF(result);
return result;
}
static char
removeAllProxies__doc__[] =
"removeAllProxies(proxy) --> object\n"
"\n"
"Get the proxied object with no proxies\n"
"\n"
"If obj is not a proxied object, return obj.\n"
"\n"
"The returned object has no proxies.\n"
;
static PyObject *
wrapper_removeAllProxies(PyObject *unused, PyObject *obj)
{
while (obj && Proxy_Check(obj))
obj = Proxy_GET_OBJECT(obj);
if (obj == NULL)
obj = Py_None;
Py_INCREF(obj);
return obj;
}
static char
sameProxiedObjects__doc__[] =
"Check whether two objects are the same or proxies of the same object";
static PyObject *
wrapper_sameProxiedObjects(PyObject *unused, PyObject *args)
{
PyObject *ob1, *ob2;
if (! PyArg_ParseTuple(args, "OO:sameProxiedObjects", &ob1, &ob2))
return NULL;
while (ob1 && Proxy_Check(ob1))
ob1 = Proxy_GET_OBJECT(ob1);
while (ob2 && Proxy_Check(ob2))
ob2 = Proxy_GET_OBJECT(ob2);
if (ob1 == ob2)
ob1 = Py_True;
else
ob1 = Py_False;
Py_INCREF(ob1);
return ob1;
}
static char
queryProxy__doc__[] =
"Look for a proxy of the given type around the object\n"
"\n"
"If no such proxy can be found, return the default.\n"
;
static PyObject *
wrapper_queryProxy(PyObject *unused, PyObject *args)
{
PyObject *obj, *result=Py_None;
PyTypeObject *proxytype=&ProxyType;
if (! PyArg_ParseTuple(args, "O|O!O:queryProxy",
&obj, &PyType_Type, &proxytype, &result)
)
return NULL;
while (obj && Proxy_Check(obj))
{
if (PyObject_TypeCheck(obj, proxytype))
{
Py_INCREF(obj);
return obj;
}
obj = Proxy_GET_OBJECT(obj);
}
Py_INCREF(result);
return result;
}
static char
queryInnerProxy__doc__[] =
"Look for the inner-most proxy of the given type around the object\n"
"\n"
"If no such proxy can be found, return the default.\n"
"\n"
"If there is such a proxy, return the inner-most one.\n"
;
static PyObject *
wrapper_queryInnerProxy(PyObject *unused, PyObject *args)
{
PyObject *obj, *result=Py_None;
PyTypeObject *proxytype=&ProxyType;
if (! PyArg_ParseTuple(args, "O|O!O:queryInnerProxy",
&obj, &PyType_Type, &proxytype, &result)
)
return NULL;
while (obj && Proxy_Check(obj))
{
if (PyObject_TypeCheck(obj, proxytype))
result = obj;
obj = Proxy_GET_OBJECT(obj);
}
Py_INCREF(result);
return result;
}
static char
module___doc__[] =
"Association between an object, a context object, and a dictionary.\n\
\n\
The context object and dictionary give additional context information\n\
associated with a reference to the basic object. The wrapper objects\n\
act as proxies for the original object.";
static PyMethodDef
module_functions[] = {
{"getProxiedObject", wrapper_getobject, METH_O, getobject__doc__},
{"isProxy", wrapper_isProxy, METH_VARARGS, isProxy__doc__},
{"sameProxiedObjects", wrapper_sameProxiedObjects, METH_VARARGS,
sameProxiedObjects__doc__},
{"queryProxy", wrapper_queryProxy, METH_VARARGS, queryProxy__doc__},
{"queryInnerProxy", wrapper_queryInnerProxy, METH_VARARGS,
queryInnerProxy__doc__},
{"removeAllProxies", wrapper_removeAllProxies, METH_O,
removeAllProxies__doc__},
{NULL}
};
void
init_zope_proxy_proxy(void)
{
PyObject *m = Py_InitModule3("_zope_proxy_proxy",
module_functions, module___doc__);
if (m == NULL)
return;
if (empty_tuple == NULL)
empty_tuple = PyTuple_New(0);
ProxyType.tp_free = _PyObject_GC_Del;
if (PyType_Ready(&ProxyType) < 0)
return;
Py_INCREF(&ProxyType);
PyModule_AddObject(m, "ProxyBase", (PyObject *)&ProxyType);
if (api_object == NULL) {
api_object = PyCObject_FromVoidPtr(&wrapper_capi, NULL);
if (api_object == NULL)
return;
}
Py_INCREF(api_object);
PyModule_AddObject(m, "_CAPI", api_object);
}
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""This module provides a sample btree container implementation.
$Id: btree.py 107468 2009-12-31 20:12:53Z hannosch $
"""
__docformat__ = 'restructuredtext'
from persistent import Persistent
from BTrees.OOBTree import OOBTree
from BTrees.Length import Length
from zope.container.interfaces import IBTreeContainer
from zope.container.contained import Contained, setitem, uncontained
from zope.interface import implements
class Lazy(object):
"""Lazy Attributes.
"""
def __init__(self, func, name=None):
if name is None:
name = func.__name__
self.data = (func, name)
def __get__(self, inst, class_):
if inst is None:
return self
func, name = self.data
value = func(inst)
inst.__dict__[name] = value
return value
class BTreeContainer(Contained, Persistent):
implements(IBTreeContainer)
def __init__(self):
# We keep the previous attribute to store the data
# for backward compatibility
self._SampleContainer__data = self._newContainerData()
self.__len = Length()
def _newContainerData(self):
"""Construct an item-data container
Subclasses should override this if they want different data.
The value returned is a mapping object that also has get,
has_key, keys, items, and values methods.
The default implementation uses an OOBTree.
"""
return OOBTree()
def __contains__(self, key):
'''See interface IReadContainer
>>> c = BTreeContainer()
>>> "a" in c
False
>>> c["a"] = 1
>>> "a" in c
True
>>> "A" in c
False
'''
return key in self._SampleContainer__data
@Lazy
def _BTreeContainer__len(self):
l = Length()
ol = len(self._SampleContainer__data)
if ol > 0:
l.change(ol)
self._p_changed = True
return l
def __len__(self):
return self.__len()
def _setitemf(self, key, value):
# make sure our lazy property gets set
l = self.__len
self._SampleContainer__data[key] = value
l.change(1)
def __iter__(self):
return iter(self._SampleContainer__data)
def __getitem__(self, key):
'''See interface `IReadContainer`'''
return self._SampleContainer__data[key]
def get(self, key, default=None):
'''See interface `IReadContainer`'''
return self._SampleContainer__data.get(key, default)
def __setitem__(self, key, value):
setitem(self, self._setitemf, key, value)
def __delitem__(self, key):
# make sure our lazy property gets set
l = self.__len
uncontained(self._SampleContainer__data[key], self, key)
del self._SampleContainer__data[key]
l.change(-1)
has_key = __contains__
def items(self, key=None):
return self._SampleContainer__data.items(key)
def keys(self, key=None):
return self._SampleContainer__data.keys(key)
def values(self, key=None):
return self._SampleContainer__data.values(key)
<configure
xmlns="http://namespaces.zope.org/zope"
i18n_domain="zope"
>
<adapter
provides=".interfaces.IFind"
for=".interfaces.IReadContainer"
permission="zope.ManageContent"
factory="zope.container.find.FindAdapter"
/>
<adapter
for=".interfaces.IReadContainer"
provides="zope.filerepresentation.interfaces.IReadDirectory"
factory=".directory.noop"
/>
<adapter
for=".interfaces.IWriteContainer"
provides="zope.filerepresentation.interfaces.IWriteDirectory"
factory=".directory.noop"
/>
<adapter
for=".interfaces.IContentContainer"
provides="zope.filerepresentation.interfaces.IDirectoryFactory"
factory=".directory.Cloner"
permission="zope.ManageContent"
/>
<adapter
for=".interfaces.IContentContainer"
provides="zope.filerepresentation.interfaces.IReadDirectory"
factory=".directory.ReadDirectory"
permission="zope.View"
/>
<adapter
factory="zope.container.traversal.ContainerTraversable"
provides="zope.traversing.interfaces.ITraversable"
for="zope.container.interfaces.IReadContainer"
/>
<adapter
factory="zope.container.size.ContainerSized"
provides="zope.size.interfaces.ISized"
for="zope.container.interfaces.IReadContainer"
/>
<adapter
provides=".interfaces.INameChooser"
for="zope.container.interfaces.IWriteContainer"
factory=".contained.NameChooser"
/>
<subscriber
for="zope.location.interfaces.ILocation
zope.lifecycleevent.interfaces.IObjectMovedEvent"
handler=".contained.dispatchToSublocations"
>
Handler dispatches moved events to sublocations of the original object.
</subscriber>
<adapter
provides="zope.location.interfaces.ISublocations"
for="zope.container.interfaces.IReadContainer"
factory=".contained.ContainerSublocations"
/>
<class class=".constraints.ItemTypePrecondition">
<allow interface=".constraints.IItemTypePrecondition" />
</class>
<adapter
for="zope.container.interfaces.IItemContainer
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.publisher.interfaces.browser.IBrowserPublisher"
factory="zope.container.traversal.ItemTraverser"
permission="zope.Public"
/>
<adapter
for="zope.container.interfaces.ISimpleReadContainer
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.publisher.interfaces.browser.IBrowserPublisher"
factory="zope.container.traversal.ItemTraverser"
permission="zope.Public"
/>
<view
for="zope.container.interfaces.IItemContainer"
type="zope.publisher.interfaces.xmlrpc.IXMLRPCRequest"
provides="zope.publisher.interfaces.xmlrpc.IXMLRPCPublisher"
factory="zope.container.traversal.ItemTraverser"
permission="zope.Public"
/>
<view
for="zope.container.interfaces.IReadContainer"
type="zope.publisher.interfaces.xmlrpc.IXMLRPCRequest"
provides="zope.publisher.interfaces.xmlrpc.IXMLRPCPublisher"
factory="zope.container.traversal.ContainerTraverser"
permission="zope.Public"
/>
</configure>
##############################################################################
#
# Copyright (c) 2003 Zope Corporation 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 for containment constraints
Either a container or an object can provide constraints on the
containment relationship.
A container expresses constraints through a precondition on it's
`__setitem__` method in it's interface.
Preconditions can be simple callable objects, like functions. They
should raise a ``zope.interface.Invalid`` exception to indicate that a
constraint isn't satisfied:
>>> def preNoZ(container, name, ob):
... "Silly precondition example"
... if name.startswith("Z"):
... raise zope.interface.Invalid("Names can not start with Z")
>>> class I1(zope.interface.Interface):
... def __setitem__(name, on):
... "Add an item"
... __setitem__.precondition = preNoZ
>>> from zope.container.interfaces import IContainer
>>> class C1(object):
... zope.interface.implements(I1, IContainer)
... def __repr__(self):
... return 'C1'
Given such a precondition, we can then check whether an object can be
added:
>>> c1 = C1()
>>> checkObject(c1, "bob", None)
>>> checkObject(c1, "Zbob", None)
Traceback (most recent call last):
...
Invalid: Names can not start with Z
We can also express constaints on the containers an object can be
added to. We do this by setting a field constraint on an object's
`__parent__` attribute:
>>> import zope.schema
A field constraint is a callable object that returns a boolean value:
>>> def con1(container):
... "silly container constraint"
... if not hasattr(container, 'x'):
... return False
... return True
>>> class I2(zope.interface.Interface):
... __parent__ = zope.schema.Field(constraint = con1)
>>> class O(object):
... zope.interface.implements(I2)
If the constraint isn't satisfied, we'll get a validation error when we
check whether the object can be added:
>>> checkObject(c1, "bob", O())
Traceback (most recent call last):
...
ConstraintNotSatisfied: C1
Note that the validation error isn't very informative. For that
reason, it's better for constraints to raise Invalid errors when they
aren't satisfied:
>>> def con1(container):
... "silly container constraint"
... if not hasattr(container, 'x'):
... raise zope.interface.Invalid("What, no x?")
... return True
>>> class I2(zope.interface.Interface):
... __parent__ = zope.schema.Field(constraint = con1)
>>> class O(object):
... zope.interface.implements(I2)
>>> checkObject(c1, "bob", O())
Traceback (most recent call last):
...
Invalid: What, no x?
>>> c1.x = 1
>>> checkObject(c1, "bob", O())
The `checkObject` function is handy when checking whether we can add an
existing object to a container, but, sometimes, we want to check
whether an object produced by a factory can be added. To do this, we
use `checkFactory`:
>>> class Factory(object):
... def __call__(self):
... return O()
... def getInterfaces(self):
... return zope.interface.implementedBy(O)
>>> factory = Factory()
>>> checkFactory(c1, "bob", factory)
True
>>> del c1.x
>>> checkFactory(c1, "bob", factory)
False
Unlike `checkObject`, `checkFactory`:
- Returns a boolean value
- Takes a factory (e.g. a class) rather than an argument.
The container constraint we defined for C1 isn't actually used to
check the factory:
>>> c1.x = 1
>>> checkFactory(c1, "Zbob", factory)
True
To work with `checkFactory`, a container precondition has to
implement a factory method. This is because a factory, rather than
an object is passed. To illustrate this, we'll make preNoZ its own
factory method:
>>> preNoZ.factory = preNoZ
We can do this (silly thing) because preNoZ doesn't use the object
argument.
>>> checkFactory(c1, "Zbob", factory)
False
$Id: constraints.py 107468 2009-12-31 20:12:53Z hannosch $
"""
__docformat__ = 'restructuredtext'
import sys
from zope.dottedname.resolve import resolve
import zope.schema
from zope.interface import providedBy
from zope.container.interfaces import InvalidItemType, InvalidContainerType
from zope.container.i18n import ZopeMessageFactory as _
from zope.container.interfaces import IContainer
def checkObject(container, name, object):
"""Check containement constraints for an object and container
"""
# check __setitem__ precondition
containerProvided = providedBy(container)
__setitem__ = containerProvided.get('__setitem__')
if __setitem__ is not None:
precondition = __setitem__.queryTaggedValue('precondition')
if precondition is not None:
precondition(container, name, object)
# check the constraint on __parent__
__parent__ = providedBy(object).get('__parent__')
if __parent__ is not None:
try:
validate = __parent__.validate
except AttributeError:
pass
else:
validate(container)
if not containerProvided.extends(IContainer):
# If it doesn't implement IContainer, it can't contain stuff.
raise TypeError(
_('Container is not a valid Zope container.')
)
def checkFactory(container, name, factory):
__setitem__ = providedBy(container).get('__setitem__')
if __setitem__ is not None:
precondition = __setitem__.queryTaggedValue('precondition')
if precondition is not None:
try:
precondition = precondition.factory
except AttributeError:
pass
else:
try:
precondition(container, name, factory)
except zope.interface.Invalid:
return False
# check the constraint on __parent__
__parent__ = factory.getInterfaces().get('__parent__')
if __parent__ is not None:
try:
validate = __parent__.validate
except AttributeError:
pass
else:
try:
validate(container)
except zope.interface.Invalid:
return False
return True
class readproperty(object):
def __init__(self, func):
self.func = func
def __get__(self, inst, class_):
if inst is None:
return self
func = self.func
return func(inst)
class IItemTypePrecondition(zope.interface.Interface):
def __call__(container, name, object):
"""Test whether container setitem arguments are valid.
Raise zope.interface.Invalid if the object is invalid.
"""
def factory(container, name, factory):
"""Test whether objects provided by the factory are acceptable
Return a boolean value.
"""
class _TypesBased(object):
@readproperty
def types(self):
raw_types, module = self.raw_types
types = []
for t in raw_types:
if isinstance(t, str):
t = resolve(t, module)
types.append(t)
self.types = types
return types
def __init__(self, *types, **kw):
if [t for t in types if isinstance(t, str)]:
# have dotted names
module = kw.get('module', sys._getframe(1).f_globals['__name__'])
self.raw_types = types, module
else:
self.types = types
class ItemTypePrecondition(_TypesBased):
"""Specify a `__setitem__` precondition that restricts item types
Items must be one of the given types.
>>> class I1(zope.interface.Interface):
... pass
>>> class I2(zope.interface.Interface):
... pass
>>> precondition = ItemTypePrecondition(I1, I2)
>>> class Ob(object):
... pass
>>> ob = Ob()
>>> class Factory(object):
... def __call__(self):
... return Ob()
... def getInterfaces(self):
... return zope.interface.implementedBy(Ob)
>>> factory = Factory()
>>> try:
... precondition(None, 'foo', ob)
... except InvalidItemType, v:
... print v[0], (v[1] is ob), (v[2] == (I1, I2))
... else:
... print 'Should have failed'
None True True
>>> try:
... precondition.factory(None, 'foo', factory)
... except InvalidItemType, v:
... print v[0], (v[1] is factory), (v[2] == (I1, I2))
... else:
... print 'Should have failed'
None True True
>>> zope.interface.classImplements(Ob, I2)
>>> precondition(None, 'foo', ob)
>>> precondition.factory(None, 'foo', factory)
"""
zope.interface.implements(IItemTypePrecondition)
def __call__(self, container, name, object):
for iface in self.types:
if iface.providedBy(object):
return
raise InvalidItemType(container, object, self.types)
def factory(self, container, name, factory):
implemented = factory.getInterfaces()
for iface in self.types:
if implemented.isOrExtends(iface):
return
raise InvalidItemType(container, factory, self.types)
def contains(*types):
"""Declare that a container type contains only the given types
This is used within a class suite defining an interface to create
a __setitem__ specification with a precondition allowing only the
given types:
>>> class IFoo(zope.interface.Interface):
... pass
>>> class IBar(zope.interface.Interface):
... pass
>>> class IFooBarContainer(IContainer):
... contains(IFoo, IBar)
>>> __setitem__ = IFooBarContainer['__setitem__']
>>> __setitem__.getTaggedValue('precondition').types == (IFoo, IBar)
True
It is invalid to call contains outside a class suite:
>>> contains(IFoo, IBar)
Traceback (most recent call last):
...
TypeError: contains not called from suite
"""
frame = sys._getframe(1)
f_locals = frame.f_locals
f_globals = frame.f_globals
if not (f_locals is not f_globals
and f_locals.get('__module__')
and f_locals.get('__module__') == f_globals.get('__name__')
):
raise TypeError("contains not called from suite")
def __setitem__(key, value):
pass
__setitem__.__doc__ = IContainer['__setitem__'].__doc__
__setitem__.precondition = ItemTypePrecondition(
*types,
**dict(module=f_globals['__name__'])
)
f_locals['__setitem__'] = __setitem__
class IContainerTypesConstraint(zope.interface.Interface):
def __call__(object):
"""Test whether object is valid.
Return True if valid.
Raise zope.interface.Invalid if the objet is invalid.
"""
class ContainerTypesConstraint(_TypesBased):
"""Constrain a container to be one of a number of types
>>> class I1(zope.interface.Interface):
... pass
>>> class I2(zope.interface.Interface):
... pass
>>> class Ob(object):
... pass
>>> ob = Ob()
>>> constraint = ContainerTypesConstraint(I1, I2)
>>> try:
... constraint(ob)
... except InvalidContainerType, v:
... print (v[0] is ob), (v[1] == (I1, I2))
... else:
... print 'Should have failed'
True True
>>> zope.interface.classImplements(Ob, I2)
>>> constraint(Ob())
True
"""
zope.interface.implements(IContainerTypesConstraint)
def __call__(self, object):
for iface in self.types:
if iface.providedBy(object):
return True
else:
raise InvalidContainerType(object, self.types)
def containers(*types):
"""Declare the container types a type can be contained in
This is used within a class suite defining an interface to create
a __parent__ specification with a constraint allowing only the
given types:
>>> class IFoo(IContainer):
... pass
>>> class IBar(IContainer):
... pass
>>> from zope.location.interfaces import IContained
>>> class IFooBarContained(IContained):
... containers(IFoo, IBar)
>>> __parent__ = IFooBarContained['__parent__']
>>> __parent__.constraint.types == (IFoo, IBar)
True
It is invalid to call containers outside a class suite:
>>> containers(IFoo, IBar)
Traceback (most recent call last):
...
TypeError: containers not called from suite
"""
frame = sys._getframe(1)
f_locals = frame.f_locals
f_globals = frame.f_globals
if not (f_locals is not f_globals
and f_locals.get('__module__')
and f_locals.get('__module__') == f_globals.get('__name__')
):
raise TypeError("containers not called from suite")
__parent__ = zope.schema.Field(
constraint = ContainerTypesConstraint(
*types,
**dict(module=f_globals['__name__'])
)
)
f_locals['__parent__'] = __parent__
=========================
Containment constraints
=========================
Containment constraints allow us to express restrictions on the types
of items that can be placed in containers or on the types of
containers an item can be placed in. We express these constraints in
interfaces. Let's define some container and item interfaces:
>>> from zope.container.interfaces import IContainer
>>> from zope.location.interfaces import IContained
>>> from zope.container.constraints import containers, contains
>>> class IBuddyFolder(IContainer):
... contains('.IBuddy')
In this example, we used the contains function to declare that objects
that provide IBuddyFolder can only contain items that provide IBuddy.
Note that we used a string containing a dotted name for the IBuddy
interface. This is because IBuddy hasn't been defined yet. When we
define IBuddy, we can use IBuddyFolder directly:
>>> class IBuddy(IContained):
... containers(IBuddyFolder)
Now, with these interfaces in place, we can define Buddy and
BuddyFolder classes and verify that we can put buddies in buddy
folders:
>>> from zope import interface
>>> class Buddy:
... interface.implements(IBuddy)
>>> class BuddyFolder:
... interface.implements(IBuddyFolder)
>>> from zope.container.constraints import checkObject, checkFactory
>>> from zope.component.factory import Factory
>>> checkObject(BuddyFolder(), 'x', Buddy())
>>> checkFactory(BuddyFolder(), 'x', Factory(Buddy))
True
If we try to use other containers or folders, we'll get errors:
>>> class Container:
... interface.implements(IContainer)
>>> class Contained:
... interface.implements(IContained)
>>> checkObject(Container(), 'x', Buddy())
... # doctest: +ELLIPSIS
Traceback (most recent call last):
InvalidContainerType: ...
>>> checkFactory(Container(), 'x', Factory(Buddy))
False
>>> checkObject(BuddyFolder(), 'x', Contained())
... # doctest: +ELLIPSIS
Traceback (most recent call last):
InvalidItemType: ...
>>> checkFactory(BuddyFolder(), 'x', Factory(Contained))
False
In the example, we defined the container first and then the items. We
could have defined these in the opposite order:
>>> class IContact(IContained):
... containers('.IContacts')
>>> class IContacts(IContainer):
... contains(IContact)
>>> class Contact:
... interface.implements(IContact)
>>> class Contacts:
... interface.implements(IContacts)
>>> checkObject(Contacts(), 'x', Contact())
>>> checkFactory(Contacts(), 'x', Factory(Contact))
True
>>> checkObject(Contacts(), 'x', Buddy())
... # doctest: +ELLIPSIS
Traceback (most recent call last):
InvalidItemType: ...
>>> checkFactory(Contacts(), 'x', Factory(Buddy))
False
##############################################################################
#
# Copyright (c) 2003 Zope Corporation 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.
#
##############################################################################
"""Classes to support implementing `IContained`
$Id: contained.py 111341 2010-04-24 12:01:17Z ccomb $
"""
__docformat__ = 'restructuredtext'
import zope.component
import zope.interface.declarations
from zope.interface import providedBy
from zope.interface.declarations import getObjectSpecification
from zope.interface.declarations import ObjectSpecification
from zope.event import notify
from zope.location.interfaces import ILocation, ISublocations
from zope.security.checker import selectChecker, CombinedChecker
from zope.lifecycleevent import ObjectModifiedEvent
from zope.container.i18n import ZopeMessageFactory as _
from zope.location.interfaces import IContained
from zope.container.interfaces import INameChooser
from zope.container.interfaces import IReservedNames, NameReserved
from zope.container.interfaces import IContainerModifiedEvent
from zope.container._zope_container_contained import ContainedProxyBase
from zope.container._zope_container_contained import getProxiedObject
from zope.lifecycleevent import ObjectMovedEvent
from zope.lifecycleevent import ObjectAddedEvent
from zope.lifecycleevent import ObjectRemovedEvent
# BBB ZODB3 < 3.10
try:
from ZODB.interfaces import IBroken
except ImportError:
from zope.broken.interfaces import IBroken
class Contained(object):
"""Stupid mix-in that defines `__parent__` and `__name__` attributes"""
zope.interface.implements(IContained)
__parent__ = __name__ = None
class ContainerModifiedEvent(ObjectModifiedEvent):
"""The container has been modified."""
zope.interface.implements(IContainerModifiedEvent)
def dispatchToSublocations(object, event):
"""Dispatch an event to sublocations of a given object
When a move event happens for an object, it's important to notify
subobjects as well.
We do this based on locations.
Suppose, for example, that we define some location objects.
>>> class L(object):
... zope.interface.implements(ILocation)
... def __init__(self, name):
... self.__name__ = name
... self.__parent__ = None
... def __repr__(self):
... return '%s(%s)' % (
... self.__class__.__name__, str(self.__name__))
>>> class C(L):
... zope.interface.implements(ISublocations)
... def __init__(self, name, *subs):
... L.__init__(self, name)
... self.subs = subs
... for sub in subs:
... sub.__parent__ = self
... def sublocations(self):
... return self.subs
>>> c = C(1,
... C(11,
... L(111),
... L(112),
... ),
... C(12,
... L(121),
... L(122),
... L(123),
... L(124),
... ),
... L(13),
... )
Now, if we call the dispatcher, it should call event handlers
for all of the objects.
Lets create an event handler that records the objects it sees:
>>> seen = []
>>> def handler(ob, event):
... seen.append((ob, event.object))
Note that we record the the object the handler is called on as
well as the event object:
Now we'll register it:
>>> from zope import component
>>> from zope.lifecycleevent.interfaces import IObjectMovedEvent
>>> component.provideHandler(handler, [None, IObjectMovedEvent])
We also register our dispatcher:
>>> component.provideHandler(dispatchToSublocations,
... [None, IObjectMovedEvent])
We can then call the dispatcher for the root object:
>>> event = ObjectRemovedEvent(c)
>>> dispatchToSublocations(c, event)
Now, we should have seen all of the subobjects:
>>> seenreprs = map(repr, seen)
>>> seenreprs.sort()
>>> seenreprs
['(C(11), C(1))', '(C(12), C(1))', '(L(111), C(1))',""" \
""" '(L(112), C(1))', '(L(121), C(1))', '(L(122), C(1))',""" \
""" '(L(123), C(1))', '(L(124), C(1))', '(L(13), C(1))']
We see that we get entries for each of the subobjects and
that,for each entry, the event object is top object.
This suggests that location event handlers need to be aware that
the objects they are called on and the event objects could be
different.
"""
subs = ISublocations(object, None)
if subs is not None:
for sub in subs.sublocations():
for ignored in zope.component.subscribers((sub, event), None):
pass # They do work in the adapter fetch
class ContainerSublocations(object):
"""Get the sublocations for a container
Obviously, this is the container values:
>>> class MyContainer(object):
... def __init__(self, **data):
... self.data = data
... def __iter__(self):
... return iter(self.data)
... def __getitem__(self, key):
... return self.data[key]
>>> container = MyContainer(x=1, y=2, z=42)
>>> adapter = ContainerSublocations(container)
>>> sublocations = list(adapter.sublocations())
>>> sublocations.sort()
>>> sublocations
[1, 2, 42]
"""
def __init__(self, container):
self.container = container
def sublocations(self):
container = self.container
for key in container:
yield container[key]
def containedEvent(object, container, name=None):
"""Establish the containment of the object in the container
The object and necessary event are returned. The object may be a
`ContainedProxy` around the original object. The event is an added
event, a moved event, or None.
If the object implements `IContained`, simply set its `__parent__`
and `__name__` attributes:
>>> container = {}
>>> item = Contained()
>>> x, event = containedEvent(item, container, u'foo')
>>> x is item
True
>>> item.__parent__ is container
True
>>> item.__name__
u'foo'
We have an added event:
>>> event.__class__.__name__
'ObjectAddedEvent'
>>> event.object is item
True
>>> event.newParent is container
True
>>> event.newName
u'foo'
>>> event.oldParent
>>> event.oldName
Now if we call contained again:
>>> x2, event = containedEvent(item, container, u'foo')
>>> x2 is item
True
>>> item.__parent__ is container
True
>>> item.__name__
u'foo'
We don't get a new added event:
>>> event
If the object already had a parent but the parent or name was
different, we get a moved event:
>>> x, event = containedEvent(item, container, u'foo2')
>>> event.__class__.__name__
'ObjectMovedEvent'
>>> event.object is item
True
>>> event.newParent is container
True
>>> event.newName
u'foo2'
>>> event.oldParent is container
True
>>> event.oldName
u'foo'
If the `object` implements `ILocation`, but not `IContained`, set its
`__parent__` and `__name__` attributes *and* declare that it
implements `IContained`:
>>> from zope.location import Location
>>> item = Location()
>>> IContained.providedBy(item)
False
>>> x, event = containedEvent(item, container, 'foo')
>>> x is item
True
>>> item.__parent__ is container
True
>>> item.__name__
'foo'
>>> IContained.providedBy(item)
True
If the `object` doesn't even implement `ILocation`, put a
`ContainedProxy` around it:
>>> item = []
>>> x, event = containedEvent(item, container, 'foo')
>>> x is item
False
>>> x.__parent__ is container
True
>>> x.__name__
'foo'
Make sure we don't lose existing directly provided interfaces.
>>> from zope.interface import Interface, directlyProvides
>>> class IOther(Interface):
... pass
>>> from zope.location import Location
>>> item = Location()
>>> directlyProvides(item, IOther)
>>> IOther.providedBy(item)
True
>>> x, event = containedEvent(item, container, 'foo')
>>> IOther.providedBy(item)
True
"""
if not IContained.providedBy(object):
if ILocation.providedBy(object):
zope.interface.alsoProvides(object, IContained)
else:
object = ContainedProxy(object)
oldparent = object.__parent__
oldname = object.__name__
if oldparent is container and oldname == name:
# No events
return object, None
object.__parent__ = container
object.__name__ = name
if oldparent is None or oldname is None:
event = ObjectAddedEvent(object, container, name)
else:
event = ObjectMovedEvent(object, oldparent, oldname, container, name)
return object, event
def contained(object, container, name=None):
"""Establish the containment of the object in the container
Just return the contained object without an event. This is a convenience
"macro" for:
``containedEvent(object, container, name)[0]``
This function is only used for tests.
"""
return containedEvent(object, container, name)[0]
def notifyContainerModified(object, *descriptions):
"""Notify that the container was modified."""
notify(ContainerModifiedEvent(object, *descriptions))
def setitem(container, setitemf, name, object):
"""Helper function to set an item and generate needed events
This helper is needed, in part, because the events need to get
published after the `object` has been added to the `container`.
If the item implements `IContained`, simply set its `__parent__`
and `__name__` attributes:
>>> class IItem(zope.interface.Interface):
... pass
>>> class Item(Contained):
... zope.interface.implements(IItem)
... def setAdded(self, event):
... self.added = event
... def setMoved(self, event):
... self.moved = event
>>> from zope.lifecycleevent.interfaces import IObjectAddedEvent
>>> from zope.lifecycleevent.interfaces import IObjectMovedEvent
>>> from zope import component
>>> component.provideHandler(lambda obj, event: obj.setAdded(event),
... [IItem, IObjectAddedEvent])
>>> component.provideHandler(lambda obj, event: obj.setMoved(event),
... [IItem, IObjectMovedEvent])
>>> item = Item()
>>> container = {}
>>> setitem(container, container.__setitem__, u'c', item)
>>> container[u'c'] is item
1
>>> item.__parent__ is container
1
>>> item.__name__
u'c'
If we run this using the testing framework, we'll use `getEvents` to
track the events generated:
>>> from zope.component.eventtesting import getEvents
>>> from zope.lifecycleevent.interfaces import IObjectModifiedEvent
We have an added event:
>>> len(getEvents(IObjectAddedEvent))
1
>>> event = getEvents(IObjectAddedEvent)[-1]
>>> event.object is item
1
>>> event.newParent is container
1
>>> event.newName
u'c'
>>> event.oldParent
>>> event.oldName
As well as a modification event for the container:
>>> len(getEvents(IObjectModifiedEvent))
1
>>> getEvents(IObjectModifiedEvent)[-1].object is container
1
The item's hooks have been called:
>>> item.added is event
1
>>> item.moved is event
1
We can suppress events and hooks by setting the `__parent__` and
`__name__` first:
>>> item = Item()
>>> item.__parent__, item.__name__ = container, 'c2'
>>> setitem(container, container.__setitem__, u'c2', item)
>>> len(container)
2
>>> len(getEvents(IObjectAddedEvent))
1
>>> len(getEvents(IObjectModifiedEvent))
1
>>> getattr(item, 'added', None)
>>> getattr(item, 'moved', None)
If the item had a parent or name (as in a move or rename),
we generate a move event, rather than an add event:
>>> setitem(container, container.__setitem__, u'c3', item)
>>> len(container)
3
>>> len(getEvents(IObjectAddedEvent))
1
>>> len(getEvents(IObjectModifiedEvent))
2
>>> len(getEvents(IObjectMovedEvent))
2
(Note that we have 2 move events because add are move events.)
We also get the move hook called, but not the add hook:
>>> event = getEvents(IObjectMovedEvent)[-1]
>>> getattr(item, 'added', None)
>>> item.moved is event
1
If we try to replace an item without deleting it first, we'll get
an error:
>>> setitem(container, container.__setitem__, u'c', [])
Traceback (most recent call last):
...
KeyError: u'c'
>>> del container[u'c']
>>> setitem(container, container.__setitem__, u'c', [])
>>> len(getEvents(IObjectAddedEvent))
2
>>> len(getEvents(IObjectModifiedEvent))
3
If the object implements `ILocation`, but not `IContained`, set it's
`__parent__` and `__name__` attributes *and* declare that it
implements `IContained`:
>>> from zope.location import Location
>>> item = Location()
>>> IContained.providedBy(item)
0
>>> setitem(container, container.__setitem__, u'l', item)
>>> container[u'l'] is item
1
>>> item.__parent__ is container
1
>>> item.__name__
u'l'
>>> IContained.providedBy(item)
1
We get new added and modification events:
>>> len(getEvents(IObjectAddedEvent))
3
>>> len(getEvents(IObjectModifiedEvent))
4
If the object doesn't even implement `ILocation`, put a
`ContainedProxy` around it:
>>> item = []
>>> setitem(container, container.__setitem__, u'i', item)
>>> container[u'i']
[]
>>> container[u'i'] is item
0
>>> item = container[u'i']
>>> item.__parent__ is container
1
>>> item.__name__
u'i'
>>> IContained.providedBy(item)
1
>>> len(getEvents(IObjectAddedEvent))
4
>>> len(getEvents(IObjectModifiedEvent))
5
We'll get type errors if we give keys that aren't unicode or ascii keys:
>>> setitem(container, container.__setitem__, 42, item)
Traceback (most recent call last):
...
TypeError: name not unicode or ascii string
>>> setitem(container, container.__setitem__, None, item)
Traceback (most recent call last):
...
TypeError: name not unicode or ascii string
>>> setitem(container, container.__setitem__, 'hello ' + chr(200), item)
Traceback (most recent call last):
...
TypeError: name not unicode or ascii string
and we'll get a value error of we give an empty string or unicode:
>>> setitem(container, container.__setitem__, '', item)
Traceback (most recent call last):
...
ValueError: empty names are not allowed
>>> setitem(container, container.__setitem__, u'', item)
Traceback (most recent call last):
...
ValueError: empty names are not allowed
"""
# Do basic name check:
if isinstance(name, str):
try:
name = unicode(name)
except UnicodeError:
raise TypeError("name not unicode or ascii string")
elif not isinstance(name, unicode):
raise TypeError("name not unicode or ascii string")
if not name:
raise ValueError("empty names are not allowed")
old = container.get(name)
if old is object:
return
if old is not None:
raise KeyError(name)
object, event = containedEvent(object, container, name)
setitemf(name, object)
if event:
notify(event)
notifyContainerModified(container)
fixing_up = False
def uncontained(object, container, name=None):
"""Clear the containment relationship between the `object` and
the `container`.
If we run this using the testing framework, we'll use `getEvents` to
track the events generated:
>>> from zope.component.eventtesting import getEvents
>>> from zope.lifecycleevent.interfaces import IObjectModifiedEvent
>>> from zope.lifecycleevent.interfaces import IObjectRemovedEvent
We'll start by creating a container with an item:
>>> class Item(Contained):
... pass
>>> item = Item()
>>> container = {u'foo': item}
>>> x, event = containedEvent(item, container, u'foo')
>>> item.__parent__ is container
1
>>> item.__name__
u'foo'
Now we'll remove the item. It's parent and name are cleared:
>>> uncontained(item, container, u'foo')
>>> item.__parent__
>>> item.__name__
We now have a new removed event:
>>> len(getEvents(IObjectRemovedEvent))
1
>>> event = getEvents(IObjectRemovedEvent)[-1]
>>> event.object is item
1
>>> event.oldParent is container
1
>>> event.oldName
u'foo'
>>> event.newParent
>>> event.newName
As well as a modification event for the container:
>>> len(getEvents(IObjectModifiedEvent))
1
>>> getEvents(IObjectModifiedEvent)[-1].object is container
1
Now if we call uncontained again:
>>> uncontained(item, container, u'foo')
We won't get any new events, because __parent__ and __name__ are None:
>>> len(getEvents(IObjectRemovedEvent))
1
>>> len(getEvents(IObjectModifiedEvent))
1
But, if either the name or parent are not ``None`` and they are not the
container and the old name, we'll get a modified event but not a removed
event.
>>> item.__parent__, item.__name__ = container, None
>>> uncontained(item, container, u'foo')
>>> len(getEvents(IObjectRemovedEvent))
1
>>> len(getEvents(IObjectModifiedEvent))
2
>>> item.__parent__, item.__name__ = None, u'bar'
>>> uncontained(item, container, u'foo')
>>> len(getEvents(IObjectRemovedEvent))
1
>>> len(getEvents(IObjectModifiedEvent))
3
"""
try:
oldparent = object.__parent__
oldname = object.__name__
except AttributeError:
# The old object doesn't implements IContained
# Maybe we're converting old data:
if not fixing_up:
raise
oldparent = None
oldname = None
if oldparent is not container or oldname != name:
if oldparent is not None or oldname is not None:
notifyContainerModified(container)
return
event = ObjectRemovedEvent(object, oldparent, oldname)
notify(event)
if not IBroken.providedBy(object):
object.__parent__ = None
object.__name__ = None
notifyContainerModified(container)
class NameChooser(object):
zope.interface.implements(INameChooser)
def __init__(self, context):
self.context = context
def checkName(self, name, object):
"""See zope.container.interfaces.INameChooser
We create and populate a dummy container
>>> from zope.container.sample import SampleContainer
>>> container = SampleContainer()
>>> container['foo'] = 'bar'
>>> from zope.container.contained import NameChooser
An invalid name raises a ValueError:
>>> NameChooser(container).checkName('+foo', object())
Traceback (most recent call last):
...
ValueError: Names cannot begin with '+' or '@' or contain '/'
A name that already exists raises a KeyError:
>>> NameChooser(container).checkName('foo', object())
Traceback (most recent call last):
...
KeyError: u'The given name is already being used'
A name must be a string or unicode string:
>>> NameChooser(container).checkName(2, object())
Traceback (most recent call last):
...
TypeError: ('Invalid name type', <type 'int'>)
A correct name returns True:
>>> NameChooser(container).checkName('2', object())
True
We can reserve some names by providing a IReservedNames adapter
to a container:
>>> from zope.container.interfaces import IContainer
>>> class ReservedNames(object):
... zope.component.adapts(IContainer)
... zope.interface.implements(IReservedNames)
...
... def __init__(self, context):
... self.reservedNames = set(('reserved', 'other'))
>>> zope.component.getSiteManager().registerAdapter(ReservedNames)
>>> NameChooser(container).checkName('reserved', None)
Traceback (most recent call last):
...
NameReserved: reserved
"""
if isinstance(name, str):
name = unicode(name)
elif not isinstance(name, unicode):
raise TypeError("Invalid name type", type(name))
if not name:
raise ValueError(
_("An empty name was provided. Names cannot be empty.")
)
if name[:1] in '+@' or '/' in name:
raise ValueError(
_("Names cannot begin with '+' or '@' or contain '/'")
)
reserved = IReservedNames(self.context, None)
if reserved is not None:
if name in reserved.reservedNames:
raise NameReserved(name)
if name in self.context:
raise KeyError(
_("The given name is already being used")
)
return True
def chooseName(self, name, object):
"""See zope.container.interfaces.INameChooser
The name chooser is expected to choose a name without error
We create and populate a dummy container
>>> from zope.container.sample import SampleContainer
>>> container = SampleContainer()
>>> container['foobar.old'] = 'rst doc'
>>> from zope.container.contained import NameChooser
the suggested name is converted to unicode:
>>> NameChooser(container).chooseName('foobar', object())
u'foobar'
If it already exists, a number is appended but keeps the same extension:
>>> NameChooser(container).chooseName('foobar.old', object())
u'foobar-2.old'
Bad characters are turned into dashes:
>>> NameChooser(container).chooseName('foo/foo', object())
u'foo-foo'
If no name is suggested, it is based on the object type:
>>> NameChooser(container).chooseName('', [])
u'list'
"""
container = self.context
# convert to unicode and remove characters that checkName does not allow
try:
name = unicode(name)
except:
name = u''
name = name.replace('/', '-').lstrip('+@')
if not name:
name = unicode(object.__class__.__name__)
# for an existing name, append a number.
# We should keep client's os.path.extsep (not ours), we assume it's '.'
dot = name.rfind('.')
if dot >= 0:
suffix = name[dot:]
name = name[:dot]
else:
suffix = ''
n = name + suffix
i = 1
while n in container:
i += 1
n = name + u'-' + unicode(i) + suffix
# Make sure the name is valid. We may have started with something bad.
self.checkName(n, object)
return n
class DecoratorSpecificationDescriptor(
zope.interface.declarations.ObjectSpecificationDescriptor):
"""Support for interface declarations on decorators
>>> from zope.interface import *
>>> class I1(Interface):
... pass
>>> class I2(Interface):
... pass
>>> class I3(Interface):
... pass
>>> class I4(Interface):
... pass
>>> class D1(ContainedProxy):
... implements(I1)
>>> class D2(ContainedProxy):
... implements(I2)
>>> class X:
... implements(I3)
>>> x = X()
>>> directlyProvides(x, I4)
Interfaces of X are ordered with the directly-provided interfaces first
>>> [interface.getName() for interface in list(providedBy(x))]
['I4', 'I3']
When we decorate objects, what order should the interfaces come in? One
could argue that decorators are less specific, so they should come last.
>>> [interface.getName() for interface in list(providedBy(D1(x)))]
['I4', 'I3', 'I1', 'IContained', 'IPersistent']
>>> [interface.getName() for interface in list(providedBy(D2(D1(x))))]
['I4', 'I3', 'I1', 'IContained', 'IPersistent', 'I2']
"""
def __get__(self, inst, cls=None):
if inst is None:
return getObjectSpecification(cls)
else:
provided = providedBy(getProxiedObject(inst))
# Use type rather than __class__ because inst is a proxy and
# will return the proxied object's class.
cls = type(inst)
return ObjectSpecification(provided, cls)
class DecoratedSecurityCheckerDescriptor(object):
"""Descriptor for a Decorator that provides a decorated security checker.
"""
def __get__(self, inst, cls=None):
if inst is None:
return self
else:
proxied_object = getProxiedObject(inst)
checker = getattr(proxied_object, '__Security_checker__', None)
if checker is None:
checker = selectChecker(proxied_object)
wrapper_checker = selectChecker(inst)
if wrapper_checker is None:
return checker
elif checker is None:
return wrapper_checker
else:
return CombinedChecker(wrapper_checker, checker)
class ContainedProxyClassProvides(zope.interface.declarations.ClassProvides):
def __set__(self, inst, value):
inst = getProxiedObject(inst)
inst.__provides__ = value
def __delete__(self, inst):
inst = getProxiedObject(inst)
del inst.__provides__
class ContainedProxy(ContainedProxyBase):
# Prevent proxies from having their own instance dictionaries:
__slots__ = ()
__safe_for_unpickling__ = True
zope.interface.implements(IContained)
__providedBy__ = DecoratorSpecificationDescriptor()
__Security_checker__ = DecoratedSecurityCheckerDescriptor()
ContainedProxy.__provides__ = ContainedProxyClassProvides(ContainedProxy, type)
##############################################################################
#
# Copyright (c) 2002 Zope Corporation 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.
#
##############################################################################
# BBB imports
from zope.app.dependable.dependency import exception_msg
from zope.app.dependable.dependency import CheckDependency
##############################################################################
# Copyright (c) 2003 Zope Corporation 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.
##############################################################################
"""File-system representation adapters for containers
This module includes two adapters (adapter factories, really) for
providing a file-system representation for containers:
`noop`
Factory that "adapts" `IContainer` to `IWriteDirectory`.
This is a lie, since it just returns the original object.
`Cloner`
An `IDirectoryFactory` adapter that just clones the original object.
$Id: directory.py 105849 2009-11-19 07:04:24Z tlotze $
"""
__docformat__ = 'restructuredtext'
from zope.interface import implements
from zope.component.interfaces import ISite
from zope.security.proxy import removeSecurityProxy
import zope.filerepresentation.interfaces
MARKER = object()
def noop(container):
"""Adapt an `IContainer` to an `IWriteDirectory` by just returning it
This "works" because `IContainer` and `IWriteDirectory` have the same
methods, however, the output doesn't actually implement `IWriteDirectory`.
"""
return container
class Cloner(object):
"""`IContainer` to `IDirectoryFactory` adapter that clones
This adapter provides a factory that creates a new empty container
of the same class as it's context.
"""
implements(zope.filerepresentation.interfaces.IDirectoryFactory)
def __init__(self, context):
self.context = context
def __call__(self, name):
# We remove the security proxy so we can actually call the
# class and return an unproxied new object. (We can't use a
# trusted adapter, because the result must be unproxied.) By
# registering this adapter, one effectively gives permission
# to clone the class. Don't use this for classes that have
# exciting side effects as a result of instantiation. :)
return removeSecurityProxy(self.context).__class__()
class RootDirectoryFactory(object):
def __init__(self, context):
pass
def __call__(self, name):
return Folder()
class ReadDirectory(object):
"""Adapter to provide a file-system rendition of folders."""
def __init__(self, context):
self.context = context
def keys(self):
keys = self.context.keys()
if ISite.providedBy(self.context):
return list(keys) + ['++etc++site']
return keys
def get(self, key, default=None):
if key == '++etc++site' and ISite.providedBy(self.context):
return self.context.getSiteManager()
return self.context.get(key, default)
def __iter__(self):
return iter(self.keys())
def __getitem__(self, key):
v = self.get(key, MARKER)
if v is MARKER:
raise KeyError(key)
return v
def values(self):
return map(self.get, self.keys())
def __len__(self):
l = len(self.context)
if ISite.providedBy(self.context):
l += 1
return l
def items(self):
get = self.get
return [(key, get(key)) for key in self.keys()]
def __contains__(self, key):
return self.get(key) is not None
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Find Support
$Id: find.py 68442 2006-06-01 12:54:41Z mj $
"""
__docformat__ = 'restructuredtext'
from zope.interface import implements
from interfaces import IFind, IIdFindFilter, IObjectFindFilter
from interfaces import IReadContainer
class FindAdapter(object):
implements(IFind)
__used_for__ = IReadContainer
def __init__(self, context):
self._context = context
def find(self, id_filters=None, object_filters=None):
'See IFind'
id_filters = id_filters or []
object_filters = object_filters or []
result = []
container = self._context
for id, object in container.items():
_find_helper(id, object, container,
id_filters, object_filters,
result)
return result
def _find_helper(id, object, container, id_filters, object_filters, result):
for id_filter in id_filters:
if not id_filter.matches(id):
break
else:
# if we didn't break out of the loop, all name filters matched
# now check all object filters
for object_filter in object_filters:
if not object_filter.matches(object):
break
else:
# if we didn't break out of the loop, all filters matched
result.append(object)
if not IReadContainer.providedBy(object):
return
container = object
for id, object in container.items():
_find_helper(id, object, container, id_filters, object_filters, result)
class SimpleIdFindFilter(object):
implements(IIdFindFilter)
def __init__(self, ids):
self._ids = ids
def matches(self, id):
'See INameFindFilter'
return id in self._ids
class SimpleInterfacesFindFilter(object):
"""Filter objects on the provided interfaces"""
implements(IObjectFindFilter)
def __init__(self, *interfaces):
self.interfaces = interfaces
def matches(self, object):
for iface in self.interfaces:
if iface.providedBy(object):
return True
return False
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""The standard Zope Folder.
$Id: folder.py 96116 2009-02-05 00:47:40Z tseaver $
"""
__docformat__ = 'restructuredtext'
from BTrees.OOBTree import OOBTree
from persistent import Persistent
from zope.container.interfaces import IContainer, IContentContainer
from zope.container.contained import Contained, setitem, uncontained
from zope.interface import implements, directlyProvides
# XXX This container implementation is really only used by
# zope.site.folder.Folder. Please do not use it.
# XXX Check whether this IContainer implementation cannot really
# be replaced by the BTreeContainer.
class Folder(Persistent, Contained):
"""The standard Zope Folder implementation."""
implements(IContentContainer)
def __init__(self):
self.data = OOBTree()
def keys(self):
"""Return a sequence-like object containing the names
associated with the objects that appear in the folder
"""
return self.data.keys()
def __iter__(self):
return iter(self.data.keys())
def values(self):
"""Return a sequence-like object containing the objects that
appear in the folder.
"""
return self.data.values()
def items(self):
"""Return a sequence-like object containing tuples of the form
(name, object) for the objects that appear in the folder.
"""
return self.data.items()
def __getitem__(self, name):
"""Return the named object, or raise ``KeyError`` if the object
is not found.
"""
return self.data[name]
def get(self, name, default=None):
"""Return the named object, or the value of the `default`
argument if the object is not found.
"""
return self.data.get(name, default)
def __contains__(self, name):
"""Return true if the named object appears in the folder."""
return self.data.has_key(name)
def __len__(self):
"""Return the number of objects in the folder."""
return len(self.data)
def __setitem__(self, name, object):
"""Add the given object to the folder under the given name."""
if not (isinstance(name, str) or isinstance(name, unicode)):
raise TypeError("Name must be a string rather than a %s" %
name.__class__.__name__)
try:
unicode(name)
except UnicodeError:
raise TypeError("Non-unicode names must be 7-bit-ascii only")
if not name:
raise TypeError("Name must not be empty")
if name in self.data:
raise KeyError("name, %s, is already in use" % name)
setitem(self, self.data.__setitem__, name, object)
def __delitem__(self, name):
"""Delete the named object from the folder. Raises a KeyError
if the object is not found."""
uncontained(self.data[name], self, name)
del self.data[name]
##############################################################################
#
# Copyright (c) 2003 Zope Corporation 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.
#
##############################################################################
"""Customization of zope.i18n for the Zope application server
$Id: i18n.py 73547 2007-03-25 09:03:04Z dobe $
"""
__docformat__ = 'restructuredtext'
# import this as _ to create i18n messages in the zope domain
from zope.i18nmessageid import MessageFactory
ZopeMessageFactory = MessageFactory('zope')
##############################################################################
#
# Copyright (c) 2003 Zope Corporation 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.
#
##############################################################################
"""Container-related interfaces
$Id: interfaces.py 100045 2009-05-17 17:41:52Z chrism $
"""
__docformat__ = 'restructuredtext'
from zope.interface import Interface, Invalid
from zope.interface.common.mapping import IItemMapping
from zope.interface.common.mapping import IReadMapping, IEnumerableMapping
from zope.location.interfaces import ILocation
from zope.schema import Set
from zope.lifecycleevent.interfaces import IObjectModifiedEvent
# the following imports provide backwards compatibility for consumers;
# do not remove them
from zope.lifecycleevent.interfaces import IObjectMovedEvent
from zope.lifecycleevent.interfaces import IObjectAddedEvent
from zope.lifecycleevent.interfaces import IObjectRemovedEvent
from zope.location.interfaces import IContained
# /end backwards compatibility imports
from zope.container.i18n import ZopeMessageFactory as _
class DuplicateIDError(KeyError):
pass
class ContainerError(Exception):
"""An error of a container with one of its components."""
class InvalidContainerType(Invalid, TypeError):
"""The type of a container is not valid."""
class InvalidItemType(Invalid, TypeError):
"""The type of an item is not valid."""
class InvalidType(Invalid, TypeError):
"""The type of an object is not valid."""
class IItemContainer(IItemMapping):
"""Minimal readable container."""
class ISimpleReadContainer(IItemContainer, IReadMapping):
"""Readable content containers."""
class IReadContainer(ISimpleReadContainer, IEnumerableMapping):
"""Readable containers that can be enumerated."""
class IWriteContainer(Interface):
"""An interface for the write aspects of a container."""
def __setitem__(name, object):
"""Add the given `object` to the container under the given name.
Raises a ``TypeError`` if the key is not a unicode or ascii string.
Raises a ``ValueError`` if the key is empty, or if the key contains
a character which is not allowed in an object name.
Raises a ``KeyError`` if the key violates a uniqueness constraint.
The container might choose to add a different object than the
one passed to this method.
If the object doesn't implement `IContained`, then one of two
things must be done:
1. If the object implements `ILocation`, then the `IContained`
interface must be declared for the object.
2. Otherwise, a `ContainedProxy` is created for the object and
stored.
The object's `__parent__` and `__name__` attributes are set to the
container and the given name.
If the old parent was ``None``, then an `IObjectAddedEvent` is
generated, otherwise, an `IObjectMovedEvent` is generated. An
`IContainerModifiedEvent` is generated for the container.
If the object replaces another object, then the old object is
deleted before the new object is added, unless the container
vetos the replacement by raising an exception.
If the object's `__parent__` and `__name__` were already set to
the container and the name, then no events are generated and
no hooks. This allows advanced clients to take over event
generation.
"""
def __delitem__(name):
"""Delete the named object from the container.
Raises a ``KeyError`` if the object is not found.
If the deleted object's `__parent__` and `__name__` match the
container and given name, then an `IObjectRemovedEvent` is
generated and the attributes are set to ``None``. If the object
can be adapted to `IObjectMovedEvent`, then the adapter's
`moveNotify` method is called with the event.
Unless the object's `__parent__` and `__name__` attributes were
initially ``None``, generate an `IContainerModifiedEvent` for the
container.
If the object's `__parent__` and `__name__` were already set to
``None``, then no events are generated. This allows advanced
clients to take over event generation.
"""
class IItemWriteContainer(IWriteContainer, IItemContainer):
"""A write container that also supports minimal reads."""
class IContainer(IReadContainer, IWriteContainer):
"""Readable and writable content container."""
class IContentContainer(IContainer):
"""A container that is to be used as a content type."""
class IBTreeContainer(IContainer):
"""Container that supports BTree semantics for some methods."""
def items(key=None):
"""Return an iterator over the key-value pairs in the container.
If ``None`` is passed as `key`, this method behaves as if no argument
were passed; exactly as required for ``IContainer.items()``.
If `key` is in the container, the first item provided by the iterator
will correspond to that key. Otherwise, the first item will be for
the key that would come next if `key` were in the container.
"""
def keys(key=None):
"""Return an iterator over the keys in the container.
If ``None`` is passed as `key`, this method behaves as if no argument
were passed; exactly as required for ``IContainer.keys()``.
If `key` is in the container, the first key provided by the iterator
will be that key. Otherwise, the first key will be the one that would
come next if `key` were in the container.
"""
def values(key=None):
"""Return an iterator over the values in the container.
If ``None`` is passed as `key`, this method behaves as if no argument
were passed; exactly as required for ``IContainer.values()``.
If `key` is in the container, the first value provided by the iterator
will correspond to that key. Otherwise, the first value will be for
the key that would come next if `key` were in the container.
"""
class IOrdered(Interface):
"""Objects whose contents are maintained in order."""
def updateOrder(order):
"""Revise the order of keys, replacing the current ordering.
order is a list or a tuple containing the set of existing keys in
the new order. `order` must contain ``len(keys())`` items and cannot
contain duplicate keys.
Raises ``TypeError`` if order is not a tuple or a list.
Raises ``ValueError`` if order contains an invalid set of keys.
"""
class IOrderedContainer(IOrdered, IContainer):
"""Containers whose contents are maintained in order."""
class IContainerNamesContainer(IContainer):
"""Containers that always choose names for their items."""
class IReservedNames(Interface):
"""A sequence of names that are reserved for that container"""
reservedNames = Set(
title=_(u'Reserved Names'),
description=_(u'Names that are not allowed for addable content'),
required=True,
)
class NameReserved(ValueError):
__doc__ = _("""The name is reserved for this container""")
##############################################################################
# Adding objects
class UnaddableError(ContainerError):
"""An object cannot be added to a container."""
def __init__(self, container, obj, message=""):
self.container = container
self.obj = obj
self.message = message and ": %s" % message
def __str__(self):
return ("%(obj)s cannot be added "
"to %(container)s%(message)s" % self.__dict__)
class INameChooser(Interface):
def checkName(name, object):
"""Check whether an object name is valid.
Raises a user error if the name is not valid.
"""
def chooseName(name, object):
"""Choose a unique valid name for the object.
The given name and object may be taken into account when
choosing the name.
chooseName is expected to always choose a valid name (that would pass
the checkName test) and never raise an error.
"""
##############################################################################
# Modifying containers
class IContainerModifiedEvent(IObjectModifiedEvent):
"""The container has been modified.
This event is specific to "containerness" modifications, which means
addition, removal or reordering of sub-objects.
"""
##############################################################################
# Finding objects
class IFind(Interface):
"""
Find support for containers.
"""
def find(id_filters=None, object_filters=None):
"""Find object that matches all filters in all sub-objects.
This container itself is not included.
"""
class IObjectFindFilter(Interface):
def matches(object):
"""Return True if the object matches the filter criteria."""
class IIdFindFilter(Interface):
def matches(id):
"""Return True if the id matches the filter criteria."""
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Ordered container implementation.
$Id: ordered.py 99988 2009-05-15 22:10:08Z pcardune $
"""
__docformat__ = 'restructuredtext'
from zope.container.interfaces import IOrderedContainer
from zope.interface import implements
from persistent import Persistent
from persistent.dict import PersistentDict
from persistent.list import PersistentList
from types import StringTypes, TupleType, ListType
from zope.container.contained import Contained, setitem, uncontained
from zope.container.contained import notifyContainerModified
class OrderedContainer(Persistent, Contained):
""" `OrderedContainer` maintains entries' order as added and moved.
>>> oc = OrderedContainer()
>>> int(IOrderedContainer.providedBy(oc))
1
>>> len(oc)
0
"""
implements(IOrderedContainer)
def __init__(self):
self._data = PersistentDict()
self._order = PersistentList()
def keys(self):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> oc.keys()
[]
>>> oc['foo'] = 'bar'
>>> oc.keys()
['foo']
>>> oc['baz'] = 'quux'
>>> oc.keys()
['foo', 'baz']
>>> int(len(oc._order) == len(oc._data))
1
"""
return self._order[:]
def __iter__(self):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> oc.keys()
[]
>>> oc['foo'] = 'bar'
>>> oc['baz'] = 'quux'
>>> [i for i in oc]
['foo', 'baz']
>>> int(len(oc._order) == len(oc._data))
1
"""
return iter(self.keys())
def __getitem__(self, key):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> oc['foo'] = 'bar'
>>> oc['foo']
'bar'
"""
return self._data[key]
def get(self, key, default=None):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> oc['foo'] = 'bar'
>>> oc.get('foo')
'bar'
>>> oc.get('funky', 'No chance, dude.')
'No chance, dude.'
"""
return self._data.get(key, default)
def values(self):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> oc.keys()
[]
>>> oc['foo'] = 'bar'
>>> oc.values()
['bar']
>>> oc['baz'] = 'quux'
>>> oc.values()
['bar', 'quux']
>>> int(len(oc._order) == len(oc._data))
1
"""
return [self._data[i] for i in self._order]
def __len__(self):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> int(len(oc) == 0)
1
>>> oc['foo'] = 'bar'
>>> int(len(oc) == 1)
1
"""
return len(self._data)
def items(self):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> oc.keys()
[]
>>> oc['foo'] = 'bar'
>>> oc.items()
[('foo', 'bar')]
>>> oc['baz'] = 'quux'
>>> oc.items()
[('foo', 'bar'), ('baz', 'quux')]
>>> int(len(oc._order) == len(oc._data))
1
"""
return [(i, self._data[i]) for i in self._order]
def __contains__(self, key):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> oc['foo'] = 'bar'
>>> int('foo' in oc)
1
>>> int('quux' in oc)
0
"""
return self._data.has_key(key)
has_key = __contains__
def __setitem__(self, key, object):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> oc.keys()
[]
>>> oc['foo'] = 'bar'
>>> oc._order
['foo']
>>> oc['baz'] = 'quux'
>>> oc._order
['foo', 'baz']
>>> int(len(oc._order) == len(oc._data))
1
>>> oc['foo'] = 'baz'
Traceback (most recent call last):
...
KeyError: u'foo'
>>> oc._order
['foo', 'baz']
"""
existed = self._data.has_key(key)
bad = False
if isinstance(key, StringTypes):
try:
unicode(key)
except UnicodeError:
bad = True
else:
bad = True
if bad:
raise TypeError("'%s' is invalid, the key must be an "
"ascii or unicode string" % key)
if len(key) == 0:
raise ValueError("The key cannot be an empty string")
# We have to first update the order, so that the item is available,
# otherwise most API functions will lie about their available values
# when an event subscriber tries to do something with the container.
if not existed:
self._order.append(key)
# This function creates a lot of events that other code listens to.
try:
setitem(self, self._data.__setitem__, key, object)
except Exception, e:
if not existed:
self._order.remove(key)
raise e
return key
def __delitem__(self, key):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> oc.keys()
[]
>>> oc['foo'] = 'bar'
>>> oc['baz'] = 'quux'
>>> oc['zork'] = 'grue'
>>> oc.items()
[('foo', 'bar'), ('baz', 'quux'), ('zork', 'grue')]
>>> int(len(oc._order) == len(oc._data))
1
>>> del oc['baz']
>>> oc.items()
[('foo', 'bar'), ('zork', 'grue')]
>>> int(len(oc._order) == len(oc._data))
1
"""
uncontained(self._data[key], self, key)
del self._data[key]
self._order.remove(key)
def updateOrder(self, order):
""" See `IOrderedContainer`.
>>> oc = OrderedContainer()
>>> oc['foo'] = 'bar'
>>> oc['baz'] = 'quux'
>>> oc['zork'] = 'grue'
>>> oc.keys()
['foo', 'baz', 'zork']
>>> oc.updateOrder(['baz', 'foo', 'zork'])
>>> oc.keys()
['baz', 'foo', 'zork']
>>> oc.updateOrder(['baz', 'zork', 'foo'])
>>> oc.keys()
['baz', 'zork', 'foo']
>>> oc.updateOrder(['baz', 'zork', 'foo'])
>>> oc.keys()
['baz', 'zork', 'foo']
>>> oc.updateOrder(('zork', 'foo', 'baz'))
>>> oc.keys()
['zork', 'foo', 'baz']
>>> oc.updateOrder(['baz', 'zork'])
Traceback (most recent call last):
...
ValueError: Incompatible key set.
>>> oc.updateOrder(['foo', 'bar', 'baz', 'quux'])
Traceback (most recent call last):
...
ValueError: Incompatible key set.
>>> oc.updateOrder(1)
Traceback (most recent call last):
...
TypeError: order must be a tuple or a list.
>>> oc.updateOrder('bar')
Traceback (most recent call last):
...
TypeError: order must be a tuple or a list.
>>> oc.updateOrder(['baz', 'zork', 'quux'])
Traceback (most recent call last):
...
ValueError: Incompatible key set.
>>> del oc['baz']
>>> del oc['zork']
>>> del oc['foo']
>>> len(oc)
0
"""
if not isinstance(order, ListType) and \
not isinstance(order, TupleType):
raise TypeError('order must be a tuple or a list.')
if len(order) != len(self._order):
raise ValueError("Incompatible key set.")
was_dict = {}
will_be_dict = {}
new_order = PersistentList()
for i in range(len(order)):
was_dict[self._order[i]] = 1
will_be_dict[order[i]] = 1
new_order.append(order[i])
if will_be_dict != was_dict:
raise ValueError("Incompatible key set.")
self._order = new_order
notifyContainerModified(self)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Sample container implementation.
This is primarily for testing purposes.
It might be useful as a mix-in for some classes, but many classes will
need a very different implementation.
$Id: sample.py 95341 2009-01-28 15:59:18Z wosc $
"""
__docformat__ = 'restructuredtext'
from zope.container.interfaces import IContainer
from zope.interface import implements
from zope.container.contained import Contained, setitem, uncontained
class SampleContainer(Contained):
"""Sample container implementation suitable for testing.
It is not suitable, directly as a base class unless the subclass
overrides `_newContainerData` to return a persistent mapping object.
"""
implements(IContainer)
def __init__(self):
self.__data = self._newContainerData()
def _newContainerData(self):
"""Construct an item-data container
Subclasses should override this if they want different data.
The value returned is a mapping object that also has `get`,
`has_key`, `keys`, `items`, and `values` methods.
"""
return {}
def keys(self):
'''See interface `IReadContainer`'''
return self.__data.keys()
def __iter__(self):
return iter(self.__data)
def __getitem__(self, key):
'''See interface `IReadContainer`'''
return self.__data[key]
def get(self, key, default=None):
'''See interface `IReadContainer`'''
return self.__data.get(key, default)
def values(self):
'''See interface `IReadContainer`'''
return self.__data.values()
def __len__(self):
'''See interface `IReadContainer`'''
return len(self.__data)
def items(self):
'''See interface `IReadContainer`'''
return self.__data.items()
def __contains__(self, key):
'''See interface `IReadContainer`'''
return self.__data.has_key(key)
has_key = __contains__
def __setitem__(self, key, object):
'''See interface `IWriteContainer`'''
setitem(self, self.__data.__setitem__, key, object)
def __delitem__(self, key):
'''See interface `IWriteContainer`'''
uncontained(self.__data[key], self, key)
del self.__data[key]
##############################################################################
#
# Copyright (c) 2002 Zope Corporation 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.
#
##############################################################################
"""Adapters that give the size of an object.
$Id: size.py 95341 2009-01-28 15:59:18Z wosc $
"""
__docformat__ = 'restructuredtext'
from zope.container.i18n import ZopeMessageFactory as _
from zope.size.interfaces import ISized
from zope.interface import implements
class ContainerSized(object):
implements(ISized)
def __init__(self, container):
self._container = container
def sizeForSorting(self):
"""See `ISized`"""
return ('item', len(self._container))
def sizeForDisplay(self):
"""See `ISized`"""
num_items = len(self._container)
if num_items == 1:
return _('1 item')
return _('${items} items', mapping={'items': str(num_items)})
##############################################################################
#
# Copyright (c) 2002 Zope Corporation 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.
#
##############################################################################
"""Unit test logic for setting up and tearing down basic infrastructure
$Id: placelesssetup.py 95341 2009-01-28 15:59:18Z wosc $
"""
from zope import component
from zope.component.testing import PlacelessSetup as CAPlacelessSetup
from zope.component.eventtesting import PlacelessSetup as EventPlacelessSetup
from zope.traversing.interfaces import ITraversable, IContainmentRoot
import zope.traversing.testing
import zope.interface
from zope.container.interfaces import IWriteContainer, INameChooser
from zope.container.contained import NameChooser
from zope.container.interfaces import ISimpleReadContainer
from zope.container.traversal import ContainerTraversable
from zope.container.sample import SampleContainer
# XXX we would like to swap the names of the *PlacelessSetup classes
# in here as that would seem to follow the convention better, but
# unfortunately that would break compatibility with zope.app.testing
# (which expects this PlacelessSetup) so it will have to wait.
class PlacelessSetup(object):
def setUp(self):
component.provideAdapter(NameChooser, (IWriteContainer,), INameChooser)
class ContainerPlacelessSetup(CAPlacelessSetup,
EventPlacelessSetup,
PlacelessSetup):
def setUp(self, doctesttest=None):
CAPlacelessSetup.setUp(self)
EventPlacelessSetup.setUp(self)
PlacelessSetup.setUp(self)
ps = ContainerPlacelessSetup()
setUp = ps.setUp
def tearDown():
tearDown_ = ps.tearDown
def tearDown(doctesttest=None):
tearDown_()
return tearDown
tearDown = tearDown()
del ps
class ContainerPlacefulSetup(ContainerPlacelessSetup):
def setUp(self, doctesttest=None):
ContainerPlacelessSetup.setUp(self, doctesttest)
zope.traversing.testing.setUp()
component.provideAdapter(ContainerTraversable,
(ISimpleReadContainer,), ITraversable)
def tearDown(self, docttesttest=None):
ContainerPlacelessSetup.tearDown(self)
def buildFolders(self):
root = self.rootFolder = SampleContainer()
zope.interface.directlyProvides(root, IContainmentRoot)
root[u'folder1'] = SampleContainer()
root[u'folder1'][u'folder1_1'] = SampleContainer()
root[u'folder1'][u'folder1_1'][u'folder1_1_1'] = SampleContainer()
root[u'folder2'] = SampleContainer()
root[u'folder2'][u'folder2_1'] = SampleContainer()
root[u'folder2'][u'folder2_1'][u'folder2_1_1'] = SampleContainer()
#
# This file is necessary to make this directory a package.
===============================
File representation for folders
===============================
Folders can be represented in file-system-like protocols (e.g. FTP). An
adapter abstracts some internals away and adds support for accessing the
'++etc++site' folder from those protocols.
>>> from zope.container.folder import Folder
>>> from zope.container.directory import ReadDirectory
>>> folder = Folder()
The new folder isn't a site manager and doesn't have any entries:
>>> fs_folder = ReadDirectory(folder)
>>> list(fs_folder.keys())
[]
>>> fs_folder.get('test', )
>>> fs_folder['test']
Traceback (most recent call last):
KeyError: 'test'
>>> list(fs_folder.__iter__())
[]
>>> fs_folder.values()
[]
>>> len(fs_folder)
0
>>> fs_folder.items()
[]
>>> 'test' in fs_folder
False
This is a short regression test for #728: we get a KeyError when trying to
access non-existing entries:
>>> from zope.security.proxy import ProxyFactory
>>> from zope.security.checker import NamesChecker
>>> proxied_folder = ProxyFactory(fs_folder, NamesChecker(('get',)))
>>> proxied_fs_folder = ReadDirectory(proxied_folder)
>>> print proxied_fs_folder['i dont exist']
Traceback (most recent call last):
KeyError: 'i dont exist'
<configure
xmlns="http://namespaces.zope.org/zope">
<include package="zope.component" file="meta.zcml"/>
<include package="zope.security" file="meta.zcml" />
<include package="zope.security" file="permissions.zcml" />
<include package="zope.container"/>
</configure>
##############################################################################
#
# Copyright (c) 2004 Zope Corporation 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.
#
##############################################################################
"""BTree Container Tests
$Id: test_btree.py 111713 2010-04-30 20:43:09Z hannosch $
"""
from doctest import DocTestSuite
from unittest import TestCase, main, makeSuite, TestSuite
from zope.interface.verify import verifyObject
from zope.component.testing import setUp, tearDown
from zope.container.tests.test_icontainer import TestSampleContainer
from zope.container.btree import BTreeContainer
from zope.container.interfaces import IBTreeContainer
class TestBTreeContainer(TestSampleContainer, TestCase):
def makeTestObject(self):
return BTreeContainer()
class TestBTreeSpecials(TestCase):
def testStoredLength(self):
# This is lazy for backward compatibility. If the len is not
# stored already we set it to the length of the underlying
# btree.
bc = BTreeContainer()
self.assertEqual(bc.__dict__['_BTreeContainer__len'](), 0)
del bc.__dict__['_BTreeContainer__len']
self.failIf(bc.__dict__.has_key('_BTreeContainer__len'))
bc['1'] = 1
self.assertEqual(len(bc), 1)
self.assertEqual(bc.__dict__['_BTreeContainer__len'](), 1)
# The tests which follow test the additional signatures and declarations
# for the BTreeContainer that allow it to provide the IBTreeContainer
# interface.
def testBTreeContainerInterface(self):
bc = BTreeContainer()
self.assert_(verifyObject(IBTreeContainer, bc))
self.checkIterable(bc.items())
self.checkIterable(bc.keys())
self.checkIterable(bc.values())
def testEmptyItemsWithArg(self):
bc = BTreeContainer()
self.assertEqual(list(bc.items(None)), list(bc.items()))
self.assertEqual(list(bc.items("")), [])
self.assertEqual(list(bc.items("not-there")), [])
self.checkIterable(bc.items(None))
self.checkIterable(bc.items(""))
self.checkIterable(bc.items("not-there"))
def testEmptyKeysWithArg(self):
bc = BTreeContainer()
self.assertEqual(list(bc.keys(None)), list(bc.keys()))
self.assertEqual(list(bc.keys("")), [])
self.assertEqual(list(bc.keys("not-there")), [])
self.checkIterable(bc.keys(None))
self.checkIterable(bc.keys(""))
self.checkIterable(bc.keys("not-there"))
def testEmptyValuesWithArg(self):
bc = BTreeContainer()
self.assertEqual(list(bc.values(None)), list(bc.values()))
self.assertEqual(list(bc.values("")), [])
self.assertEqual(list(bc.values("not-there")), [])
self.checkIterable(bc.values(None))
self.checkIterable(bc.values(""))
self.checkIterable(bc.values("not-there"))
def testNonemptyItemsWithArg(self):
bc = BTreeContainer()
bc["0"] = 1
bc["1"] = 2
bc["2"] = 3
self.assertEqual(list(bc.items(None)), list(bc.items()))
self.assertEqual(list(bc.items("")), [("0", 1), ("1", 2), ("2", 3)])
self.assertEqual(list(bc.items("3")), [])
self.assertEqual(list(bc.items("2.")), [])
self.assertEqual(list(bc.items("2")), [("2", 3)])
self.assertEqual(list(bc.items("1.")), [("2", 3)])
self.assertEqual(list(bc.items("1")), [("1", 2), ("2", 3)])
self.assertEqual(list(bc.items("0.")), [("1", 2), ("2", 3)])
self.assertEqual(list(bc.items("0")), [("0", 1), ("1", 2), ("2", 3)])
self.checkIterable(bc.items(None))
self.checkIterable(bc.items(""))
self.checkIterable(bc.items("0."))
self.checkIterable(bc.items("3"))
def testNonemptyKeysWithArg(self):
bc = BTreeContainer()
bc["0"] = 1
bc["1"] = 2
bc["2"] = 3
self.assertEqual(list(bc.keys(None)), list(bc.keys()))
self.assertEqual(list(bc.keys("")), ["0", "1", "2"])
self.assertEqual(list(bc.keys("3")), [])
self.assertEqual(list(bc.keys("2.")), [])
self.assertEqual(list(bc.keys("2")), ["2"])
self.assertEqual(list(bc.keys("1.")), ["2"])
self.assertEqual(list(bc.keys("1")), ["1", "2"])
self.assertEqual(list(bc.keys("0.")), ["1", "2"])
self.assertEqual(list(bc.keys("0")), ["0", "1", "2"])
self.checkIterable(bc.keys(None))
self.checkIterable(bc.keys(""))
self.checkIterable(bc.keys("0."))
self.checkIterable(bc.keys("3"))
def testNonemptyValueWithArg(self):
bc = BTreeContainer()
bc["0"] = 1
bc["1"] = 2
bc["2"] = 3
self.assertEqual(list(bc.values(None)), list(bc.values()))
self.assertEqual(list(bc.values("")), [1, 2, 3])
self.assertEqual(list(bc.values("3")), [])
self.assertEqual(list(bc.values("2.")), [])
self.assertEqual(list(bc.values("2")), [3])
self.assertEqual(list(bc.values("1.")), [3])
self.assertEqual(list(bc.values("1")), [2, 3])
self.assertEqual(list(bc.values("0.")), [2, 3])
self.assertEqual(list(bc.values("0")), [1, 2, 3])
self.checkIterable(bc.values(None))
self.checkIterable(bc.values(""))
self.checkIterable(bc.values("0."))
self.checkIterable(bc.values("3"))
def testCorrectLengthWhenAddingExistingItem(self):
"""
for bug #175388
"""
bc = BTreeContainer()
bc[u'x'] = object()
self.assertEqual(len(bc), 1)
bc[u'x'] = bc[u'x']
self.assertEqual(len(bc), 1)
self.assertEqual(list(bc), [u'x'])
def checkIterable(self, iterable):
it = iter(iterable)
self.assert_(callable(it.next))
self.assert_(callable(it.__iter__))
self.assert_(iter(it) is it)
# Exhaust the iterator:
first_time = list(it)
self.assertRaises(StopIteration, it.next)
# Subsequent iterations will return the same values:
self.assertEqual(list(iterable), first_time)
self.assertEqual(list(iterable), first_time)
def test_suite():
return TestSuite((
makeSuite(TestBTreeContainer),
makeSuite(TestBTreeSpecials),
DocTestSuite('zope.container.btree',
setUp=setUp,
tearDown=tearDown),
))
if __name__=='__main__':
main(defaultTest='test_suite')
##############################################################################
#
# Copyright (c) 2003 Zope Corporation 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.
#
##############################################################################
"""Container constraint tests
$Id: test_constraints.py 111713 2010-04-30 20:43:09Z hannosch $
"""
import doctest
import unittest
from zope.testing import module
def setUp(test):
module.setUp(test, 'zope.container.constraints_txt')
def tearDown(test):
module.tearDown(test, 'zope.container.constraints_txt')
def test_suite():
return unittest.TestSuite((
doctest.DocTestSuite('zope.container.constraints'),
doctest.DocFileSuite('../constraints.txt',
setUp=setUp, tearDown=tearDown),
))
if __name__ == '__main__': unittest.main()
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Contained Tests
$Id: test_contained.py 111713 2010-04-30 20:43:09Z hannosch $
"""
import doctest
import gc
import unittest
from ZODB.DemoStorage import DemoStorage
from ZODB.DB import DB
import transaction
from persistent import Persistent
import zope.interface
import zope.component
from zope.container.contained import ContainedProxy, NameChooser
from zope.container.sample import SampleContainer
from zope.container import testing
from zope.container.interfaces import NameReserved, IContainer, IReservedNames
class MyOb(Persistent):
pass
def test_basic_proxy_attribute_management_and_picklability():
"""Contained-object proxy
This is a picklable proxy that can be put around objects that
don't implement IContained.
>>> l = [1, 2, 3]
>>> p = ContainedProxy(l)
>>> p.__parent__ = 'Dad'
>>> p.__name__ = 'p'
>>> p
[1, 2, 3]
>>> p.__parent__
'Dad'
>>> p.__name__
'p'
>>> import pickle
>>> p2 = pickle.loads(pickle.dumps(p))
>>> p2
[1, 2, 3]
>>> p2.__parent__
'Dad'
>>> p2.__name__
'p'
"""
def test_basic_persistent_w_non_persistent_proxied():
"""
>>> p = ContainedProxy([1])
>>> p.__parent__ = 2
>>> p.__name__ = 'test'
>>> db = DB(DemoStorage('test_storage'))
>>> c = db.open()
>>> c.root()['p'] = p
>>> transaction.commit()
>>> c2 = db.open()
>>> p2 = c2.root()['p']
>>> p2
[1]
>>> p2.__parent__
2
>>> p2.__name__
'test'
>>> p2._p_changed
0
>>> p2._p_deactivate()
>>> p2._p_changed
>>> p2.__name__
'test'
>>> db.close()
"""
def test_declarations_on_ContainedProxy():
r"""
It is possible to make declarations on ContainedProxy objects.
>>> class I1(zope.interface.Interface):
... pass
>>> class C(object):
... zope.interface.implements(I1)
>>> c = C()
>>> p = ContainedProxy(c)
ContainedProxy provides no interfaces on it's own:
>>> tuple(zope.interface.providedBy(ContainedProxy))
()
It implements IContained and IPersistent:
>>> tuple(zope.interface.implementedBy(ContainedProxy))
(<InterfaceClass zope.location.interfaces.IContained>,
<InterfaceClass persistent.interfaces.IPersistent>)
A proxied object has IContainer, in addition to what the unproxied
object has:
>>> tuple(zope.interface.providedBy(p))
(<InterfaceClass zope.container.tests.test_contained.I1>,
<InterfaceClass zope.location.interfaces.IContained>,
<InterfaceClass persistent.interfaces.IPersistent>)
>>> class I2(zope.interface.Interface):
... pass
>>> zope.interface.directlyProvides(c, I2)
>>> tuple(zope.interface.providedBy(p))
(<InterfaceClass zope.container.tests.test_contained.I2>,
<InterfaceClass zope.container.tests.test_contained.I1>,
<InterfaceClass zope.location.interfaces.IContained>,
<InterfaceClass persistent.interfaces.IPersistent>)
We can declare interfaces through the proxy:
>>> class I3(zope.interface.Interface):
... pass
>>> zope.interface.directlyProvides(p, I3)
>>> tuple(zope.interface.providedBy(p))
(<InterfaceClass zope.container.tests.test_contained.I3>,
<InterfaceClass zope.container.tests.test_contained.I1>,
<InterfaceClass zope.location.interfaces.IContained>,
<InterfaceClass persistent.interfaces.IPersistent>)
"""
def test_basic_persistent_w_persistent_proxied():
"""
Here, we'll verify that shared references work and
that updates to both the proxies and the proxied objects
are made correctly.
----------------------
| |
parent other
| /
ob <--------------
Here we have an object, parent, that contains ob. There is another
object, other, that has a non-container reference to ob.
>>> parent = MyOb()
>>> parent.ob = ContainedProxy(MyOb())
>>> parent.ob.__parent__ = parent
>>> parent.ob.__name__ = 'test'
>>> other = MyOb()
>>> other.ob = parent.ob
We can change ob through either parent or other
>>> parent.ob.x = 1
>>> other.ob.y = 2
Now we'll save the data:
>>> db = DB(DemoStorage('test_storage'))
>>> c1 = db.open()
>>> c1.root()['parent'] = parent
>>> c1.root()['other'] = other
>>> transaction.commit()
We'll open a second connection and verify that we have the data we
expect:
>>> c2 = db.open()
>>> p2 = c2.root()['parent']
>>> p2.ob.__parent__ is p2
1
>>> p2.ob.x
1
>>> p2.ob.y
2
>>> o2 = c2.root()['other']
>>> o2.ob is p2.ob
1
>>> o2.ob is p2.ob
1
>>> o2.ob.__name__
'test'
Now we'll change things around a bit. We'll move things around
a bit. We'll also add an attribute to ob
>>> o2.ob.__name__ = 'test 2'
>>> o2.ob.__parent__ = o2
>>> o2.ob.z = 3
>>> p2.ob.__parent__ is p2
0
>>> p2.ob.__parent__ is o2
1
And save the changes:
>>> transaction.commit()
Now we'll reopen the first connection and verify that we can see
the changes:
>>> c1.close()
>>> c1 = db.open()
>>> p2 = c1.root()['parent']
>>> p2.ob.__name__
'test 2'
>>> p2.ob.z
3
>>> p2.ob.__parent__ is c1.root()['other']
1
>>> db.close()
"""
def test_proxy_cache_interaction():
"""Test to make sure the proxy properly interacts with the object cache
Persistent objects are their own weak refs. Thier deallocators
need to notify their connection's cache that their object is being
deallocated, so that it is removed from the cache.
>>> from ZODB.tests.util import DB
>>> db = DB()
>>> db.setCacheSize(5)
>>> conn = db.open()
>>> conn.root()['p'] = ContainedProxy(None)
We need to create some filler objects to push our proxy out of the cache:
>>> for i in range(10):
... conn.root()[i] = MyOb()
>>> transaction.commit()
Let's get the oid of our proxy:
>>> oid = conn.root()['p']._p_oid
Now, we'll access the filler object's:
>>> x = [getattr(conn.root()[i], 'x', 0) for i in range(10)]
We've also accessed the root object. If we garbage-collect the
cache:
>>> conn._cache.incrgc()
Then the root object will still be active, because it was accessed
recently:
>>> conn.root()._p_changed
0
And the proxy will be in the cache, because it's refernced from
the root object:
>>> conn._cache.get(oid) is not None
True
But it's a ghost:
>>> conn.root()['p']._p_changed
If we deactivate the root object:
>>> conn.root()._p_deactivate()
Then we'll release the last reference to the proxy and it should
no longer be in the cache. To be sure, we'll call gc:
>>> x = gc.collect()
>>> conn._cache.get(oid) is not None
False
"""
def test_ContainedProxy_instances_have_no_instance_dictionaries():
"""Make sure that proxies don't introduce extra instance dictionaries
>>> from zope.container.contained import ContainedProxy
>>> class C:
... pass
>>> c = C()
>>> c.x = 1
>>> c.__dict__
{'x': 1}
>>> p = ContainedProxy(c)
>>> p.__dict__
{'x': 1}
>>> p.y = 3
>>> p.__dict__
{'y': 3, 'x': 1}
>>> c.__dict__
{'y': 3, 'x': 1}
>>> p.__dict__ is c.__dict__
True
"""
class TestNameChooser(unittest.TestCase):
def test_checkName(self):
container = SampleContainer()
container['foo'] = 'bar'
checkName = NameChooser(container).checkName
# invalid type for the name
self.assertRaises(TypeError, checkName, 2, object())
self.assertRaises(TypeError, checkName, [], object())
self.assertRaises(TypeError, checkName, None, object())
self.assertRaises(TypeError, checkName, None, None)
# invalid names
self.assertRaises(ValueError, checkName, '+foo', object())
self.assertRaises(ValueError, checkName, '@foo', object())
self.assertRaises(ValueError, checkName, 'f/oo', object())
self.assertRaises(ValueError, checkName, '', object())
# existing names
self.assertRaises(KeyError, checkName, 'foo', object())
self.assertRaises(KeyError, checkName, u'foo', object())
# correct names
self.assertEqual(True, checkName('2', object()))
self.assertEqual(True, checkName(u'2', object()))
self.assertEqual(True, checkName('other', object()))
self.assertEqual(True, checkName(u'reserved', object()))
self.assertEqual(True, checkName(u'r\xe9served', object()))
# reserved names
class ReservedNames(object):
zope.component.adapts(IContainer)
zope.interface.implements(IReservedNames)
def __init__(self, context):
self.reservedNames = set(('reserved', 'other'))
zope.component.getSiteManager().registerAdapter(ReservedNames)
self.assertRaises(NameReserved, checkName, 'reserved', object())
self.assertRaises(NameReserved, checkName, 'other', object())
self.assertRaises(NameReserved, checkName, u'reserved', object())
self.assertRaises(NameReserved, checkName, u'other', object())
def test_chooseName(self):
container = SampleContainer()
container['foo.old.rst'] = 'rst doc'
nc = NameChooser(container)
# correct name without changes
self.assertEqual(nc.chooseName('foobar.rst', None),
u'foobar.rst')
self.assertEqual(nc.chooseName(u'\xe9', None),
u'\xe9')
# automatically modified named
self.assertEqual(nc.chooseName('foo.old.rst', None),
u'foo.old-2.rst')
self.assertEqual(nc.chooseName('+@+@foo.old.rst', None),
u'foo.old-2.rst')
self.assertEqual(nc.chooseName('+@+@foo/foo+@', None),
u'foo-foo+@')
# empty name
self.assertEqual(nc.chooseName('', None), u'NoneType')
self.assertEqual(nc.chooseName('@+@', []), u'list')
# if the name is not a string it is converted
self.assertEqual(nc.chooseName(None, None), u'None')
self.assertEqual(nc.chooseName(2, None), u'2')
self.assertEqual(nc.chooseName([], None), u'[]')
container['None'] = 'something'
self.assertEqual(nc.chooseName(None, None), u'None-2')
container['None-2'] = 'something'
self.assertEqual(nc.chooseName(None, None), u'None-3')
# even if the given name cannot be converted to unicode
class BadBoy:
def __unicode__(self):
raise Exception
self.assertEqual(nc.chooseName(BadBoy(), set()), u'set')
def test_suite():
return unittest.TestSuite((
doctest.DocTestSuite('zope.container.contained',
setUp=testing.setUp,
tearDown=testing.tearDown),
doctest.DocTestSuite(optionflags=doctest.NORMALIZE_WHITESPACE),
unittest.makeSuite(TestNameChooser),
))
if __name__ == '__main__': unittest.main()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Container Traverser tests.
$Id: test_containertraversable.py 95341 2009-01-28 15:59:18Z wosc $
"""
import unittest
from zope.testing.cleanup import CleanUp
from zope.interface import implements
from zope.traversing.interfaces import TraversalError
from zope.container.traversal import ContainerTraversable
from zope.container.interfaces import IContainer
class Container(object):
implements(IContainer)
def __init__(self, attrs={}, objs={}):
for attr,value in attrs.iteritems():
setattr(self, attr, value)
self.__objs = {}
for name,value in objs.iteritems():
self.__objs[name] = value
def __getitem__(self, name):
return self.__objs[name]
def get(self, name, default=None):
return self.__objs.get(name, default)
def __contains__(self, name):
return self.__objs.has_key(name)
class Test(CleanUp, unittest.TestCase):
def testAttr(self):
# test container path traversal
foo = Container()
bar = Container()
baz = Container()
c = Container({'foo': foo}, {'bar': bar, 'foo': baz})
T = ContainerTraversable(c)
self.failUnless(T.traverse('foo', []) is baz)
self.failUnless(T.traverse('bar', []) is bar)
self.assertRaises(TraversalError , T.traverse, 'morebar', [])
def test_unicode_attr(self):
# test traversal with unicode
voila = Container()
c = Container({}, {u'voil\xe0': voila})
self.failUnless(ContainerTraversable(c).traverse(u'voil\xe0', []) is voila)
def test_suite():
loader = unittest.TestLoader()
return loader.loadTestsFromTestCase(Test)
if __name__ == '__main__':
unittest.TextTestRunner().run(test_suite())
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Container Traverser Tests
$Id: test_containertraverser.py 97680 2009-03-09 07:32:19Z wosc $
"""
import unittest
from zope.interface import Interface, implements
from zope import component
from zope.publisher.interfaces import NotFound, IDefaultViewName
from zope.publisher.browser import TestRequest
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
from zope.container.traversal import ContainerTraverser
from zope.container.interfaces import IReadContainer
from zope.container import testing
class TestContainer(object):
implements(IReadContainer)
def __init__(self, **kw):
for name, value in kw.items():
setattr(self, name , value)
def get(self, name, default=None):
return getattr(self, name, default)
class View(object):
def __init__(self, context, request):
self.context = context
self.request = request
class TraverserTest(testing.ContainerPlacelessSetup, unittest.TestCase):
# The following two methods exist, so that other container traversers can
# use these tests as a base.
def _getTraverser(self, context, request):
return ContainerTraverser(context, request)
def _getContainer(self, **kw):
return TestContainer(**kw)
def setUp(self):
super(TraverserTest, self).setUp()
# Create a small object tree
self.container = self._getContainer()
self.subcontainer = self._getContainer(Foo=self.container)
# Initiate a request
self.request = TestRequest()
# Create the traverser
self.traverser = self._getTraverser(self.subcontainer, self.request)
# Define a simple view for the container
component.provideAdapter(
View, (IReadContainer, IDefaultBrowserLayer), Interface,
name='viewfoo')
def test_itemTraversal(self):
self.assertEqual(
self.traverser.publishTraverse(self.request, 'Foo'),
self.container)
self.assertRaises(
NotFound,
self.traverser.publishTraverse, self.request, 'morebar')
def test_viewTraversal(self):
self.assertEquals(
self.traverser.publishTraverse(self.request, 'viewfoo').__class__,
View)
self.assertEquals(
self.traverser.publishTraverse(self.request, 'Foo'),
self.container)
self.assertRaises(
NotFound,
self.traverser.publishTraverse, self.request, 'morebar')
self.assertRaises(
NotFound,
self.traverser.publishTraverse, self.request, '@@morebar')
def test_browserDefault_without_registration_should_raise(self):
self.assertRaises(component.ComponentLookupError,
self.traverser.browserDefault, self.request)
def test_browserDefault(self):
component.provideAdapter(
'myDefaultView', (Interface, IDefaultBrowserLayer),
IDefaultViewName)
self.assertEquals((self.subcontainer, ('@@myDefaultView',)),
self.traverser.browserDefault(self.request))
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(TraverserTest),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
import unittest
from zope.configuration.xmlconfig import XMLConfig
from zope.interface import implements
from zope.publisher.browser import TestRequest
from zope.publisher.interfaces.browser import IBrowserPublisher
from zope.container.interfaces import IItemContainer
from zope.container.interfaces import ISimpleReadContainer
from zope.container.traversal import ItemTraverser
from zope.container.testing import ContainerPlacelessSetup
class ZCMLDependencies(ContainerPlacelessSetup, unittest.TestCase):
def test_zcml_can_load_with_only_zope_component_meta(self):
# this is just an example. It is supposed to show that the
# configure.zcml file has loaded successfully.
import zope.component
XMLConfig('meta.zcml', zope.component)()
import zope.security
XMLConfig('meta.zcml', zope.security)()
XMLConfig('permissions.zcml', zope.security)()
import zope.container
XMLConfig('configure.zcml', zope.container)()
request = TestRequest()
class SampleItemContainer(object):
implements(IItemContainer)
sampleitemcontainer = SampleItemContainer()
res = zope.component.getMultiAdapter(
(sampleitemcontainer, request), IBrowserPublisher)
self.failUnless(isinstance(res, ItemTraverser))
self.failUnless(res.context is sampleitemcontainer)
class SampleSimpleReadContainer(object):
implements(ISimpleReadContainer)
samplesimplereadcontainer = SampleSimpleReadContainer()
res = zope.component.getMultiAdapter(
(samplesimplereadcontainer, request), IBrowserPublisher)
self.failUnless(isinstance(res, ItemTraverser))
self.failUnless(res.context is samplesimplereadcontainer)
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ZCMLDependencies))
return suite
if __name__ == '__main__':
unittest.main()
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""FS-based directory implementation tests for containers
$Id: test_directory.py 111713 2010-04-30 20:43:09Z hannosch $
"""
import doctest
from unittest import TestCase, TestSuite, main, makeSuite
from zope.container import testing
import zope.container.directory
class Directory(object):
pass
class Test(TestCase):
def test_Cloner(self):
d = Directory()
d.a = 1
clone = zope.container.directory.Cloner(d)('foo')
self.assert_(clone != d)
self.assertEqual(clone.__class__, d.__class__)
def test_suite():
flags = doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE
return TestSuite((
makeSuite(Test),
doctest.DocFileSuite("directory.txt",
setUp=testing.setUp, tearDown=testing.tearDown,
optionflags=flags),
))
if __name__=='__main__':
main(defaultTest='test_suite')
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Find functionality tests
$Id: test_find.py 95341 2009-01-28 15:59:18Z wosc $
"""
from unittest import TestCase, main, makeSuite
from zope.container.interfaces import IReadContainer
from zope.container.interfaces import IObjectFindFilter
from zope.container.find import FindAdapter, SimpleIdFindFilter
from zope.container.find import SimpleInterfacesFindFilter
from zope.interface import implements, Interface, directlyProvides
class FakeContainer(object):
implements(IReadContainer)
def __init__(self, id, objects):
self._id = id
self._objects = objects
def keys(self):
return [object._id for object in self._objects]
def values(self):
return self._objects
def items(self):
return [(object._id, object) for object in self._objects]
def __getitem__(self, id):
for object in self._objects:
if object._id == id:
return object
raise KeyError("Could not find %s" % id)
def get(self, id, default=None):
for object in self._objects:
if object._id == id:
return object
return default
def __contains__(self, id):
for object in self._objects:
if object.id == id:
return True
return False
def __len__(self):
return len(self._objects)
class FakeInterfaceFoo(Interface):
"""Test interface Foo"""
class FakeInterfaceBar(Interface):
"""Test interface Bar"""
class FakeInterfaceSpam(Interface):
"""Test interface Spam"""
class TestObjectFindFilter(object):
implements(IObjectFindFilter)
def __init__(self, count):
self._count = count
def matches(self, object):
if IReadContainer.providedBy(object):
return len(object) == self._count
else:
return False
class Test(TestCase):
def test_idFind(self):
alpha = FakeContainer('alpha', [])
delta = FakeContainer('delta', [])
beta = FakeContainer('beta', [delta])
gamma = FakeContainer('gamma', [])
tree = FakeContainer(
'tree',
[alpha, beta, gamma])
find = FindAdapter(tree)
# some simple searches
result = find.find([SimpleIdFindFilter(['beta'])])
self.assertEquals([beta], result)
result = find.find([SimpleIdFindFilter(['gamma'])])
self.assertEquals([gamma], result)
result = find.find([SimpleIdFindFilter(['delta'])])
self.assertEquals([delta], result)
# we should not find the container we search on
result = find.find([SimpleIdFindFilter(['tree'])])
self.assertEquals([], result)
# search for multiple ids
result = find.find([SimpleIdFindFilter(['alpha', 'beta'])])
self.assertEquals([alpha, beta], result)
result = find.find([SimpleIdFindFilter(['beta', 'delta'])])
self.assertEquals([beta, delta], result)
# search without any filters, find everything
result = find.find([])
self.assertEquals([alpha, beta, delta, gamma], result)
# search for something that doesn't exist
result = find.find([SimpleIdFindFilter(['foo'])])
self.assertEquals([], result)
# find for something that has two ids at the same time,
# can't ever be the case
result = find.find([SimpleIdFindFilter(['alpha']),
SimpleIdFindFilter(['beta'])])
self.assertEquals([], result)
def test_objectFind(self):
alpha = FakeContainer('alpha', [])
delta = FakeContainer('delta', [])
beta = FakeContainer('beta', [delta])
gamma = FakeContainer('gamma', [])
tree = FakeContainer(
'tree',
[alpha, beta, gamma])
find = FindAdapter(tree)
result = find.find(object_filters=[TestObjectFindFilter(0)])
self.assertEquals([alpha, delta, gamma], result)
result = find.find(object_filters=[TestObjectFindFilter(1)])
self.assertEquals([beta], result)
result = find.find(object_filters=[TestObjectFindFilter(2)])
self.assertEquals([], result)
def test_combinedFind(self):
alpha = FakeContainer('alpha', [])
delta = FakeContainer('delta', [])
beta = FakeContainer('beta', [delta])
gamma = FakeContainer('gamma', [])
tree = FakeContainer(
'tree',
[alpha, beta, gamma])
find = FindAdapter(tree)
result = find.find(id_filters=[SimpleIdFindFilter(['alpha'])],
object_filters=[TestObjectFindFilter(0)])
self.assertEquals([alpha], result)
result = find.find(id_filters=[SimpleIdFindFilter(['alpha'])],
object_filters=[TestObjectFindFilter(1)])
self.assertEquals([], result)
def test_interfaceFind(self):
alpha = FakeContainer('alpha', [])
directlyProvides(alpha, FakeInterfaceBar)
delta = FakeContainer('delta', [])
directlyProvides(delta, FakeInterfaceFoo)
beta = FakeContainer('beta', [delta])
directlyProvides(beta, FakeInterfaceSpam)
gamma = FakeContainer('gamma', [])
tree = FakeContainer(
'tree',
[alpha, beta, gamma])
find = FindAdapter(tree)
result = find.find(object_filters=[
SimpleInterfacesFindFilter(FakeInterfaceFoo, FakeInterfaceSpam)])
self.assertEqual([beta, delta], result)
def test_suite():
return makeSuite(Test)
if __name__=='__main__':
main(defaultTest='test_suite')
from unittest import TestCase, makeSuite
from zope.container.folder import Folder
from zope.container.tests.test_icontainer import BaseTestIContainer
from zope.container.tests.test_icontainer import DefaultTestData
class Test(BaseTestIContainer, TestCase):
def makeTestObject(self):
return Folder()
def makeTestData(self):
return DefaultTestData()
def getUnknownKey(self):
return '10'
def getBadKeyTypes(self):
return [None, ['foo'], 1, '\xf3abc']
def test_suite():
return makeSuite(Test)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Test the IContainer interface.
$Id: test_icontainer.py 95417 2009-01-29 11:53:38Z faassen $
"""
from unittest import TestCase, main, makeSuite
from zope.interface.verify import verifyObject
from zope.container.interfaces import IContainer
from zope.container import testing
def DefaultTestData():
return [('3', '0'), ('2', '1'), ('4', '2'), ('6', '3'), ('0', '4'),
('5', '5'), ('1', '6'), ('8', '7'), ('7', '8'), ('9', '9')]
class BaseTestIContainer(testing.ContainerPlacelessSetup):
"""Base test cases for containers.
Subclasses must define a makeTestObject that takes no
arguments and that returns a new empty test container,
and a makeTestData that also takes no arguments and returns
a sequence of (key, value) pairs that may be stored in
the test container. The list must be at least ten items long.
'NoSuchKey' may not be used as a key value in the returned list.
"""
def __setUp(self):
self.__container = container = self.makeTestObject()
self.__data = data = self.makeTestData()
for k, v in data:
container[k] = v
return container, data
############################################################
# Interface-driven tests:
def testIContainerVerify(self):
verifyObject(IContainer, self.makeTestObject())
def test_keys(self):
# See interface IReadContainer
container = self.makeTestObject()
keys = container.keys()
self.assertEqual(list(keys), [])
container, data = self.__setUp()
keys = container.keys()
keys = list(keys); keys.sort() # convert to sorted list
ikeys = [ k for k, v in data ]; ikeys.sort() # sort input keys
self.assertEqual(keys, ikeys)
def test_get(self):
# See interface IReadContainer
default = object()
data = self.makeTestData()
container = self.makeTestObject()
self.assertRaises(KeyError, container.__getitem__, data[0][0])
self.assertEqual(container.get(data[0][0], default), default)
container, data = self.__setUp()
self.assertRaises(KeyError, container.__getitem__,
self.getUnknownKey())
self.assertEqual(container.get(self.getUnknownKey(), default), default)
for i in (1, 8, 7, 3, 4):
self.assertEqual(container.get(data[i][0], default), data[i][1])
self.assertEqual(container.get(data[i][0]), data[i][1])
def test_values(self):
# See interface IReadContainer
container = self.makeTestObject()
values = container.values()
self.assertEqual(list(values), [])
container, data = self.__setUp()
values = list(container.values())
for k, v in data:
try:
values.remove(v)
except ValueError:
self.fail('Value not in list')
self.assertEqual(values, [])
def test_len(self):
# See interface IReadContainer
container = self.makeTestObject()
self.assertEqual(len(container), 0)
container, data = self.__setUp()
self.assertEqual(len(container), len(data))
def test_items(self):
# See interface IReadContainer
container = self.makeTestObject()
items = container.items()
self.assertEqual(list(items), [])
container, data = self.__setUp()
items = container.items()
items = list(items); items.sort() # convert to sorted list
data.sort() # sort input data
self.assertEqual(items, data)
def test___contains__(self):
# See interface IReadContainer
container = self.makeTestObject()
data = self.makeTestData()
self.assertEqual(not not (data[6][0] in container), False)
container, data = self.__setUp()
self.assertEqual(not not (data[6][0] in container), True)
for i in (1, 8, 7, 3, 4):
self.assertEqual(not not (data[i][0] in container), 1)
def test_delObject(self):
# See interface IWriteContainer
default = object()
data = self.makeTestData()
container = self.makeTestObject()
self.assertRaises(KeyError, container.__delitem__, data[0][0])
container, data = self.__setUp()
self.assertRaises(KeyError, container.__delitem__,
self.getUnknownKey())
for i in (1, 8, 7, 3, 4):
del container[data[i][0]]
for i in (1, 8, 7, 3, 4):
self.assertRaises(KeyError, container.__getitem__, data[i][0])
self.assertEqual(container.get(data[i][0], default), default)
for i in (0, 2, 9, 6, 5):
self.assertEqual(container[data[i][0]], data[i][1])
############################################################
# Tests from Folder
def testEmpty(self):
folder = self.makeTestObject()
data = self.makeTestData()
self.failIf(folder.keys())
self.failIf(folder.values())
self.failIf(folder.items())
self.failIf(len(folder))
self.failIf(data[6][0] in folder)
self.assertEquals(folder.get(data[6][0], None), None)
self.assertRaises(KeyError, folder.__getitem__, data[6][0])
self.assertRaises(KeyError, folder.__delitem__, data[6][0])
def testBadKeyTypes(self):
folder = self.makeTestObject()
data = self.makeTestData()
value = data[1][1]
for name in self.getBadKeyTypes():
self.assertRaises(TypeError, folder.__setitem__, name, value)
def testOneItem(self):
folder = self.makeTestObject()
data = self.makeTestData()
foo = data[0][1]
name = data[0][0]
folder[name] = foo
self.assertEquals(len(folder.keys()), 1)
self.assertEquals(folder.keys()[0], name)
self.assertEquals(len(folder.values()), 1)
self.assertEquals(folder.values()[0], foo)
self.assertEquals(len(folder.items()), 1)
self.assertEquals(folder.items()[0], (name, foo))
self.assertEquals(len(folder), 1)
self.failUnless(name in folder)
# Use an arbitrary id frpm the data set; don;t just use any id, since
# there might be restrictions on their form
self.failIf(data[6][0] in folder)
self.assertEquals(folder.get(name, None), foo)
self.assertEquals(folder[name], foo)
self.assertRaises(KeyError, folder.__getitem__, data[6][0])
foo2 = data[1][1]
name2 = data[1][0]
folder[name2] = foo2
self.assertEquals(len(folder.keys()), 2)
self.assertEquals(not not name2 in folder.keys(), True)
self.assertEquals(len(folder.values()), 2)
self.assertEquals(not not foo2 in folder.values(), True)
self.assertEquals(len(folder.items()), 2)
self.assertEquals(not not (name2, foo2) in folder.items(), True)
self.assertEquals(len(folder), 2)
del folder[name]
del folder[name2]
self.failIf(folder.keys())
self.failIf(folder.values())
self.failIf(folder.items())
self.failIf(len(folder))
self.failIf(name in folder)
self.assertRaises(KeyError, folder.__getitem__, name)
self.assertEquals(folder.get(name, None), None)
self.assertRaises(KeyError, folder.__delitem__, name)
def testManyItems(self):
folder = self.makeTestObject()
data = self.makeTestData()
objects = [ data[i][1] for i in range(4) ]
name0 = data[0][0]
name1 = data[1][0]
name2 = data[2][0]
name3 = data[3][0]
folder[name0] = objects[0]
folder[name1] = objects[1]
folder[name2] = objects[2]
folder[name3] = objects[3]
self.assertEquals(len(folder.keys()), len(objects))
self.failUnless(name0 in folder.keys())
self.failUnless(name1 in folder.keys())
self.failUnless(name2 in folder.keys())
self.failUnless(name3 in folder.keys())
self.assertEquals(len(folder.values()), len(objects))
self.failUnless(objects[0] in folder.values())
self.failUnless(objects[1] in folder.values())
self.failUnless(objects[2] in folder.values())
self.failUnless(objects[3] in folder.values())
self.assertEquals(len(folder.items()), len(objects))
self.failUnless((name0, objects[0]) in folder.items())
self.failUnless((name1, objects[1]) in folder.items())
self.failUnless((name2, objects[2]) in folder.items())
self.failUnless((name3, objects[3]) in folder.items())
self.assertEquals(len(folder), len(objects))
self.failUnless(name0 in folder)
self.failUnless(name1 in folder)
self.failUnless(name2 in folder)
self.failUnless(name3 in folder)
self.failIf(data[5][0] in folder)
self.assertEquals(folder.get(name0, None), objects[0])
self.assertEquals(folder[name0], objects[0])
self.assertEquals(folder.get(name1, None), objects[1])
self.assertEquals(folder[name1], objects[1])
self.assertEquals(folder.get(name2, None), objects[2])
self.assertEquals(folder[name2], objects[2])
self.assertEquals(folder.get(name3, None), objects[3])
self.assertEquals(folder[name3], objects[3])
self.assertEquals(folder.get(data[5][0], None), None)
self.assertRaises(KeyError, folder.__getitem__, data[5][0])
del folder[name0]
self.assertEquals(len(folder), len(objects) - 1)
self.failIf(name0 in folder)
self.failIf(name0 in folder.keys())
self.failIf(objects[0] in folder.values())
self.failIf((name0, objects[0]) in folder.items())
self.assertEquals(folder.get(name0, None), None)
self.assertRaises(KeyError, folder.__getitem__, name0)
self.assertRaises(KeyError, folder.__delitem__, name0)
del folder[name1]
del folder[name2]
del folder[name3]
self.failIf(folder.keys())
self.failIf(folder.values())
self.failIf(folder.items())
self.failIf(len(folder))
self.failIf(name0 in folder)
self.failIf(name1 in folder)
self.failIf(name2 in folder)
self.failIf(name3 in folder)
class TestSampleContainer(BaseTestIContainer, TestCase):
def makeTestObject(self):
from zope.container.sample import SampleContainer
return SampleContainer()
def makeTestData(self):
return DefaultTestData()
def getUnknownKey(self):
return '10'
def getBadKeyTypes(self):
return [None, ['foo'], 1, '\xf3abc']
def test_suite():
return makeSuite(TestSampleContainer)
if __name__=='__main__':
main(defaultTest='test_suite')
##############################################################################
#
# Copyright (c) 2003 Zope Corporation 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.
#
##############################################################################
"""Test the OrderedContainer.
$Id: test_ordered.py 111713 2010-04-30 20:43:09Z hannosch $
"""
import unittest
from doctest import DocTestSuite
from zope.component.eventtesting import getEvents, clearEvents
from zope.container import testing
def test_order_events():
"""
Prepare the setup::
>>> from zope.container.sample import SampleContainer
>>> root = SampleContainer()
Prepare some objects::
>>> from zope.container.ordered import OrderedContainer
>>> oc = OrderedContainer()
>>> oc['foo'] = 'bar'
>>> oc['baz'] = 'quux'
>>> oc['zork'] = 'grue'
>>> oc.keys()
['foo', 'baz', 'zork']
Now change the order::
>>> clearEvents()
>>> oc.updateOrder(['baz', 'foo', 'zork'])
>>> oc.keys()
['baz', 'foo', 'zork']
Check what events have been sent::
>>> events = getEvents()
>>> [event.__class__.__name__ for event in events]
['ContainerModifiedEvent']
This is in fact a specialized modification event::
>>> from zope.lifecycleevent.interfaces import IObjectModifiedEvent
>>> IObjectModifiedEvent.providedBy(events[0])
True
"""
def test_all_items_available_at_object_added_event():
"""
Prepare the setup::
>>> from zope.container.sample import SampleContainer
>>> root = SampleContainer()
Now register an event subscriber to object added events.
>>> import zope.component
>>> from zope.container import interfaces
>>> from zope.lifecycleevent.interfaces import IObjectAddedEvent
>>> @zope.component.adapter(IObjectAddedEvent)
... def printContainerKeys(event):
... print event.newParent.keys()
>>> zope.component.provideHandler(printContainerKeys)
Now we are adding an object to the container.
>>> from zope.container.ordered import OrderedContainer
>>> oc = OrderedContainer()
>>> oc['foo'] = 'FOO'
['foo']
"""
def test_exception_causes_order_fix():
"""
Prepare the setup::
>>> from zope.container.sample import SampleContainer
>>> root = SampleContainer()
Now register an event subscriber to object added events that
throws an error.
>>> import zope.component
>>> from zope.container import interfaces
>>> from zope.lifecycleevent.interfaces import IObjectAddedEvent
>>> @zope.component.adapter(IObjectAddedEvent)
... def raiseException(event):
... raise Exception()
>>> zope.component.provideHandler(raiseException)
Now we are adding an object to the container.
>>> from zope.container.ordered import OrderedContainer
>>> oc = OrderedContainer()
>>> oc['foo'] = 'FOO'
Traceback (most recent call last):
...
Exception
The key 'foo' should not be around:
>>> 'foo' in oc.keys()
False
"""
def test_suite():
suite = unittest.TestSuite()
suite.addTest(DocTestSuite("zope.container.ordered",
setUp=testing.setUp,
tearDown=testing.tearDown))
suite.addTest(DocTestSuite(
setUp=testing.ContainerPlacefulSetup().setUp,
tearDown=testing.ContainerPlacefulSetup().tearDown))
return suite
if __name__ == '__main__':
unittest.main()
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Test container ISized adapter.
$Id: test_size.py 95341 2009-01-28 15:59:18Z wosc $
"""
import unittest
from zope.interface import implements
from zope.size.interfaces import ISized
from zope.container.interfaces import IContainer
class DummyContainer(object):
implements(IContainer)
def __init__(self, numitems):
self._numitems = numitems
def __len__(self):
return self._numitems
class Test(unittest.TestCase):
def testImplementsISized(self):
from zope.container.size import ContainerSized
sized = ContainerSized(DummyContainer(23))
self.assert_(ISized.providedBy(sized))
def testEmptyContainer(self):
from zope.container.size import ContainerSized
obj = DummyContainer(0)
sized = ContainerSized(obj)
self.assertEqual(sized.sizeForSorting(), ('item', 0))
self.assertEqual(sized.sizeForDisplay(), u'${items} items')
self.assertEqual(sized.sizeForDisplay().mapping['items'], '0')
def testOneItem(self):
from zope.container.size import ContainerSized
obj = DummyContainer(1)
sized = ContainerSized(obj)
self.assertEqual(sized.sizeForSorting(), ('item', 1))
self.assertEqual(sized.sizeForDisplay(), u'1 item')
def testSeveralItems(self):
from zope.container.size import ContainerSized
obj = DummyContainer(2)
sized = ContainerSized(obj)
self.assertEqual(sized.sizeForSorting(), ('item', 2))
self.assertEqual(sized.sizeForDisplay(), u'${items} items')
self.assertEqual(sized.sizeForDisplay().mapping['items'], '2')
def test_suite():
loader = unittest.TestLoader()
return loader.loadTestsFromTestCase(Test)
if __name__=='__main__':
unittest.TextTestRunner().run(test_suite())
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.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.
#
##############################################################################
"""Traversal components for containers
$Id: traversal.py 97680 2009-03-09 07:32:19Z wosc $
"""
__docformat__ = 'restructuredtext'
from zope.interface import implements, providedBy
from zope.component import queryMultiAdapter, getSiteManager
from zope.component import ComponentLookupError
from zope.traversing.interfaces import TraversalError, ITraversable
from zope.publisher.interfaces.browser import IBrowserPublisher
from zope.publisher.interfaces.xmlrpc import IXMLRPCPublisher
from zope.publisher.interfaces import IDefaultViewName, NotFound
from zope.container.interfaces import ISimpleReadContainer, IItemContainer
from zope.container.interfaces import IReadContainer
# Note that the next two classes are included here because they
# can be used for multiple view types.
class ContainerTraverser(object):
"""A traverser that knows how to look up objects by name in a container."""
implements(IBrowserPublisher, IXMLRPCPublisher)
__used_for__ = ISimpleReadContainer
def __init__(self, container, request):
self.context = container
self.request = request
def publishTraverse(self, request, name):
"""See zope.publisher.interfaces.IPublishTraverse"""
subob = self.context.get(name, None)
if subob is None:
view = queryMultiAdapter((self.context, request), name=name)
if view is not None:
return view
raise NotFound(self.context, name, request)
return subob
def browserDefault(self, request):
"""See zope.publisher.browser.interfaces.IBrowserPublisher"""
# XXX this re-implements zope.app.publisher.browser.getDefaultViewName()
# to break our only dependency on it.
view_name = getSiteManager(None).adapters.lookup(
map(providedBy, (self.context, request)), IDefaultViewName)
if view_name is None:
raise ComponentLookupError("Couldn't find default view name",
self.context, request)
view_uri = "@@%s" %view_name
return self.context, (view_uri,)
class ItemTraverser(ContainerTraverser):
"""A traverser that knows how to look up objects by name in an item
container."""
__used_for__ = IItemContainer
def publishTraverse(self, request, name):
"""See zope.publisher.interfaces.IPublishTraverse"""
try:
return self.context[name]
except KeyError:
view = queryMultiAdapter((self.context, request), name=name)
if view is not None:
return view
raise NotFound(self.context, name, request)
_marker = object()
class ContainerTraversable(object):
"""Traverses containers via `getattr` and `get`."""
implements(ITraversable)
__used_for__ = IReadContainer
def __init__(self, container):
self._container = container
def traverse(self, name, furtherPath):
container = self._container
v = container.get(name, _marker)
if v is _marker:
try:
# Note that if name is a unicode string, getattr will
# implicitly try to encode it using the system
# encoding (usually ascii). Failure to encode means
# invalid attribute name.
v = getattr(container, name, _marker)
except UnicodeEncodeError:
raise TraversalError(container, name)
if v is _marker:
raise TraversalError(container, name)
return v
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment