@@ -22,6 +22,7 @@ urlpatterns = [ | |||
url(r'^post/(?P<pk>\d+)/remove/$', views.post_remove, name='post_remove'), | |||
url(r'^student/(?P<slug>[-\w]+)/remove/$', views.tag_remove, name='tag_remove'), | |||
url(r'^tags/', include('taggit_templatetags2.urls')), | |||
url(r'^newsletter/', include('newsletter.urls')), | |||
] | |||
if settings.DEBUG: |
@@ -400,3 +400,6 @@ | |||
[24/Oct/2018 19:03:28] INFO [mysite:191] <QuerySet [<Post: Third one>]> | |||
[24/Oct/2018 19:03:45] INFO [mysite:189] bamberg | |||
[24/Oct/2018 19:03:45] INFO [mysite:191] <QuerySet [<Post: Third one>, <Post: here i go again>]> | |||
[25/Oct/2018 10:33:13] INFO [mysite:189] bamberg | |||
[25/Oct/2018 15:28:52] INFO [mysite:189] hi | |||
[25/Oct/2018 15:28:54] INFO [mysite:189] |
@@ -44,11 +44,14 @@ INSTALLED_APPS = [ | |||
'django.contrib.sessions', | |||
'django.contrib.messages', | |||
'django.contrib.staticfiles', | |||
'django.contrib.sites', | |||
'application', | |||
'taggit', | |||
'taggit_templatetags2', | |||
'djcelery', | |||
'kombu.transport.django', | |||
'sorl.thumbnail', | |||
'newsletter', | |||
] | |||
MIDDLEWARE = [ | |||
@@ -261,4 +264,6 @@ CELERY_TASK_SERIALIZER = 'json' | |||
CELERY_RESULT_SERIALIZER = 'json' | |||
CELERY_RESULT_BACKEND = 'djcelery.backends.database:DatabaseBackend' | |||
CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" | |||
djcelery.setup_loader() | |||
djcelery.setup_loader() | |||
SITE_ID = 1 |
@@ -0,0 +1,12 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# EASY-INSTALL-ENTRY-SCRIPT: 'python-card-me==0.9.3','console_scripts','change_tz' | |||
__requires__ = 'python-card-me==0.9.3' | |||
import re | |||
import sys | |||
from pkg_resources import load_entry_point | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit( | |||
load_entry_point('python-card-me==0.9.3', 'console_scripts', 'change_tz')() | |||
) |
@@ -0,0 +1,11 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# -*- coding: utf-8 -*- | |||
import re | |||
import sys | |||
from chardet.cli.chardetect import main | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit(main()) |
@@ -0,0 +1,11 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# -*- coding: utf-8 -*- | |||
import re | |||
import sys | |||
from ZODB.FileStorage.fsdump import main | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit(main()) |
@@ -0,0 +1,11 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# -*- coding: utf-8 -*- | |||
import re | |||
import sys | |||
from ZODB.scripts.fsoids import main | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit(main()) |
@@ -0,0 +1,11 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# -*- coding: utf-8 -*- | |||
import re | |||
import sys | |||
from ZODB.scripts.fsrefs import main | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit(main()) |
@@ -0,0 +1,11 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# -*- coding: utf-8 -*- | |||
import re | |||
import sys | |||
from ZODB.scripts.fstail import Main | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit(Main()) |
@@ -0,0 +1,12 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# EASY-INSTALL-ENTRY-SCRIPT: 'python-card-me==0.9.3','console_scripts','ics_diff' | |||
__requires__ = 'python-card-me==0.9.3' | |||
import re | |||
import sys | |||
from pkg_resources import load_entry_point | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit( | |||
load_entry_point('python-card-me==0.9.3', 'console_scripts', 'ics_diff')() | |||
) |
@@ -0,0 +1,11 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# -*- coding: utf-8 -*- | |||
import re | |||
import sys | |||
from ZODB.scripts.repozo import main | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit(main()) |
@@ -0,0 +1,12 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# EASY-INSTALL-ENTRY-SCRIPT: 'ZEO==5.2.0','console_scripts','runzeo' | |||
__requires__ = 'ZEO==5.2.0' | |||
import re | |||
import sys | |||
from pkg_resources import load_entry_point | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit( | |||
load_entry_point('ZEO==5.2.0', 'console_scripts', 'runzeo')() | |||
) |
@@ -0,0 +1,17 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
from surlex import Surlex | |||
import sys | |||
from optparse import OptionParser | |||
def main(): | |||
parser = OptionParser() | |||
parser.set_usage('surlex2regex.py <surlex>') | |||
if len(sys.argv) == 1: | |||
argv = ['-h'] | |||
else: | |||
argv = sys.argv[1:] | |||
options, args = parser.parse_args(argv) | |||
print (Surlex(args[0]).translate()) | |||
if __name__ == '__main__': | |||
main() |
@@ -0,0 +1,11 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# -*- coding: utf-8 -*- | |||
import re | |||
import sys | |||
from ZConfig.validator import main | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit(main()) |
@@ -0,0 +1,11 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# -*- coding: utf-8 -*- | |||
import re | |||
import sys | |||
from ZConfig.schema2html import main | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit(main()) |
@@ -0,0 +1,12 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# EASY-INSTALL-ENTRY-SCRIPT: 'zdaemon==4.2.0','console_scripts','zdaemon' | |||
__requires__ = 'zdaemon==4.2.0' | |||
import re | |||
import sys | |||
from pkg_resources import load_entry_point | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit( | |||
load_entry_point('zdaemon==4.2.0', 'console_scripts', 'zdaemon')() | |||
) |
@@ -0,0 +1,12 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# EASY-INSTALL-ENTRY-SCRIPT: 'ZEO==5.2.0','console_scripts','zeo-nagios' | |||
__requires__ = 'ZEO==5.2.0' | |||
import re | |||
import sys | |||
from pkg_resources import load_entry_point | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit( | |||
load_entry_point('ZEO==5.2.0', 'console_scripts', 'zeo-nagios')() | |||
) |
@@ -0,0 +1,12 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# EASY-INSTALL-ENTRY-SCRIPT: 'ZEO==5.2.0','console_scripts','zeoctl' | |||
__requires__ = 'ZEO==5.2.0' | |||
import re | |||
import sys | |||
from pkg_resources import load_entry_point | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit( | |||
load_entry_point('ZEO==5.2.0', 'console_scripts', 'zeoctl')() | |||
) |
@@ -0,0 +1,12 @@ | |||
#!/Users/Esthi/thesis_ek/thesisenv/bin/python3 | |||
# EASY-INSTALL-ENTRY-SCRIPT: 'ZEO==5.2.0','console_scripts','zeopack' | |||
__requires__ = 'ZEO==5.2.0' | |||
import re | |||
import sys | |||
from pkg_resources import load_entry_point | |||
if __name__ == '__main__': | |||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) | |||
sys.exit( | |||
load_entry_point('ZEO==5.2.0', 'console_scripts', 'zeopack')() | |||
) |
@@ -0,0 +1,156 @@ | |||
/***************************************************************************** | |||
Copyright (c) 2001, 2002 Zope Foundation and Contributors. | |||
All Rights Reserved. | |||
This software is subject to the provisions of the Zope Public License, | |||
Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
FOR A PARTICULAR PURPOSE | |||
****************************************************************************/ | |||
#ifndef CPERSISTENCE_H | |||
#define CPERSISTENCE_H | |||
#include "_compat.h" | |||
#include "bytesobject.h" | |||
#include "ring.h" | |||
#define CACHE_HEAD \ | |||
PyObject_HEAD \ | |||
CPersistentRing ring_home; \ | |||
int non_ghost_count; \ | |||
Py_ssize_t total_estimated_size; | |||
struct ccobject_head_struct; | |||
typedef struct ccobject_head_struct PerCache; | |||
/* How big is a persistent object? | |||
12 PyGC_Head is two pointers and an int | |||
8 PyObject_HEAD is an int and a pointer | |||
12 jar, oid, cache pointers | |||
8 ring struct | |||
8 serialno | |||
4 state + extra | |||
4 size info | |||
(56) so far | |||
4 dict ptr | |||
4 weaklist ptr | |||
------------------------- | |||
68 only need 62, but obmalloc rounds up to multiple of eight | |||
Even a ghost requires 64 bytes. It's possible to make a persistent | |||
instance with slots and no dict, which changes the storage needed. | |||
*/ | |||
#define cPersistent_HEAD \ | |||
PyObject_HEAD \ | |||
PyObject *jar; \ | |||
PyObject *oid; \ | |||
PerCache *cache; \ | |||
CPersistentRing ring; \ | |||
char serial[8]; \ | |||
signed state:8; \ | |||
unsigned estimated_size:24; | |||
/* We recently added estimated_size. We originally added it as a new | |||
unsigned long field after a signed char state field and a | |||
3-character reserved field. This didn't work because there | |||
are packages in the wild that have their own copies of cPersistence.h | |||
that didn't see the update. | |||
To get around this, we used the reserved space by making | |||
estimated_size a 24-bit bit field in the space occupied by the old | |||
3-character reserved field. To fit in 24 bits, we made the units | |||
of estimated_size 64-character blocks. This allows is to handle up | |||
to a GB. We should never see that, but to be paranoid, we also | |||
truncate sizes greater than 1GB. We also set the minimum size to | |||
64 bytes. | |||
We use the _estimated_size_in_24_bits and _estimated_size_in_bytes | |||
macros both to avoid repetition and to make intent a little clearer. | |||
*/ | |||
#define _estimated_size_in_24_bits(I) ((I) > 1073741696 ? 16777215 : (I)/64+1) | |||
#define _estimated_size_in_bytes(I) ((I)*64) | |||
#define cPersistent_GHOST_STATE -1 | |||
#define cPersistent_UPTODATE_STATE 0 | |||
#define cPersistent_CHANGED_STATE 1 | |||
#define cPersistent_STICKY_STATE 2 | |||
typedef struct { | |||
cPersistent_HEAD | |||
} cPersistentObject; | |||
typedef void (*percachedelfunc)(PerCache *, PyObject *); | |||
typedef struct { | |||
PyTypeObject *pertype; | |||
getattrofunc getattro; | |||
setattrofunc setattro; | |||
int (*changed)(cPersistentObject*); | |||
void (*accessed)(cPersistentObject*); | |||
void (*ghostify)(cPersistentObject*); | |||
int (*setstate)(PyObject*); | |||
percachedelfunc percachedel; | |||
int (*readCurrent)(cPersistentObject*); | |||
} cPersistenceCAPIstruct; | |||
#define cPersistenceType cPersistenceCAPI->pertype | |||
#ifndef DONT_USE_CPERSISTENCECAPI | |||
static cPersistenceCAPIstruct *cPersistenceCAPI; | |||
#endif | |||
#define cPersistanceModuleName "cPersistence" | |||
#define PER_TypeCheck(O) PyObject_TypeCheck((O), cPersistenceCAPI->pertype) | |||
#define PER_USE_OR_RETURN(O,R) {if((O)->state==cPersistent_GHOST_STATE && cPersistenceCAPI->setstate((PyObject*)(O)) < 0) return (R); else if ((O)->state==cPersistent_UPTODATE_STATE) (O)->state=cPersistent_STICKY_STATE;} | |||
#define PER_CHANGED(O) (cPersistenceCAPI->changed((cPersistentObject*)(O))) | |||
#define PER_READCURRENT(O, E) \ | |||
if (cPersistenceCAPI->readCurrent((cPersistentObject*)(O)) < 0) { E; } | |||
#define PER_GHOSTIFY(O) (cPersistenceCAPI->ghostify((cPersistentObject*)(O))) | |||
/* If the object is sticky, make it non-sticky, so that it can be ghostified. | |||
The value is not meaningful | |||
*/ | |||
#define PER_ALLOW_DEACTIVATION(O) ((O)->state==cPersistent_STICKY_STATE && ((O)->state=cPersistent_UPTODATE_STATE)) | |||
#define PER_PREVENT_DEACTIVATION(O) ((O)->state==cPersistent_UPTODATE_STATE && ((O)->state=cPersistent_STICKY_STATE)) | |||
/* | |||
Make a persistent object usable from C by: | |||
- Making sure it is not a ghost | |||
- Making it sticky. | |||
IMPORTANT: If you call this and don't call PER_ALLOW_DEACTIVATION, | |||
your object will not be ghostified. | |||
PER_USE returns a 1 on success and 0 failure, where failure means | |||
error. | |||
*/ | |||
#define PER_USE(O) \ | |||
(((O)->state != cPersistent_GHOST_STATE \ | |||
|| (cPersistenceCAPI->setstate((PyObject*)(O)) >= 0)) \ | |||
? (((O)->state==cPersistent_UPTODATE_STATE) \ | |||
? ((O)->state=cPersistent_STICKY_STATE) : 1) : 0) | |||
#define PER_ACCESSED(O) (cPersistenceCAPI->accessed((cPersistentObject*)(O))) | |||
#endif |
@@ -0,0 +1,66 @@ | |||
/***************************************************************************** | |||
Copyright (c) 2003 Zope Foundation and Contributors. | |||
All Rights Reserved. | |||
This software is subject to the provisions of the Zope Public License, | |||
Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
FOR A PARTICULAR PURPOSE | |||
****************************************************************************/ | |||
/* Support routines for the doubly-linked list of cached objects. | |||
The cache stores a headed, doubly-linked, circular list of persistent | |||
objects, with space for the pointers allocated in the objects themselves. | |||
The cache stores the distinguished head of the list, which is not a valid | |||
persistent object. The other list members are non-ghost persistent | |||
objects, linked in LRU (least-recently used) order. | |||
The r_next pointers traverse the ring starting with the least recently used | |||
object. The r_prev pointers traverse the ring starting with the most | |||
recently used object. | |||
Obscure: While each object is pointed at twice by list pointers (once by | |||
its predecessor's r_next, again by its successor's r_prev), the refcount | |||
on the object is bumped only by 1. This leads to some possibly surprising | |||
sequences of incref and decref code. Note that since the refcount is | |||
bumped at least once, the list does hold a strong reference to each | |||
object in it. | |||
*/ | |||
typedef struct CPersistentRing_struct | |||
{ | |||
struct CPersistentRing_struct *r_prev; | |||
struct CPersistentRing_struct *r_next; | |||
} CPersistentRing; | |||
/* The list operations here take constant time independent of the | |||
* number of objects in the list: | |||
*/ | |||
/* Add elt as the most recently used object. elt must not already be | |||
* in the list, although this isn't checked. | |||
*/ | |||
void ring_add(CPersistentRing *ring, CPersistentRing *elt); | |||
/* Remove elt from the list. elt must already be in the list, although | |||
* this isn't checked. | |||
*/ | |||
void ring_del(CPersistentRing *elt); | |||
/* elt must already be in the list, although this isn't checked. It's | |||
* unlinked from its current position, and relinked into the list as the | |||
* most recently used object (which is arguably the tail of the list | |||
* instead of the head -- but the name of this function could be argued | |||
* either way). This is equivalent to | |||
* | |||
* ring_del(elt); | |||
* ring_add(ring, elt); | |||
* | |||
* but may be a little quicker. | |||
*/ | |||
void ring_move_to_head(CPersistentRing *ring, CPersistentRing *elt); |
@@ -0,0 +1,59 @@ | |||
#ifndef _proxy_H_ | |||
#define _proxy_H_ 1 | |||
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 PY_VERSION_HEX < 0x02070000 | |||
if (PyCObject_Check(tmp)) | |||
_proxy_api = (ProxyInterface *) | |||
PyCObject_AsVoidPtr(tmp); | |||
#else | |||
if (PyCapsule_CheckExact(tmp)) | |||
_proxy_api = (ProxyInterface *) | |||
PyCapsule_GetPointer(tmp, NULL); | |||
#endif | |||
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_ */ |
@@ -0,0 +1,697 @@ | |||
Metadata-Version: 1.1 | |||
Name: Acquisition | |||
Version: 4.5 | |||
Summary: Acquisition is a mechanism that allows objects to obtain attributes from the containment hierarchy they're in. | |||
Home-page: https://github.com/zopefoundation/Acquisition | |||
Author: Zope Foundation and Contributors | |||
Author-email: zope-dev@zope.org | |||
License: ZPL 2.1 | |||
Description: Environmental Acquisiton | |||
======================== | |||
This package implements "environmental acquisiton" for Python, as | |||
proposed in the OOPSLA96_ paper by Joseph Gil and David H. Lorenz: | |||
We propose a new programming paradigm, environmental acquisition in | |||
the context of object aggregation, in which objects acquire | |||
behaviour from their current containers at runtime. The key idea is | |||
that the behaviour of a component may depend upon its enclosing | |||
composite(s). In particular, we propose a form of feature sharing in | |||
which an object "inherits" features from the classes of objects in | |||
its environment. By examining the declaration of classes, it is | |||
possible to determine which kinds of classes may contain a | |||
component, and which components must be contained in a given kind of | |||
composite. These relationships are the basis for language constructs | |||
that supports acquisition. | |||
.. _OOPSLA96: http://www.cs.virginia.edu/~lorenz/papers/oopsla96/>`_: | |||
.. contents:: | |||
Introductory Example | |||
-------------------- | |||
Zope implements acquisition with "Extension Class" mix-in classes. To | |||
use acquisition your classes must inherit from an acquisition base | |||
class. For example:: | |||
>>> import ExtensionClass, Acquisition | |||
>>> class C(ExtensionClass.Base): | |||
... color = 'red' | |||
>>> class A(Acquisition.Implicit): | |||
... def report(self): | |||
... print(self.color) | |||
... | |||
>>> a = A() | |||
>>> c = C() | |||
>>> c.a = a | |||
>>> c.a.report() | |||
red | |||
>>> d = C() | |||
>>> d.color = 'green' | |||
>>> d.a = a | |||
>>> d.a.report() | |||
green | |||
>>> try: | |||
... a.report() | |||
... except AttributeError: | |||
... pass | |||
... else: | |||
... raise AssertionError('AttributeError not raised.') | |||
The class ``A`` inherits acquisition behavior from | |||
``Acquisition.Implicit``. The object, ``a``, "has" the color of | |||
objects ``c`` and d when it is accessed through them, but it has no | |||
color by itself. The object ``a`` obtains attributes from its | |||
environment, where its environment is defined by the access path used | |||
to reach ``a``. | |||
Acquisition Wrappers | |||
-------------------- | |||
When an object that supports acquisition is accessed through an | |||
extension class instance, a special object, called an acquisition | |||
wrapper, is returned. In the example above, the expression ``c.a`` | |||
returns an acquisition wrapper that contains references to both ``c`` | |||
and ``a``. It is this wrapper that performs attribute lookup in ``c`` | |||
when an attribute cannot be found in ``a``. | |||
Acquisition wrappers provide access to the wrapped objects through the | |||
attributes ``aq_parent``, ``aq_self``, ``aq_base``. Continue the | |||
example from above:: | |||
>>> c.a.aq_parent is c | |||
True | |||
>>> c.a.aq_self is a | |||
True | |||
Explicit and Implicit Acquisition | |||
--------------------------------- | |||
Two styles of acquisition are supported: implicit and explicit | |||
acquisition. | |||
Implicit acquisition | |||
-------------------- | |||
Implicit acquisition is so named because it searches for attributes | |||
from the environment automatically whenever an attribute cannot be | |||
obtained directly from an object or through inheritance. | |||
An attribute can be implicitly acquired if its name does not begin | |||
with an underscore. | |||
To support implicit acquisition, your class should inherit from the | |||
mix-in class ``Acquisition.Implicit``. | |||
Explicit Acquisition | |||
-------------------- | |||
When explicit acquisition is used, attributes are not automatically | |||
obtained from the environment. Instead, the method aq_acquire must be | |||
used. For example:: | |||
>>> print(c.a.aq_acquire('color')) | |||
red | |||
To support explicit acquisition, your class should inherit from the | |||
mix-in class ``Acquisition.Explicit``. | |||
Controlling Acquisition | |||
----------------------- | |||
A class (or instance) can provide attribute by attribute control over | |||
acquisition. Your should subclass from ``Acquisition.Explicit``, and set | |||
all attributes that should be acquired to the special value | |||
``Acquisition.Acquired``. Setting an attribute to this value also allows | |||
inherited attributes to be overridden with acquired ones. For example:: | |||
>>> class C(Acquisition.Explicit): | |||
... id = 1 | |||
... secret = 2 | |||
... color = Acquisition.Acquired | |||
... __roles__ = Acquisition.Acquired | |||
The only attributes that are automatically acquired from containing | |||
objects are color, and ``__roles__``. Note that the ``__roles__`` | |||
attribute is acquired even though its name begins with an | |||
underscore. In fact, the special ``Acquisition.Acquired`` value can be | |||
used in ``Acquisition.Implicit`` objects to implicitly acquire | |||
selected objects that smell like private objects. | |||
Sometimes, you want to dynamically make an implicitly acquiring object | |||
acquire explicitly. You can do this by getting the object's | |||
aq_explicit attribute. This attribute provides the object with an | |||
explicit wrapper that replaces the original implicit wrapper. | |||
Filtered Acquisition | |||
-------------------- | |||
The acquisition method, ``aq_acquire``, accepts two optional | |||
arguments. The first of the additional arguments is a "filtering" | |||
function that is used when considering whether to acquire an | |||
object. The second of the additional arguments is an object that is | |||
passed as extra data when calling the filtering function and which | |||
defaults to ``None``. The filter function is called with five | |||
arguments: | |||
* The object that the aq_acquire method was called on, | |||
* The object where an object was found, | |||
* The name of the object, as passed to aq_acquire, | |||
* The object found, and | |||
* The extra data passed to aq_acquire. | |||
If the filter returns a true object that the object found is returned, | |||
otherwise, the acquisition search continues. | |||
Here's an example:: | |||
>>> from Acquisition import Explicit | |||
>>> class HandyForTesting(object): | |||
... def __init__(self, name): | |||
... self.name = name | |||
... def __str__(self): | |||
... return "%s(%s)" % (self.name, self.__class__.__name__) | |||
... __repr__=__str__ | |||
... | |||
>>> class E(Explicit, HandyForTesting): pass | |||
... | |||
>>> class Nice(HandyForTesting): | |||
... isNice = 1 | |||
... def __str__(self): | |||
... return HandyForTesting.__str__(self)+' and I am nice!' | |||
... __repr__ = __str__ | |||
... | |||
>>> a = E('a') | |||
>>> a.b = E('b') | |||
>>> a.b.c = E('c') | |||
>>> a.p = Nice('spam') | |||
>>> a.b.p = E('p') | |||
>>> def find_nice(self, ancestor, name, object, extra): | |||
... return hasattr(object,'isNice') and object.isNice | |||
>>> print(a.b.c.aq_acquire('p', find_nice)) | |||
spam(Nice) and I am nice! | |||
The filtered acquisition in the last line skips over the first | |||
attribute it finds with the name ``p``, because the attribute doesn't | |||
satisfy the condition given in the filter. | |||
Filtered acquisition is rarely used in Zope. | |||
Acquiring from Context | |||
---------------------- | |||
Normally acquisition allows objects to acquire data from their | |||
containers. However an object can acquire from objects that aren't its | |||
containers. | |||
Most of the examples we've seen so far show establishing of an | |||
acquisition context using getattr semantics. For example, ``a.b`` is a | |||
reference to ``b`` in the context of ``a``. | |||
You can also manually set acquisition context using the ``__of__`` | |||
method. For example:: | |||
>>> from Acquisition import Implicit | |||
>>> class C(Implicit): pass | |||
... | |||
>>> a = C() | |||
>>> b = C() | |||
>>> a.color = "red" | |||
>>> print(b.__of__(a).color) | |||
red | |||
In this case, ``a`` does not contain ``b``, but it is put in ``b``'s | |||
context using the ``__of__`` method. | |||
Here's another subtler example that shows how you can construct an | |||
acquisition context that includes non-container objects:: | |||
>>> from Acquisition import Implicit | |||
>>> class C(Implicit): | |||
... def __init__(self, name): | |||
... self.name = name | |||
>>> a = C("a") | |||
>>> a.b = C("b") | |||
>>> a.b.color = "red" | |||
>>> a.x = C("x") | |||
>>> print(a.b.x.color) | |||
red | |||
Even though ``b`` does not contain ``x``, ``x`` can acquire the color | |||
attribute from ``b``. This works because in this case, ``x`` is accessed | |||
in the context of ``b`` even though it is not contained by ``b``. | |||
Here acquisition context is defined by the objects used to access | |||
another object. | |||
Containment Before Context | |||
-------------------------- | |||
If in the example above suppose both a and b have an color attribute:: | |||
>>> a = C("a") | |||
>>> a.color = "green" | |||
>>> a.b = C("b") | |||
>>> a.b.color = "red" | |||
>>> a.x = C("x") | |||
>>> print(a.b.x.color) | |||
green | |||
Why does ``a.b.x.color`` acquire color from ``a`` and not from ``b``? | |||
The answer is that an object acquires from its containers before | |||
non-containers in its context. | |||
To see why consider this example in terms of expressions using the | |||
``__of__`` method:: | |||
a.x -> x.__of__(a) | |||
a.b -> b.__of__(a) | |||
a.b.x -> x.__of__(a).__of__(b.__of__(a)) | |||
Keep in mind that attribute lookup in a wrapper is done by trying to | |||
look up the attribute in the wrapped object first and then in the | |||
parent object. So in the expressions above proceeds from left to | |||
right. | |||
The upshot of these rules is that attributes are looked up by | |||
containment before context. | |||
This rule holds true also for more complex examples. For example, | |||
``a.b.c.d.e.f.g.attribute`` would search for attribute in ``g`` and | |||
all its containers first. (Containers are searched in order from the | |||
innermost parent to the outermost container.) If the attribute is not | |||
found in ``g`` or any of its containers, then the search moves to | |||
``f`` and all its containers, and so on. | |||
Additional Attributes and Methods | |||
--------------------------------- | |||
You can use the special method ``aq_inner`` to access an object | |||
wrapped only by containment. So in the example above, | |||
``a.b.x.aq_inner`` is equivalent to ``a.x``. | |||
You can find out the acquisition context of an object using the | |||
aq_chain method like so: | |||
>>> [obj.name for obj in a.b.x.aq_chain] | |||
['x', 'b', 'a'] | |||
You can find out if an object is in the containment context of another | |||
object using the ``aq_inContextOf`` method. For example: | |||
>>> a.b.aq_inContextOf(a) | |||
True | |||
.. Note: as of this writing the aq_inContextOf examples don't work the | |||
way they should be working. According to Jim, this is because | |||
aq_inContextOf works by comparing object pointer addresses, which | |||
(because they are actually different wrapper objects) doesn't give | |||
you the expected results. He acknowledges that this behavior is | |||
controversial, and says that there is a collector entry to change | |||
it so that you would get the answer you expect in the above. (We | |||
just need to get to it). | |||
Acquisition Module Functions | |||
---------------------------- | |||
In addition to using acquisition attributes and methods directly on | |||
objects you can use similar functions defined in the ``Acquisition`` | |||
module. These functions have the advantage that you don't need to | |||
check to make sure that the object has the method or attribute before | |||
calling it. | |||
``aq_acquire(object, name [, filter, extra, explicit, default, containment])`` | |||
Acquires an object with the given name. | |||
This function can be used to explictly acquire when using explicit | |||
acquisition and to acquire names that wouldn't normally be | |||
acquired. | |||
The function accepts a number of optional arguments: | |||
``filter`` | |||
A callable filter object that is used to decide if an object | |||
should be acquired. | |||
The filter is called with five arguments: | |||
* The object that the aq_acquire method was called on, | |||
* The object where an object was found, | |||
* The name of the object, as passed to aq_acquire, | |||
* The object found, and | |||
* The extra argument passed to aq_acquire. | |||
If the filter returns a true object that the object found is | |||
returned, otherwise, the acquisition search continues. | |||
``extra`` | |||
Extra data to be passed as the last argument to the filter. | |||
``explicit`` | |||
A flag (boolean value) indicating whether explicit acquisition | |||
should be used. The default value is true. If the flag is | |||
true, then acquisition will proceed regardless of whether | |||
wrappers encountered in the search of the acquisition | |||
hierarchy are explicit or implicit wrappers. If the flag is | |||
false, then parents of explicit wrappers are not searched. | |||
This argument is useful if you want to apply a filter without | |||
overriding explicit wrappers. | |||
``default`` | |||
A default value to return if no value can be acquired. | |||
``containment`` | |||
A flag indicating whether the search should be limited to the | |||
containment hierarchy. | |||
In addition, arguments can be provided as keywords. | |||
``aq_base(object)`` | |||
Return the object with all wrapping removed. | |||
``aq_chain(object [, containment])`` | |||
Return a list containing the object and it's acquisition | |||
parents. The optional argument, containment, controls whether the | |||
containment or access hierarchy is used. | |||
``aq_get(object, name [, default, containment])`` | |||
Acquire an attribute, name. A default value can be provided, as | |||
can a flag that limits search to the containment hierarchy. | |||
``aq_inner(object)`` | |||
Return the object with all but the innermost layer of wrapping | |||
removed. | |||
``aq_parent(object)`` | |||
Return the acquisition parent of the object or None if the object | |||
is unwrapped. | |||
``aq_self(object)`` | |||
Return the object with one layer of wrapping removed, unless the | |||
object is unwrapped, in which case the object is returned. | |||
In most cases it is more convenient to use these module functions | |||
instead of the acquisition attributes and methods directly. | |||
Acquisition and Methods | |||
----------------------- | |||
Python methods of objects that support acquisition can use acquired | |||
attributes. When a Python method is called on an object that is | |||
wrapped by an acquisition wrapper, the wrapper is passed to the method | |||
as the first argument. This rule also applies to user-defined method | |||
types and to C methods defined in pure mix-in classes. | |||
Unfortunately, C methods defined in extension base classes that define | |||
their own data structures, cannot use aquired attributes at this | |||
time. This is because wrapper objects do not conform to the data | |||
structures expected by these methods. In practice, you will seldom | |||
find this a problem. | |||
Conclusion | |||
---------- | |||
Acquisition provides a powerful way to dynamically share information | |||
between objects. Zope 2 uses acquisition for a number of its key | |||
features including security, object publishing, and DTML variable | |||
lookup. Acquisition also provides an elegant solution to the problem | |||
of circular references for many classes of problems. While acquisition | |||
is powerful, you should take care when using acquisition in your | |||
applications. The details can get complex, especially with the | |||
differences between acquiring from context and acquiring from | |||
containment. | |||
Changelog | |||
========= | |||
4.5 (2018-10-05) | |||
---------------- | |||
- Avoid deprecation warnings by using current API. | |||
- Add support for Python 3.7. | |||
4.4.4 (2017-11-24) | |||
------------------ | |||
- Add Appveyor configuration to automate building Windows eggs. | |||
4.4.3 (2017-11-23) | |||
------------------ | |||
- Fix the extremely rare potential for a crash when the C extensions | |||
are in use. See `issue 21 <https://github.com/zopefoundation/Acquisition/issues/21>`_. | |||
4.4.2 (2017-05-12) | |||
------------------ | |||
- Fix C capsule name to fix import errors. | |||
- Ensure our dependencies match our expactations about C extensions. | |||
4.4.1 (2017-05-04) | |||
------------------ | |||
- Fix C code under Python 3.4, with missing Py_XSETREF. | |||
4.4.0 (2017-05-04) | |||
------------------ | |||
- Enable the C extension under Python 3. | |||
- Drop support for Python 3.3. | |||
4.3.0 (2017-01-20) | |||
------------------ | |||
- Make tests compatible with ExtensionClass 4.2.0. | |||
- Drop support for Python 2.6 and 3.2. | |||
- Add support for Python 3.5 and 3.6. | |||
4.2.2 (2015-05-19) | |||
------------------ | |||
- Make the pure-Python Acquirer objects cooperatively use the | |||
superclass ``__getattribute__`` method, like the C implementation. | |||
See https://github.com/zopefoundation/Acquisition/issues/7. | |||
- The pure-Python implicit acquisition wrapper allows wrapped objects | |||
to use ``object.__getattribute__(self, name)``. This differs from | |||
the C implementation, but is important for compatibility with the | |||
pure-Python versions of libraries like ``persistent``. See | |||
https://github.com/zopefoundation/Acquisition/issues/9. | |||
4.2.1 (2015-04-23) | |||
------------------ | |||
- Correct several dangling pointer uses in the C extension, | |||
potentially fixing a few interpreter crashes. See | |||
https://github.com/zopefoundation/Acquisition/issues/5. | |||
4.2 (2015-04-04) | |||
---------------- | |||
- Add support for PyPy, PyPy3, and Python 3.2, 3.3, and 3.4. | |||
4.1 (2014-12-18) | |||
---------------- | |||
- Bump dependency on ``ExtensionClass`` to match current release. | |||
4.0.3 (2014-11-02) | |||
------------------ | |||
- Skip readme.rst tests when tests are run outside a source checkout. | |||
4.0.2 (2014-11-02) | |||
------------------ | |||
- Include ``*.rst`` files in the release. | |||
4.0.1 (2014-10-30) | |||
------------------ | |||
- Tolerate Unicode attribute names (ASCII only). LP #143358. | |||
- Make module-level ``aq_acquire`` API respect the ``default`` parameter. | |||
LP #1387363. | |||
- Don't raise an attribute error for ``__iter__`` if the fallback to | |||
``__getitem__`` succeeds. LP #1155760. | |||
4.0 (2013-02-24) | |||
---------------- | |||
- Added trove classifiers to project metadata. | |||
4.0a1 (2011-12-13) | |||
------------------ | |||
- Raise `RuntimeError: Recursion detected in acquisition wrapper` if an object | |||
with a `__parent__` pointer points to a wrapper that in turn points to the | |||
original object. | |||
- Prevent wrappers to be created while accessing `__parent__` on types derived | |||
from Explicit or Implicit base classes. | |||
2.13.9 (2015-02-17) | |||
------------------- | |||
- Tolerate Unicode attribute names (ASCII only). LP #143358. | |||
- Make module-level ``aq_acquire`` API respect the ``default`` parameter. | |||
LP #1387363. | |||
- Don't raise an attribute error for ``__iter__`` if the fallback to | |||
``__getitem__`` succeeds. LP #1155760. | |||
2.13.8 (2011-06-11) | |||
------------------- | |||
- Fixed a segfault on 64bit platforms when providing the `explicit` argument to | |||
the aq_acquire method of an Acquisition wrapper. Thx to LP #675064 for the | |||
hint to the solution. The code passed an int instead of a pointer into a | |||
function. | |||
2.13.7 (2011-03-02) | |||
------------------- | |||
- Fixed bug: When an object did not implement ``__unicode__``, calling | |||
``unicode(wrapped)`` was calling ``__str__`` with an unwrapped ``self``. | |||
2.13.6 (2011-02-19) | |||
------------------- | |||
- Add ``aq_explicit`` to ``IAcquisitionWrapper``. | |||
- Fixed bug: ``unicode(wrapped)`` was not calling a ``__unicode__`` | |||
method on wrapped objects. | |||
2.13.5 (2010-09-29) | |||
------------------- | |||
- Fixed unit tests that failed on 64bit Python on Windows machines. | |||
2.13.4 (2010-08-31) | |||
------------------- | |||
- LP 623665: Fixed typo in Acquisition.h. | |||
2.13.3 (2010-04-19) | |||
------------------- | |||
- Use the doctest module from the standard library and no longer depend on | |||
zope.testing. | |||
2.13.2 (2010-04-04) | |||
------------------- | |||
- Give both wrapper classes a ``__getnewargs__`` method, which causes the ZODB | |||
optimization to fail and create persistent references using the ``_p_oid`` | |||
alone. This happens to be the persistent oid of the wrapped object. This lets | |||
these objects to be persisted correctly, even though they are passed to the | |||
ZODB in a wrapped state. | |||
- Added failing tests for http://dev.plone.org/plone/ticket/10318. This shows | |||
an edge-case where AQ wrappers can be pickled using the specific combination | |||
of cPickle, pickle protocol one and a custom Pickler class with an | |||
``inst_persistent_id`` hook. Unfortunately this is the exact combination used | |||
by ZODB3. | |||
2.13.1 (2010-02-23) | |||
------------------- | |||
- Update to include ExtensionClass 2.13.0. | |||
- Fix the ``tp_name`` of the ImplicitAcquisitionWrapper and | |||
ExplicitAcquisitionWrapper to match their Python visible names and thus have | |||
a correct ``__name__``. | |||
- Expand the ``tp_name`` of our extension types to hold the fully qualified | |||
name. This ensures classes have their ``__module__`` set correctly. | |||
2.13.0 (2010-02-14) | |||
------------------- | |||
- Added support for method cache in Acquisition. Patch contributed by | |||
Yoshinori K. Okuji. See https://bugs.launchpad.net/zope2/+bug/486182. | |||
2.12.4 (2009-10-29) | |||
------------------- | |||
- Fix iteration proxying to pass `self` acquisition-wrapped into both | |||
`__iter__` as well as `__getitem__` (this fixes | |||
https://bugs.launchpad.net/zope2/+bug/360761). | |||
- Add tests for the __getslice__ proxying, including open-ended slicing. | |||
2.12.3 (2009-08-08) | |||
------------------- | |||
- More 64-bit fixes in Py_BuildValue calls. | |||
- More 64-bit issues fixed: Use correct integer size for slice operations. | |||
2.12.2 (2009-08-02) | |||
------------------- | |||
- Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See | |||
http://www.python.org/dev/peps/pep-0353/ for details. | |||
2.12.1 (2009-04-15) | |||
------------------- | |||
- Update for iteration proxying: The proxy for `__iter__` must not rely on the | |||
object to have an `__iter__` itself, but also support fall-back iteration via | |||
`__getitem__` (this fixes https://bugs.launchpad.net/zope2/+bug/360761). | |||
2.12 (2009-01-25) | |||
----------------- | |||
- Release as separate package. | |||
Platform: UNKNOWN | |||
Classifier: Development Status :: 6 - Mature | |||
Classifier: Environment :: Web Environment | |||
Classifier: Framework :: Zope2 | |||
Classifier: License :: OSI Approved :: Zope Public License | |||
Classifier: Operating System :: OS Independent | |||
Classifier: Programming Language :: Python | |||
Classifier: Programming Language :: Python :: 2 | |||
Classifier: Programming Language :: Python :: 2.7 | |||
Classifier: Programming Language :: Python :: 3 | |||
Classifier: Programming Language :: Python :: 3.4 | |||
Classifier: Programming Language :: Python :: 3.5 | |||
Classifier: Programming Language :: Python :: 3.6 | |||
Classifier: Programming Language :: Python :: 3.7 | |||
Classifier: Programming Language :: Python :: Implementation :: CPython | |||
Classifier: Programming Language :: Python :: Implementation :: PyPy |
@@ -0,0 +1,25 @@ | |||
.coveragerc | |||
.gitignore | |||
CHANGES.rst | |||
COPYRIGHT.txt | |||
LICENSE.txt | |||
MANIFEST.in | |||
README.rst | |||
buildout.cfg | |||
pip-delete-this-directory.txt | |||
setup.cfg | |||
setup.py | |||
tox.ini | |||
include/ExtensionClass/ExtensionClass.h | |||
include/ExtensionClass/_compat.h | |||
src/Acquisition/Acquisition.h | |||
src/Acquisition/_Acquisition.c | |||
src/Acquisition/__init__.py | |||
src/Acquisition/interfaces.py | |||
src/Acquisition/tests.py | |||
src/Acquisition.egg-info/PKG-INFO | |||
src/Acquisition.egg-info/SOURCES.txt | |||
src/Acquisition.egg-info/dependency_links.txt | |||
src/Acquisition.egg-info/not-zip-safe | |||
src/Acquisition.egg-info/requires.txt | |||
src/Acquisition.egg-info/top_level.txt |
@@ -0,0 +1 @@ | |||
@@ -0,0 +1,15 @@ | |||
../Acquisition/Acquisition.h | |||
../Acquisition/_Acquisition.c | |||
../Acquisition/_Acquisition.cpython-36m-darwin.so | |||
../Acquisition/__init__.py | |||
../Acquisition/__pycache__/__init__.cpython-36.pyc | |||
../Acquisition/__pycache__/interfaces.cpython-36.pyc | |||
../Acquisition/__pycache__/tests.cpython-36.pyc | |||
../Acquisition/interfaces.py | |||
../Acquisition/tests.py | |||
PKG-INFO | |||
SOURCES.txt | |||
dependency_links.txt | |||
not-zip-safe | |||
requires.txt | |||
top_level.txt |
@@ -0,0 +1 @@ | |||
@@ -0,0 +1,2 @@ | |||
ExtensionClass>=4.2.0 | |||
zope.interface |
@@ -0,0 +1 @@ | |||
Acquisition |
@@ -0,0 +1,53 @@ | |||
/***************************************************************************** | |||
Copyright (c) 1996-2002 Zope Foundation and Contributors. | |||
All Rights Reserved. | |||
This software is subject to the provisions of the Zope Public License, | |||
Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
FOR A PARTICULAR PURPOSE | |||
****************************************************************************/ | |||
#ifndef __ACQUISITION_H_ | |||
#define __ACQUISITION_H_ | |||
typedef struct { | |||
PyObject *(*AQ_Acquire) (PyObject *obj, PyObject *name, PyObject *filter, | |||
PyObject *extra, int explicit, PyObject *deflt, | |||
int containment); | |||
PyObject *(*AQ_Get) (PyObject *obj, PyObject *name, PyObject *deflt, | |||
int containment); | |||
int (*AQ_IsWrapper) (PyObject *obj); | |||
PyObject *(*AQ_Base) (PyObject *obj); | |||
PyObject *(*AQ_Parent) (PyObject *obj); | |||
PyObject *(*AQ_Self) (PyObject *obj); | |||
PyObject *(*AQ_Inner) (PyObject *obj); | |||
PyObject *(*AQ_Chain) (PyObject *obj, int containment); | |||
} ACQUISITIONCAPI; | |||
#ifndef _IN_ACQUISITION_C | |||
#define aq_Acquire(obj, name, filter, extra, explicit, deflt, containment ) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Acquire(obj, name, filter, extra, explicit, deflt, containment))) | |||
#define aq_acquire(obj, name) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Acquire(obj, name, NULL, NULL, 1, NULL, 0))) | |||
#define aq_get(obj, name, deflt, containment) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Get(obj, name, deflt, containment))) | |||
#define aq_isWrapper(obj) (AcquisitionCAPI == NULL ? -1 : (AcquisitionCAPI->AQ_IsWrapper(obj))) | |||
#define aq_base(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Base(obj))) | |||
#define aq_parent(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Parent(obj))) | |||
#define aq_self(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Self(obj))) | |||
#define aq_inner(obj) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_Inner(obj))) | |||
#define aq_chain(obj, containment) (AcquisitionCAPI == NULL ? NULL : (AcquisitionCAPI->AQ_CHain(obj, containment))) | |||
static ACQUISITIONCAPI *AcquisitionCAPI = NULL; | |||
#define aq_init() { \ | |||
AcquisitionCAPI = PyCapsule_Import("Acquisition.AcquisitionCAPI", 0); \ | |||
} | |||
#endif | |||
#endif |
@@ -0,0 +1,953 @@ | |||
from __future__ import absolute_import, print_function | |||
# pylint:disable=W0212,R0911,R0912 | |||
import os | |||
import operator | |||
import platform | |||
import sys | |||
import types | |||
import weakref | |||
import ExtensionClass | |||
from zope.interface import classImplements | |||
from .interfaces import IAcquirer | |||
from .interfaces import IAcquisitionWrapper | |||
IS_PYPY = getattr(platform, 'python_implementation', lambda: None)() == 'PyPy' | |||
IS_PURE = 'PURE_PYTHON' in os.environ | |||
class Acquired(object): | |||
"Marker for explicit acquisition" | |||
_NOT_FOUND = object() # marker | |||
### | |||
# Helper functions | |||
### | |||
def _has__of__(obj): | |||
"""Check whether an object has an __of__ method for returning itself | |||
in the context of a container.""" | |||
# It is necessary to check both the type (or we get into cycles) | |||
# as well as the presence of the method (or mixins of Base pre- or | |||
# post-class-creation as done in, e.g., | |||
# zopefoundation/Persistence) can fail. | |||
return (isinstance(obj, ExtensionClass.Base) and | |||
hasattr(type(obj), '__of__')) | |||
def _apply_filter(predicate, inst, name, result, extra, orig): | |||
return predicate(orig, inst, name, result, extra) | |||
if sys.version_info < (3,): | |||
import copy_reg | |||
def _rebound_method(method, wrapper): | |||
"""Returns a version of the method with self bound to `wrapper`""" | |||
if isinstance(method, types.MethodType): | |||
method = types.MethodType(method.im_func, wrapper, method.im_class) | |||
return method | |||
exec("""def _reraise(tp, value, tb=None): | |||
raise tp, value, tb | |||
""") | |||
else: # pragma: no cover (python 2 is currently our reference) | |||
import copyreg as copy_reg | |||
def _rebound_method(method, wrapper): | |||
"""Returns a version of the method with self bound to `wrapper`""" | |||
if isinstance(method, types.MethodType): | |||
method = types.MethodType(method.__func__, wrapper) | |||
return method | |||
def _reraise(tp, value, tb=None): | |||
if value is None: | |||
value = tp() | |||
if value.__traceback__ is not tb: | |||
raise value.with_traceback(tb) | |||
raise value | |||
### | |||
# Wrapper object protocol, mostly ported from C directly | |||
### | |||
def _Wrapper_findspecial(wrapper, name): | |||
""" | |||
Looks up the special acquisition attributes of an object. | |||
:param str name: The attribute to find, with 'aq' already stripped. | |||
""" | |||
result = _NOT_FOUND | |||
if name == 'base': | |||
result = wrapper._obj | |||
while isinstance(result, _Wrapper) and result._obj is not None: | |||
result = result._obj | |||
elif name == 'parent': | |||
result = wrapper._container | |||
elif name == 'self': | |||
result = wrapper._obj | |||
elif name == 'explicit': | |||
if type(wrapper)._IS_IMPLICIT: | |||
result = ExplicitAcquisitionWrapper( | |||
wrapper._obj, wrapper._container) | |||
else: | |||
result = wrapper | |||
elif name == 'acquire': | |||
result = object.__getattribute__(wrapper, 'aq_acquire') | |||
elif name == 'chain': | |||
# XXX: C has a second implementation here | |||
result = aq_chain(wrapper) | |||
elif name == 'inContextOf': | |||
result = object.__getattribute__(wrapper, 'aq_inContextOf') | |||
elif name == 'inner': | |||
# XXX: C has a second implementation here | |||
result = aq_inner(wrapper) | |||
elif name == 'uncle': | |||
result = 'Bob' | |||
return result | |||
def _Wrapper_acquire(wrapper, name, | |||
predicate=None, predicate_extra=None, | |||
orig_object=None, | |||
explicit=True, containment=True): | |||
""" | |||
Attempt to acquire the `name` from the parent of the wrapper. | |||
:raises AttributeError: If the wrapper has no parent or the | |||
attribute cannot be found. | |||
""" | |||
if wrapper._container is None: | |||
raise AttributeError(name) | |||
search_self = True | |||
search_parent = True | |||
# If the container has an acquisition wrapper itself, we'll use | |||
# _Wrapper_findattr to progress further | |||
if isinstance(wrapper._container, _Wrapper): | |||
if isinstance(wrapper._obj, _Wrapper): | |||
# try to optimize search by recognizing repeated objects in path | |||
if wrapper._obj._container is wrapper._container._container: | |||
search_parent = False | |||
elif wrapper._obj._container is wrapper._container._obj: | |||
search_self = False | |||
# Don't search the container when the container of the container | |||
# is the same object as `wrapper` | |||
if wrapper._container._container is wrapper._obj: | |||
search_parent = False | |||
containment = True | |||
result = _Wrapper_findattr(wrapper._container, name, | |||
predicate=predicate, | |||
predicate_extra=predicate_extra, | |||
orig_object=orig_object, | |||
search_self=search_self, | |||
search_parent=search_parent, | |||
explicit=explicit, | |||
containment=containment) | |||
# XXX: Why does this branch of the C code check __of__, | |||
# but the next one doesn't? | |||
if _has__of__(result): | |||
result = result.__of__(wrapper) | |||
return result | |||
# If the container has a __parent__ pointer, we create an | |||
# acquisition wrapper for it accordingly. Then we can proceed | |||
# with Wrapper_findattr, just as if the container had an | |||
# acquisition wrapper in the first place (see above). | |||
# NOTE: This mutates the wrapper | |||
elif hasattr(wrapper._container, '__parent__'): | |||
parent = wrapper._container.__parent__ | |||
# Don't search the container when the parent of the parent | |||
# is the same object as 'self' | |||
if parent is wrapper._obj: | |||
search_parent = False | |||
elif isinstance(parent, _Wrapper) and parent._obj is wrapper._obj: | |||
# XXX: C code just does parent._obj, assumes its a wrapper | |||
search_parent = False | |||
wrapper._container = ImplicitAcquisitionWrapper( | |||
wrapper._container, parent) | |||
return _Wrapper_findattr(wrapper._container, name, | |||
predicate=predicate, | |||
predicate_extra=predicate_extra, | |||
orig_object=orig_object, | |||
search_self=search_self, | |||
search_parent=search_parent, | |||
explicit=explicit, | |||
containment=containment) | |||
else: | |||
# The container is the end of the acquisition chain; if we | |||
# can't look up the attributes here, we can't look it up at all | |||
result = getattr(wrapper._container, name) | |||
if result is not Acquired: | |||
if predicate: | |||
if _apply_filter(predicate, wrapper._container, name, | |||
result, predicate_extra, orig_object): | |||
return (result.__of__(wrapper) | |||
if _has__of__(result) else result) | |||
else: | |||
raise AttributeError(name) | |||
else: | |||
if _has__of__(result): | |||
result = result.__of__(wrapper) | |||
return result | |||
# this line cannot be reached | |||
raise AttributeError(name) # pragma: no cover | |||
def _Wrapper_findattr(wrapper, name, | |||
predicate=None, predicate_extra=None, | |||
orig_object=None, | |||
search_self=True, search_parent=True, | |||
explicit=True, containment=True): | |||
""" | |||
Search the `wrapper` object for the attribute `name`. | |||
:param bool search_self: Search `wrapper.aq_self` for the attribute. | |||
:param bool search_parent: Search `wrapper.aq_parent` for the attribute. | |||
:param bool explicit: Explicitly acquire the attribute from the parent | |||
(should be assumed with implicit wrapper) | |||
:param bool containment: Use the innermost wrapper (`aq_inner`) | |||
for looking up the attribute. | |||
""" | |||
orig_name = name | |||
if orig_object is None: | |||
orig_object = wrapper | |||
# First, special names | |||
if name.startswith('aq') or name == '__parent__': | |||
# __parent__ is an alias of aq_parent | |||
if name == '__parent__': | |||
name = 'parent' | |||
else: | |||
name = name[3:] | |||
result = _Wrapper_findspecial(wrapper, name) | |||
if result is not _NOT_FOUND: | |||
if predicate: | |||
if _apply_filter(predicate, wrapper, orig_name, | |||
result, predicate_extra, orig_object): | |||
return result | |||
else: | |||
raise AttributeError(orig_name) | |||
return result | |||
elif name in ('__reduce__', '__reduce_ex__', '__getstate__', | |||
'__of__', '__cmp__', '__eq__', '__ne__', '__lt__', | |||
'__le__', '__gt__', '__ge__'): | |||
return object.__getattribute__(wrapper, orig_name) | |||
# If we're doing a containment search, replace the wrapper with aq_inner | |||
if containment: | |||
while isinstance(wrapper._obj, _Wrapper): | |||
wrapper = wrapper._obj | |||
if search_self and wrapper._obj is not None: | |||
if isinstance(wrapper._obj, _Wrapper): | |||
if wrapper is wrapper._obj: | |||
raise RuntimeError("Recursion detected in acquisition wrapper") | |||
try: | |||
result = _Wrapper_findattr(wrapper._obj, orig_name, | |||
predicate=predicate, | |||
predicate_extra=predicate_extra, | |||
orig_object=orig_object, | |||
search_self=True, | |||
search_parent=explicit or isinstance(wrapper._obj, ImplicitAcquisitionWrapper), # NOQA | |||
explicit=explicit, | |||
containment=containment) | |||
if isinstance(result, types.MethodType): | |||
result = _rebound_method(result, wrapper) | |||
elif _has__of__(result): | |||
result = result.__of__(wrapper) | |||
return result | |||
except AttributeError: | |||
pass | |||
# deal with mixed __parent__ / aq_parent circles | |||
elif (isinstance(wrapper._container, _Wrapper) and | |||
wrapper._container._container is wrapper): | |||
raise RuntimeError("Recursion detected in acquisition wrapper") | |||
else: | |||
# normal attribute lookup | |||
try: | |||
result = getattr(wrapper._obj, orig_name) | |||
except AttributeError: | |||
pass | |||
else: | |||
if result is Acquired: | |||
return _Wrapper_acquire(wrapper, orig_name, | |||
predicate=predicate, | |||
predicate_extra=predicate_extra, | |||
orig_object=orig_object, | |||
explicit=True, | |||
containment=containment) | |||
if isinstance(result, types.MethodType): | |||
result = _rebound_method(result, wrapper) | |||
elif _has__of__(result): | |||
result = result.__of__(wrapper) | |||
if predicate: | |||
if _apply_filter(predicate, wrapper, orig_name, | |||
result, predicate_extra, orig_object): | |||
return result | |||
else: | |||
return result | |||
# lookup has failed, acquire from the parent | |||
if search_parent and (not name.startswith('_') or explicit): | |||
return _Wrapper_acquire(wrapper, orig_name, | |||
predicate=predicate, | |||
predicate_extra=predicate_extra, | |||
orig_object=orig_object, | |||
explicit=explicit, | |||
containment=containment) | |||
raise AttributeError(orig_name) | |||
_NOT_GIVEN = object() # marker | |||
_OGA = object.__getattribute__ | |||
# Map from object types with slots to their generated, derived | |||
# types (or None if no derived type is needed) | |||
_wrapper_subclass_cache = weakref.WeakKeyDictionary() | |||
def _make_wrapper_subclass_if_needed(cls, obj, container): | |||
# If the type of an object to be wrapped has __slots__, then we | |||
# must create a wrapper subclass that has descriptors for those | |||
# same slots. In this way, its methods that use object.__getattribute__ | |||
# directly will continue to work, even when given an instance of _Wrapper | |||
if getattr(cls, '_Wrapper__DERIVED', False): | |||
return None | |||
type_obj = type(obj) | |||
wrapper_subclass = _wrapper_subclass_cache.get(type_obj, _NOT_GIVEN) | |||
if wrapper_subclass is _NOT_GIVEN: | |||
slotnames = copy_reg._slotnames(type_obj) | |||
if slotnames and not isinstance(obj, _Wrapper): | |||
new_type_dict = {'_Wrapper__DERIVED': True} | |||
def _make_property(slotname): | |||
return property(lambda s: getattr(s._obj, slotname), | |||
lambda s, v: setattr(s._obj, slotname, v), | |||
lambda s: delattr(s._obj, slotname)) | |||
for slotname in slotnames: | |||
new_type_dict[slotname] = _make_property(slotname) | |||
new_type = type(cls.__name__ + '_' + type_obj.__name__, | |||
(cls,), | |||
new_type_dict) | |||
else: | |||
new_type = None | |||
wrapper_subclass = _wrapper_subclass_cache[type_obj] = new_type | |||
return wrapper_subclass | |||
class _Wrapper(ExtensionClass.Base): | |||
__slots__ = ('_obj', '_container', '__dict__') | |||
_IS_IMPLICIT = None | |||
def __new__(cls, obj, container): | |||
wrapper_subclass = _make_wrapper_subclass_if_needed(cls, obj, container) # NOQA | |||
if wrapper_subclass: | |||
inst = wrapper_subclass(obj, container) | |||
else: | |||
inst = super(_Wrapper, cls).__new__(cls) | |||
inst._obj = obj | |||
inst._container = container | |||
if hasattr(obj, '__dict__') and not isinstance(obj, _Wrapper): | |||
# Make our __dict__ refer to the same dict as the other object, | |||
# so that if it has methods that use `object.__getattribute__` | |||
# they still work. Note that because we have slots, | |||
# we won't interfere with the contents of that dict. | |||
object.__setattr__(inst, '__dict__', obj.__dict__) | |||
return inst | |||
def __init__(self, obj, container): | |||
super(_Wrapper, self).__init__() | |||
self._obj = obj | |||
self._container = container | |||
def __setattr__(self, name, value): | |||
if name == '__parent__' or name == 'aq_parent': | |||
object.__setattr__(self, '_container', value) | |||
return | |||
if name == '_obj' or name == '_container': | |||
# should only happen at init time | |||
object.__setattr__(self, name, value) | |||
return | |||
# If we are wrapping something, unwrap passed in wrappers | |||
if self._obj is None: | |||
raise AttributeError( | |||
'Attempt to set attribute on empty acquisition wrapper') | |||
while value is not None and isinstance(value, _Wrapper): | |||
value = value._obj | |||
setattr(self._obj, name, value) | |||
def __delattr__(self, name): | |||
if name == '__parent__' or name == 'aq_parent': | |||
self._container = None | |||
else: | |||
delattr(self._obj, name) | |||
def __getattribute__(self, name): | |||
if name in ('_obj', '_container'): | |||
return _OGA(self, name) | |||
if (_OGA(self, '_obj') is not None or | |||
_OGA(self, '_container') is not None): | |||
return _Wrapper_findattr(self, name, None, None, None, True, | |||
type(self)._IS_IMPLICIT, False, False) | |||
return _OGA(self, name) | |||
def __of__(self, parent): | |||
# Based on __of__ in the C code; | |||
# simplify a layer of wrapping. | |||
# We have to call the raw __of__ method or we recurse on our | |||
# own lookup (the C code does not have this issue, it can use | |||
# the wrapped __of__ method because it gets here via the | |||
# descriptor code path)... | |||
wrapper = self._obj.__of__(parent) | |||
if (not isinstance(wrapper, _Wrapper) or | |||
not isinstance(wrapper._container, _Wrapper)): | |||
return wrapper | |||
# but the returned wrapper should be based on this object's | |||
# wrapping chain | |||
wrapper._obj = self | |||
while (isinstance(wrapper._obj, _Wrapper) and | |||
(wrapper._obj._container is wrapper._container._obj)): | |||
# Since we mutate the wrapper as we walk up, we must copy | |||
# XXX: This comes from the C implementation. Do we really need to | |||
# copy? | |||
wrapper = type(wrapper)(wrapper._obj, wrapper._container) | |||
wrapper._obj = wrapper._obj._obj | |||
return wrapper | |||
def aq_acquire(self, name, | |||
filter=None, extra=None, | |||
explicit=True, | |||
default=_NOT_GIVEN, | |||
containment=False): | |||
try: | |||
return _Wrapper_findattr(self, name, | |||
predicate=filter, | |||
predicate_extra=extra, | |||
orig_object=self, | |||
search_self=True, | |||
search_parent=explicit or type(self)._IS_IMPLICIT, # NOQA | |||
explicit=explicit, | |||
containment=containment) | |||
except AttributeError: | |||
if default is _NOT_GIVEN: | |||
raise | |||
return default | |||
acquire = aq_acquire | |||
def aq_inContextOf(self, o, inner=True): | |||
return aq_inContextOf(self, o, inner=inner) | |||
# Wrappers themselves are not picklable, but if the underlying | |||
# object has a _p_oid, then the __getnewargs__ method is allowed | |||
def __reduce__(self, *args): | |||
raise TypeError("Can't pickle objects in acquisition wrappers.") | |||
__reduce_ex__ = __reduce__ | |||
__getstate__ = __reduce__ | |||
def __getnewargs__(self): | |||
return () | |||
# Equality and comparisons | |||
def __hash__(self): | |||
# The C implementation doesn't pass the wrapper | |||
# to any __hash__ that the object implements, | |||
# so it can't access derived attributes. | |||
# (If that changes, just add this to __unary_special_methods__ | |||
# and remove this method) | |||
return hash(self._obj) | |||
# The C implementation forces all comparisons through the | |||
# __cmp__ method, if it's implemented. If it's not implemented, | |||
# then comparisons are based strictly on the memory addresses | |||
# of the underlying object (aq_base). We could mostly emulate | |||
# this behaviour on Python 2, but on Python 3 __cmp__ is gone, | |||
# so users won't have an expectation to write it. | |||
# Because users have never had an expectation that the rich comparison | |||
# methods would be called on their wrapped objects (and so would not be | |||
# accessing acquired attributes there), we can't/don't want to start | |||
# proxying to them? | |||
# For the moment, we settle for an emulation of the C behaviour: | |||
# define __cmp__ the same way, and redirect the rich comparison operators | |||
# to it. (Note that these attributes are also hardcoded in getattribute) | |||
def __cmp__(self, other): | |||
aq_self = self._obj | |||
if hasattr(type(aq_self), '__cmp__'): | |||
return _rebound_method(aq_self.__cmp__, self)(other) | |||
my_base = aq_base(self) | |||
other_base = aq_base(other) | |||
if my_base is other_base: | |||
return 0 | |||
return -1 if id(my_base) < id(other_base) else 1 | |||
def __eq__(self, other): | |||
return self.__cmp__(other) == 0 | |||
def __ne__(self, other): | |||
return self.__cmp__(other) != 0 | |||
def __lt__(self, other): | |||
return self.__cmp__(other) < 0 | |||
def __le__(self, other): | |||
return self.__cmp__(other) <= 0 | |||
def __gt__(self, other): | |||
return self.__cmp__(other) > 0 | |||
def __ge__(self, other): | |||
return self.__cmp__(other) >= 0 | |||
# Special methods looked up by the type of self._obj, | |||
# but which must have the wrapper as self when called | |||
def __nonzero__(self): | |||
aq_self = self._obj | |||
type_aq_self = type(aq_self) | |||
nonzero = getattr(type_aq_self, '__nonzero__', None) | |||
if nonzero is None: | |||
# Py3 bool? | |||
nonzero = getattr(type_aq_self, '__bool__', None) | |||
if nonzero is None: | |||
# a len? | |||
nonzero = getattr(type_aq_self, '__len__', None) | |||
if nonzero: | |||
return bool(nonzero(self)) # Py3 is strict about the return type | |||
# If nothing was defined, then it's true | |||
return True | |||
__bool__ = __nonzero__ | |||
def __unicode__(self): | |||
f = getattr(self.aq_self, '__unicode__', | |||
getattr(self.aq_self, '__str__', object.__str__)) | |||
return _rebound_method(f, self)() | |||
def __repr__(self): | |||
aq_self = self._obj | |||
try: | |||
return _rebound_method(aq_self.__repr__, self)() | |||
except (AttributeError, TypeError): | |||
return repr(aq_self) | |||
def __str__(self): | |||
aq_self = self._obj | |||
try: | |||
return _rebound_method(aq_self.__str__, self)() | |||
except (AttributeError, TypeError): # pragma: no cover (Only Py3) | |||
return str(aq_self) | |||
__binary_special_methods__ = [ | |||
# general numeric | |||
'__add__', | |||
'__sub__', | |||
'__mul__', | |||
'__matmul__', | |||
'__floordiv__', # not implemented in C | |||
'__mod__', | |||
'__divmod__', | |||
'__pow__', | |||
'__lshift__', | |||
'__rshift__', | |||
'__and__', | |||
'__xor__', | |||
'__or__', | |||
# division; only one of these will be used at any one time | |||
'__truediv__', | |||
'__div__', | |||
# reflected numeric | |||
'__radd__', | |||
'__rsub__', | |||
'__rmul__', | |||
'__rdiv__', | |||
'__rtruediv__', | |||
'__rfloordiv__', | |||
'__rmod__', | |||
'__rdivmod__', | |||
'__rpow__', | |||
'__rlshift__', | |||
'__rrshift__', | |||
'__rand__', | |||
'__rxor__', | |||
'__ror__', | |||
# in place numeric | |||
'__iadd__', | |||
'__isub__', | |||
'__imul__', | |||
'__imatmul__', | |||
'__idiv__', | |||
'__itruediv__', | |||
'__ifloordiv__', | |||
'__imod__', | |||
'__idivmod__', | |||
'__ipow__', | |||
'__ilshift__', | |||
'__irshift__', | |||
'__iand__', | |||
'__ixor__', | |||
'__ior__', | |||
# conversion | |||
'__coerce__', | |||
# container | |||
'__delitem__', | |||
] | |||
__unary_special_methods__ = [ | |||
# arithmetic | |||
'__neg__', | |||
'__pos__', | |||
'__abs__', | |||
'__invert__', | |||
# conversion | |||
'__complex__', | |||
'__int__', | |||
'__long__', | |||
'__float__', | |||
'__oct__', | |||
'__hex__', | |||
'__index__', | |||
# '__len__', | |||
# strings are special | |||
# '__repr__', | |||
# '__str__', | |||
] | |||
for _name in __binary_special_methods__: | |||
def _make_op(_name): | |||
def op(self, other): | |||
aq_self = self._obj | |||
return getattr(type(aq_self), _name)(self, other) | |||
return op | |||
locals()[_name] = _make_op(_name) | |||
for _name in __unary_special_methods__: | |||
def _make_op(_name): | |||
def op(self): | |||
aq_self = self._obj | |||
return getattr(type(aq_self), _name)(self) | |||
return op | |||
locals()[_name] = _make_op(_name) | |||
del _make_op | |||
del _name | |||
# Container protocol | |||
def __len__(self): | |||
# if len is missing, it should raise TypeError | |||
# (AttributeError is acceptable under Py2, but Py3 | |||
# breaks list conversion if AttributeError is raised) | |||
try: | |||
l = getattr(type(self._obj), '__len__') | |||
except AttributeError: | |||
raise TypeError('object has no len()') | |||
else: | |||
return l(self) | |||
def __iter__(self): | |||
# For things that provide either __iter__ or just __getitem__, | |||
# we need to be sure that the wrapper is provided as self | |||
if hasattr(self._obj, '__iter__'): | |||
return _rebound_method(self._obj.__iter__, self)() | |||
if hasattr(self._obj, '__getitem__'): | |||
# Unfortunately we cannot simply call iter(self._obj) | |||
# and rebind im_self like we do above: the Python runtime | |||
# complains: | |||
# (TypeError: 'sequenceiterator' expected, got 'Wrapper' instead) | |||
class WrapperIter(object): | |||
__slots__ = ('_wrapper',) | |||
def __init__(self, o): | |||
self._wrapper = o | |||
def __getitem__(self, i): | |||
return self._wrapper.__getitem__(i) | |||
it = WrapperIter(self) | |||
return iter(it) | |||
return iter(self._obj) | |||
def __contains__(self, item): | |||
# First, if the type of the object defines __contains__ then | |||
# use it | |||
aq_self = self._obj | |||
aq_contains = getattr(type(aq_self), '__contains__', None) | |||
if aq_contains: | |||
return aq_contains(self, item) | |||
# Next, we should attempt to iterate like the interpreter; | |||
# but the C code doesn't do this, so we don't either. | |||
# return item in iter(self) | |||
raise AttributeError('__contains__') | |||
def __setitem__(self, key, value): | |||
aq_self = self._obj | |||
try: | |||
setter = type(aq_self).__setitem__ | |||
except AttributeError: | |||
raise AttributeError("__setitem__") # doctests care about the name | |||
else: | |||
setter(self, key, value) | |||
def __getitem__(self, key): | |||
if isinstance(key, slice) and hasattr(operator, 'getslice'): | |||
# Only on Python 2 | |||
# XXX: This is probably not proxying correctly, but the existing | |||
# tests pass with this behaviour | |||
return operator.getslice( | |||
self._obj, | |||
key.start if key.start is not None else 0, | |||
key.stop if key.stop is not None else sys.maxint) | |||
aq_self = self._obj | |||
try: | |||
getter = type(aq_self).__getitem__ | |||
except AttributeError: | |||
raise AttributeError("__getitem__") # doctests care about the name | |||
else: | |||
return getter(self, key) | |||
def __call__(self, *args, **kwargs): | |||
try: | |||
# Note we look this up on the completely unwrapped | |||
# object, so as not to get a class | |||
call = getattr(self.aq_base, '__call__') | |||
except AttributeError: # pragma: no cover | |||
# A TypeError is what the interpreter raises; | |||
# AttributeError is allowed to percolate through the | |||
# C proxy | |||
raise TypeError('object is not callable') | |||
else: | |||
return _rebound_method(call, self)(*args, **kwargs) | |||
class ImplicitAcquisitionWrapper(_Wrapper): | |||
_IS_IMPLICIT = True | |||
class ExplicitAcquisitionWrapper(_Wrapper): | |||
_IS_IMPLICIT = False | |||
def __getattribute__(self, name): | |||
# Special case backwards-compatible acquire method | |||
if name == 'acquire': | |||
return object.__getattribute__(self, name) | |||
return _Wrapper.__getattribute__(self, name) | |||
class _Acquirer(ExtensionClass.Base): | |||
def __getattribute__(self, name): | |||
try: | |||
return super(_Acquirer, self).__getattribute__(name) | |||
except AttributeError: | |||
# the doctests have very specific error message | |||
# requirements (but at least we can preserve the traceback) | |||
_, _, tb = sys.exc_info() | |||
try: | |||
_reraise(AttributeError, AttributeError(name), tb) | |||
finally: | |||
del tb | |||
def __of__(self, context): | |||
return type(self)._Wrapper(self, context) | |||
class Implicit(_Acquirer): | |||
_Wrapper = ImplicitAcquisitionWrapper | |||
ImplicitAcquisitionWrapper._Wrapper = ImplicitAcquisitionWrapper | |||
class Explicit(_Acquirer): | |||
_Wrapper = ExplicitAcquisitionWrapper | |||
ExplicitAcquisitionWrapper._Wrapper = ExplicitAcquisitionWrapper | |||
### | |||
# Exported module functions | |||
### | |||
def aq_acquire(obj, name, | |||
filter=None, extra=None, | |||
explicit=True, | |||
default=_NOT_GIVEN, | |||
containment=False): | |||
if isinstance(obj, _Wrapper): | |||
return obj.aq_acquire(name, | |||
filter=filter, extra=extra, | |||
default=default, | |||
explicit=explicit or type(obj)._IS_IMPLICIT, | |||
containment=containment) | |||
# Does it have a parent, or do we have a filter? | |||
# Then go through the acquisition code | |||
if hasattr(obj, '__parent__') or filter is not None: | |||
parent = getattr(obj, '__parent__', None) | |||
return aq_acquire(ImplicitAcquisitionWrapper(obj, parent), | |||
name, | |||
filter=filter, extra=extra, | |||
default=default, | |||
explicit=explicit, | |||
containment=containment) | |||
# no parent and no filter, simple case | |||
try: | |||
return getattr(obj, name) | |||
except AttributeError: | |||
if default is _NOT_GIVEN: | |||
raise AttributeError(name) # doctests are strict | |||
return default | |||
def aq_parent(obj): | |||
# needs to be safe to call from __getattribute__ of a wrapper | |||
# and reasonably fast | |||
if isinstance(obj, _Wrapper): | |||
return object.__getattribute__(obj, '_container') | |||
# if not a wrapper, deal with the __parent__ | |||
return getattr(obj, '__parent__', None) | |||
def aq_chain(obj, containment=False): | |||
result = [] | |||
while True: | |||
if isinstance(obj, _Wrapper): | |||
if obj._obj is not None: | |||
if containment: | |||
while isinstance(obj._obj, _Wrapper): | |||
obj = obj._obj | |||
result.append(obj) | |||
if obj._container is not None: | |||
obj = obj._container | |||
continue | |||
else: | |||
result.append(obj) | |||
obj = getattr(obj, '__parent__', None) | |||
if obj is not None: | |||
continue | |||
break | |||
return result | |||
def aq_base(obj): | |||
result = obj | |||
while isinstance(result, _Wrapper): | |||
result = result._obj | |||
return result | |||
def aq_get(obj, name, default=_NOT_GIVEN, containment=False): | |||
# Not wrapped. If we have a __parent__ pointer, create a wrapper | |||
# and go as usual | |||
if not isinstance(obj, _Wrapper) and hasattr(obj, '__parent__'): | |||
obj = ImplicitAcquisitionWrapper(obj, obj.__parent__) | |||
try: | |||
# We got a wrapped object, business as usual | |||
return (_Wrapper_findattr(obj, name, None, None, obj, | |||
True, True, True, containment) | |||
if isinstance(obj, _Wrapper) | |||
# ok, plain getattr | |||
else getattr(obj, name)) | |||
except AttributeError: | |||
if default is _NOT_GIVEN: | |||
raise | |||
return default | |||
def aq_inner(obj): | |||
if not isinstance(obj, _Wrapper): | |||
return obj | |||
result = obj._obj | |||
while isinstance(result, _Wrapper): | |||
obj = result | |||
result = result._obj | |||
result = obj | |||
return result | |||
def aq_self(obj): | |||
if isinstance(obj, _Wrapper): | |||
return obj.aq_self | |||
return obj | |||
def aq_inContextOf(self, o, inner=True): | |||
next = self | |||
o = aq_base(o) | |||
while True: | |||
if aq_base(next) is o: | |||
return True | |||
if inner: | |||
self = aq_inner(next) | |||
if self is None: # pragma: no cover | |||
# This branch is normally impossible to hit, | |||
# it just mirrors a check in C | |||
break | |||
else: | |||
self = next | |||
next = aq_parent(self) | |||
if next is None: | |||
break | |||
return False | |||
if not (IS_PYPY or IS_PURE): # pragma: no cover | |||
# Make sure we can import the C extension of our dependency. | |||
from ExtensionClass import _ExtensionClass # NOQA | |||
from ._Acquisition import * # NOQA | |||
classImplements(Explicit, IAcquirer) | |||
classImplements(ExplicitAcquisitionWrapper, IAcquisitionWrapper) | |||
classImplements(Implicit, IAcquirer) | |||
classImplements(ImplicitAcquisitionWrapper, IAcquisitionWrapper) |
@@ -0,0 +1,62 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2005 Zope Foundation and Contributors. | |||
# | |||
# 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. | |||
# | |||
############################################################################## | |||
"""Acquisition z3 interfaces. | |||
$Id$ | |||
""" | |||
from zope.interface import Attribute | |||
from zope.interface import Interface | |||
class IAcquirer(Interface): | |||
"""Acquire attributes from containers. | |||
""" | |||
def __of__(context): | |||
"""Get the object in a context. | |||
""" | |||
class IAcquisitionWrapper(Interface): | |||
"""Wrapper object for acquisition. | |||
""" | |||
def aq_acquire(name, filter=None, extra=None, explicit=True, default=0, | |||
containment=0): | |||
"""Get an attribute, acquiring it if necessary. | |||
""" | |||
def aq_inContextOf(obj, inner=1): | |||
"""Test whether the object is currently in the context of the argument. | |||
""" | |||
aq_base = Attribute( | |||
"""Get the object unwrapped.""") | |||
aq_parent = Attribute( | |||
"""Get the parent of an object.""") | |||
aq_self = Attribute( | |||
"""Get the object with the outermost wrapper removed.""") | |||
aq_inner = Attribute( | |||
"""Get the object with all but the innermost wrapper removed.""") | |||
aq_chain = Attribute( | |||
"""Get a list of objects in the acquisition environment.""") | |||
aq_explicit = Attribute( | |||
"""Get the object with an explicit acquisition wrapper.""") |
@@ -0,0 +1,25 @@ | |||
Overview | |||
======== | |||
AuthEncoding is a framework for handling LDAP style password hashes. | |||
It is used in Zope2 but does not depend on any other Zope package. | |||
Changelog | |||
========= | |||
4.0.0 (2015-09-30) | |||
------------------ | |||
- Supporting Python 3.3 up to 3.5 and PyPy2. | |||
- Added ``SHA256DigestScheme``. | |||
3.0.0 (2015-09-28) | |||
------------------ | |||
- Extracted from ``AccessControl 3.0.11`` | |||
@@ -0,0 +1 @@ | |||
pip |
@@ -0,0 +1,54 @@ | |||
Metadata-Version: 2.0 | |||
Name: AuthEncoding | |||
Version: 4.0.0 | |||
Summary: Framework for handling LDAP style password hashes. | |||
Home-page: https://github.com/zopefoundation/AuthEncoding | |||
Author: Zope Foundation and Contributors | |||
Author-email: zope-dev@zope.org | |||
License: ZPL 2.1 | |||
Platform: UNKNOWN | |||
Classifier: Development Status :: 6 - Mature | |||
Classifier: Environment :: Web Environment | |||
Classifier: Framework :: Zope2 | |||
Classifier: License :: OSI Approved :: Zope Public License | |||
Classifier: Operating System :: OS Independent | |||
Classifier: Programming Language :: Python | |||
Classifier: Programming Language :: Python :: 2 | |||
Classifier: Programming Language :: Python :: 2.6 | |||
Classifier: Programming Language :: Python :: 2.7 | |||
Classifier: Programming Language :: Python :: 3 | |||
Classifier: Programming Language :: Python :: 3.3 | |||
Classifier: Programming Language :: Python :: 3.4 | |||
Classifier: Programming Language :: Python :: 3.5 | |||
Classifier: Programming Language :: Python :: Implementation :: CPython | |||
Classifier: Programming Language :: Python :: Implementation :: PyPy | |||
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP | |||
Requires-Dist: six | |||
Provides-Extra: test | |||
Requires-Dist: pytest; extra == 'test' | |||
Overview | |||
======== | |||
AuthEncoding is a framework for handling LDAP style password hashes. | |||
It is used in Zope2 but does not depend on any other Zope package. | |||
Changelog | |||
========= | |||
4.0.0 (2015-09-30) | |||
------------------ | |||
- Supporting Python 3.3 up to 3.5 and PyPy2. | |||
- Added ``SHA256DigestScheme``. | |||
3.0.0 (2015-09-28) | |||
------------------ | |||
- Extracted from ``AccessControl 3.0.11`` | |||
@@ -0,0 +1,18 @@ | |||
AuthEncoding-4.0.0.dist-info/DESCRIPTION.rst,sha256=UKoEVLqA6eoGuaGlkVcj4mZJUnzBr3IdyZ4rLj9tBY4,381 | |||
AuthEncoding-4.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | |||
AuthEncoding-4.0.0.dist-info/METADATA,sha256=LMz4ZjZ94m7OrJJAs4_0fs1X4C-gvWPTEw8uGwX-LvY,1588 | |||
AuthEncoding-4.0.0.dist-info/RECORD,, | |||
AuthEncoding-4.0.0.dist-info/RECORD.jws,, | |||
AuthEncoding-4.0.0.dist-info/WHEEL,sha256=5wvfB7GvgZAbKBSE9uX9Zbi6LCL-_KgezgHblXhCRnM,113 | |||
AuthEncoding-4.0.0.dist-info/metadata.json,sha256=yBATAjh7I5YnUKtIQHGhhActRCg60BSsaAeM0r5dPFY,1294 | |||
AuthEncoding-4.0.0.dist-info/top_level.txt,sha256=r_cFzzt28gdOYPoRX7HjrbsJE6G-ZPK0n3sWbqE4PdQ,13 | |||
AuthEncoding/AuthEncoding.py,sha256=A82pAZQA-r1NSG7jBf1qDJn4utAQXMHMSF09Do35t0w,7265 | |||
AuthEncoding/__init__.py,sha256=9fdxLPEIHDUW2ig-pE92y7BkZAHItLvvFpgXTs4byiU,791 | |||
AuthEncoding/__pycache__/AuthEncoding.cpython-36.pyc,, | |||
AuthEncoding/__pycache__/__init__.cpython-36.pyc,, | |||
AuthEncoding/__pycache__/compat.cpython-36.pyc,, | |||
AuthEncoding/compat.py,sha256=o2nbexNN16x67B8xLKZRQQo_F4LheBnRtKgO---vuPY,347 | |||
AuthEncoding/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 | |||
AuthEncoding/tests/__pycache__/__init__.cpython-36.pyc,, | |||
AuthEncoding/tests/__pycache__/test_AuthEncoding.cpython-36.pyc,, | |||
AuthEncoding/tests/test_AuthEncoding.py,sha256=zEj6Uz2eC3VPxmafNcy2ZvdSQLfVSu_Yf9X63X2XHfM,3814 |
@@ -0,0 +1 @@ | |||
{"payload": "eyJoYXNoIjogInNoYTI1Nj1FbUpVbjFTUEdYeXhKc2FnMWpxNGJkMnlnYW80TlhzY2hORW5iSEFISHdnIn0", "recipients": [{"header": "eyJhbGciOiAiRWQyNTUxOSIsICJqd2siOiB7Imt0eSI6ICJFZDI1NTE5IiwgInZrIjogInZUZU1lalVPMTV1SkQzY203enZPLV9oc0dMSld4ZlFNMEFFd2R5SUQyeTQifX0", "signature": "D1i5q57sD-x3nU_8L_49SwW7b--DjetQhUpnMDwDDui6PVsLfsAr65teCK_GbT4txrxkqzCHEMV8byYKzz4XDw"}]} |
@@ -0,0 +1,6 @@ | |||
Wheel-Version: 1.0 | |||
Generator: bdist_wheel (0.30.0.a0) | |||
Root-Is-Purelib: true | |||
Tag: py2-none-any | |||
Tag: py3-none-any | |||
@@ -0,0 +1 @@ | |||
{"classifiers": ["Development Status :: 6 - Mature", "Environment :: Web Environment", "Framework :: Zope2", "License :: OSI Approved :: Zope Public License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP"], "extensions": {"python.details": {"contacts": [{"email": "zope-dev@zope.org", "name": "Zope Foundation and Contributors", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/zopefoundation/AuthEncoding"}}}, "extras": ["test"], "generator": "bdist_wheel (0.30.0.a0)", "license": "ZPL 2.1", "metadata_version": "2.0", "name": "AuthEncoding", "run_requires": [{"extra": "test", "requires": ["pytest"]}, {"requires": ["six"]}], "summary": "Framework for handling LDAP style password hashes.", "version": "4.0.0"} |
@@ -0,0 +1 @@ | |||
AuthEncoding |
@@ -0,0 +1,253 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2002, 2015 Zope Foundation and Contributors. | |||
# | |||
# 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 binascii | |||
import six | |||
from binascii import b2a_base64, a2b_base64 | |||
from hashlib import sha1 as sha | |||
from hashlib import sha256 | |||
from os import getpid | |||
import time | |||
from .compat import long, b, u | |||
# Use the system PRNG if possible | |||
import random | |||
try: | |||
random = random.SystemRandom() | |||
using_sysrandom = True | |||
except NotImplementedError: | |||
using_sysrandom = False | |||
def _reseed(): | |||
if not using_sysrandom: | |||
# This is ugly, and a hack, but it makes things better than | |||
# the alternative of predictability. This re-seeds the PRNG | |||
# using a value that is hard for an attacker to predict, every | |||
# time a random string is required. This may change the | |||
# properties of the chosen random sequence slightly, but this | |||
# is better than absolute predictability. | |||
random.seed(sha256( | |||
"%s%s%s" % (random.getstate(), time.time(), getpid()) | |||
).digest()) | |||
def _choice(c): | |||
_reseed() | |||
return random.choice(c) | |||
def _randrange(r): | |||
_reseed() | |||
return random.randrange(r) | |||
def constant_time_compare(val1, val2): | |||
""" | |||
Returns True if the two strings are equal, False otherwise. | |||
The time taken is independent of the number of characters that match. | |||
""" | |||
if len(val1) != len(val2): | |||
return False | |||
result = 0 | |||
for x, y in zip(six.iterbytes(val1), six.iterbytes(val2)): | |||
result |= x ^ y | |||
return result == 0 | |||
class PasswordEncryptionScheme: # An Interface | |||
def encrypt(pw): | |||
""" | |||
Encrypt the provided plain text password. | |||
""" | |||
def validate(reference, attempt): | |||
""" | |||
Validate the provided password string. Reference is the | |||
correct password, which may be encrypted; attempt is clear text | |||
password attempt. | |||
""" | |||
_schemes = [] | |||
def registerScheme(id, s): | |||
''' | |||
Registers an LDAP password encoding scheme. | |||
''' | |||
_schemes.append((id, u'{%s}' % id, s)) | |||
def listSchemes(): | |||
return [id for id, prefix, scheme in _schemes] | |||
class SSHADigestScheme: | |||
''' | |||
SSHA is a modification of the SHA digest scheme with a salt | |||
starting at byte 20 of the base64-encoded string. | |||
''' | |||
# Source: http://developer.netscape.com/docs/technote/ldap/pass_sha.html | |||
def generate_salt(self): | |||
# Salt can be any length, but not more than about 37 characters | |||
# because of limitations of the binascii module. | |||
# 7 is what Netscape's example used and should be enough. | |||
# All 256 characters are available. | |||
salt = b'' | |||
for n in range(7): | |||
salt += six.int2byte(_randrange(256)) | |||
return salt | |||
def encrypt(self, pw): | |||
return self._encrypt_with_salt(pw, self.generate_salt()) | |||
def validate(self, reference, attempt): | |||
try: | |||
ref = a2b_base64(reference) | |||
except binascii.Error: | |||
# Not valid base64. | |||
return 0 | |||
salt = ref[20:] | |||
compare = self._encrypt_with_salt(attempt, salt) | |||
return constant_time_compare(compare, reference) | |||
def _encrypt_with_salt(self, pw, salt): | |||
pw = b(pw) | |||
return b2a_base64(sha(pw + salt).digest() + salt)[:-1] | |||
registerScheme(u'SSHA', SSHADigestScheme()) | |||
class SHADigestScheme: | |||
def encrypt(self, pw): | |||
return self._encrypt(pw) | |||
def validate(self, reference, attempt): | |||
compare = self._encrypt(attempt) | |||
return constant_time_compare(compare, reference) | |||
def _encrypt(self, pw): | |||
pw = b(pw) | |||
return b2a_base64(sha(pw).digest())[:-1] | |||
registerScheme(u'SHA', SHADigestScheme()) | |||
class SHA256DigestScheme: | |||
def encrypt(self, pw): | |||
return b(sha256(b(pw)).hexdigest()) | |||
def validate(self, reference, attempt): | |||
a = self.encrypt(attempt) | |||
return constant_time_compare(a, reference) | |||
registerScheme(u'SHA256', SHA256DigestScheme()) | |||
# Bogosity on various platforms due to ITAR restrictions | |||
try: | |||
from crypt import crypt | |||
except ImportError: | |||
crypt = None | |||
if crypt is not None: | |||
class CryptDigestScheme: | |||
def generate_salt(self): | |||
choices = (u"ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |||
u"abcdefghijklmnopqrstuvwxyz" | |||
u"0123456789./") | |||
return _choice(choices) + _choice(choices) | |||
def encrypt(self, pw): | |||
return b(crypt(self._recode_password(pw), self.generate_salt())) | |||
def validate(self, reference, attempt): | |||
attempt = self._recode_password(attempt) | |||
a = b(crypt(attempt, reference[:2].decode('ascii'))) | |||
return constant_time_compare(a, reference) | |||
def _recode_password(self, pw): | |||
# crypt always requires `str` which has a different meaning among | |||
# the Python versions: | |||
if six.PY3: | |||
return u(pw) | |||
return b(pw) | |||
registerScheme(u'CRYPT', CryptDigestScheme()) | |||
class MySQLDigestScheme: | |||
def encrypt(self, pw): | |||
pw = u(pw) | |||
nr = long(1345345333) | |||
add = 7 | |||
nr2 = long(0x12345671) | |||
for i in pw: | |||
if i == ' ' or i == '\t': | |||
continue | |||
nr ^= (((nr & 63) + add) * ord(i)) + (nr << 8) | |||
nr2 += (nr2 << 8) ^ nr | |||
add += ord(i) | |||
r0 = nr & ((long(1) << 31) - long(1)) | |||
r1 = nr2 & ((long(1) << 31) - long(1)) | |||
return (u"%08lx%08lx" % (r0, r1)).encode('ascii') | |||
def validate(self, reference, attempt): | |||
a = self.encrypt(attempt) | |||
return constant_time_compare(a, reference) | |||
registerScheme(u'MYSQL', MySQLDigestScheme()) | |||
def pw_validate(reference, attempt): | |||
"""Validate the provided password string, which uses LDAP-style encoding | |||
notation. Reference is the correct password, attempt is clear text | |||
password attempt.""" | |||
reference = b(reference) | |||
for id, prefix, scheme in _schemes: | |||
lp = len(prefix) | |||
if reference[:lp] == b(prefix): | |||
return scheme.validate(reference[lp:], attempt) | |||
# Assume cleartext. | |||
return constant_time_compare(reference, b(attempt)) | |||
def is_encrypted(pw): | |||
for id, prefix, scheme in _schemes: | |||
lp = len(prefix) | |||
if pw[:lp] == b(prefix): | |||
return 1 | |||
return 0 | |||
def pw_encrypt(pw, encoding=u'SSHA'): | |||
"""Encrypt the provided plain text password using the encoding if provided | |||
and return it in an LDAP-style representation.""" | |||
encoding = u(encoding) | |||
for id, prefix, scheme in _schemes: | |||
if encoding == id: | |||
return b(prefix) + scheme.encrypt(pw) | |||
raise ValueError('Not supported: %s' % encoding) | |||
pw_encode = pw_encrypt # backward compatibility |
@@ -0,0 +1,17 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2002,2015 Zope Foundation and Contributors. | |||
# | |||
# 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 .AuthEncoding import (is_encrypted, pw_encrypt, pw_validate, | |||
registerScheme, listSchemes, | |||
constant_time_compare) |
@@ -0,0 +1,20 @@ | |||
import six | |||
if six.PY3: | |||
long = int | |||
else: | |||
long = long | |||
def b(arg): | |||
"""Convert `arg` to bytes.""" | |||
if isinstance(arg, six.text_type): | |||
arg = arg.encode("latin-1") | |||
return arg | |||
def u(arg): | |||
"""Convert `arg` to text.""" | |||
if isinstance(arg, six.binary_type): | |||
arg = arg.decode('ascii', 'replace') | |||
return arg |
@@ -0,0 +1,101 @@ | |||
# -*- coding: utf-8 -*- | |||
############################################################################## | |||
# | |||
# Copyright (c) 2002, 2015 Zope Foundation and Contributors. | |||
# | |||
# 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 of AuthEncoding | |||
""" | |||
from AuthEncoding import AuthEncoding | |||
from ..compat import b, u | |||
import pytest | |||
def testListSchemes(): | |||
assert len(AuthEncoding.listSchemes()) > 0 # At least one must exist! | |||
@pytest.mark.parametrize('schema_id', AuthEncoding.listSchemes()) | |||
@pytest.mark.parametrize('password', [u'good_pw', u'gööd_pw', b(u'gööd_pw')]) | |||
def testGoodPassword(schema_id, password): | |||
enc = AuthEncoding.pw_encrypt(password, schema_id) | |||
assert enc != password | |||
assert AuthEncoding.pw_validate(enc, password) | |||
assert AuthEncoding.pw_validate(u(enc), password) | |||
assert AuthEncoding.is_encrypted(enc) | |||
assert not AuthEncoding.is_encrypted(password) | |||
@pytest.mark.parametrize('schema_id', AuthEncoding.listSchemes()) | |||
@pytest.mark.parametrize( | |||
'password', [u'OK_pa55w0rd \n', u'OK_pä55w0rd \n', b(u'OK_pä55w0rd \n')]) | |||
def testBadPassword(schema_id, password): | |||
enc = AuthEncoding.pw_encrypt(password, schema_id) | |||
assert enc != password | |||
assert not AuthEncoding.pw_validate(enc, u'xxx') | |||
assert not AuthEncoding.pw_validate(enc, b'xxx') | |||
assert not AuthEncoding.pw_validate(u(enc), u'xxx') | |||
assert not AuthEncoding.pw_validate(enc, enc) | |||
if schema_id != u'CRYPT': | |||
# crypt truncates passwords and would fail this test. | |||
assert not AuthEncoding.pw_validate(enc, password[:-1]) | |||
assert not AuthEncoding.pw_validate(enc, password[1:]) | |||
assert AuthEncoding.pw_validate(enc, password) | |||
@pytest.mark.parametrize('schema_id', AuthEncoding.listSchemes()) | |||
def testShortPassword(schema_id): | |||
pw = u'1' | |||
enc = AuthEncoding.pw_encrypt(pw, schema_id) | |||
assert AuthEncoding.pw_validate(enc, pw) | |||
assert not AuthEncoding.pw_validate(enc, enc) | |||
assert not AuthEncoding.pw_validate(enc, u'xxx') | |||
@pytest.mark.parametrize('schema_id', AuthEncoding.listSchemes()) | |||
def testLongPassword(schema_id): | |||
pw = u'Pw' * 2000 | |||
enc = AuthEncoding.pw_encrypt(pw, schema_id) | |||
assert AuthEncoding.pw_validate(enc, pw) | |||
assert not AuthEncoding.pw_validate(enc, enc) | |||
assert not AuthEncoding.pw_validate(enc, u'xxx') | |||
if u'CRYPT' not in schema_id: | |||
# crypt and bcrypt truncates passwords and would fail these tests. | |||
assert not AuthEncoding.pw_validate(enc, pw[:-2]) | |||
assert not AuthEncoding.pw_validate(enc, pw[2:]) | |||
@pytest.mark.parametrize('schema_id', AuthEncoding.listSchemes()) | |||
def testBlankPassword(schema_id): | |||
pw = u'' | |||
enc = AuthEncoding.pw_encrypt(pw, schema_id) | |||
assert enc != pw | |||
assert AuthEncoding.pw_validate(enc, pw) | |||
assert not AuthEncoding.pw_validate(enc, enc) | |||
assert not AuthEncoding.pw_validate(enc, u'xxx') | |||
def testUnencryptedPassword(): | |||
# Sanity check | |||
pw = u'my-password' | |||
assert AuthEncoding.pw_validate(pw, pw) | |||
assert not AuthEncoding.pw_validate(pw, pw + u'asdf') | |||
def testEncryptWithNotSupportedScheme(): | |||
with pytest.raises(ValueError) as err: | |||
AuthEncoding.pw_encrypt(u'asdf', 'MD1') | |||
assert 'Not supported: MD1' == str(err.value) | |||
def testEncryptAcceptsTextAndBinaryEncodingNames(): | |||
assert (AuthEncoding.pw_encrypt(u'asdf', b'SHA') == | |||
AuthEncoding.pw_encrypt(u'asdf', u'SHA')) |
@@ -0,0 +1 @@ | |||
pip |
@@ -0,0 +1,348 @@ | |||
Metadata-Version: 2.1 | |||
Name: BTrees | |||
Version: 4.5.1 | |||
Summary: Scalable persistent object containers | |||
Home-page: https://github.com/zopefoundation/BTrees | |||
Author: Zope Foundation | |||
Author-email: zodb-dev@zope.org | |||
License: ZPL 2.1 | |||
Platform: any | |||
Classifier: Development Status :: 6 - Mature | |||
Classifier: License :: OSI Approved :: Zope Public License | |||
Classifier: Programming Language :: Python | |||
Classifier: Programming Language :: Python :: 2 | |||
Classifier: Programming Language :: Python :: 2.7 | |||
Classifier: Programming Language :: Python :: 3 | |||
Classifier: Programming Language :: Python :: 3.4 | |||
Classifier: Programming Language :: Python :: 3.5 | |||
Classifier: Programming Language :: Python :: 3.6 | |||
Classifier: Programming Language :: Python :: 3.7 | |||
Classifier: Programming Language :: Python :: Implementation :: CPython | |||
Classifier: Programming Language :: Python :: Implementation :: PyPy | |||
Classifier: Framework :: ZODB | |||
Classifier: Topic :: Database | |||
Classifier: Topic :: Software Development :: Libraries :: Python Modules | |||
Classifier: Operating System :: Microsoft :: Windows | |||
Classifier: Operating System :: Unix | |||
Provides-Extra: test | |||
Provides-Extra: ZODB | |||
Provides-Extra: docs | |||
Requires-Dist: persistent (>=4.1.0) | |||
Requires-Dist: zope.interface | |||
Provides-Extra: ZODB | |||
Requires-Dist: ZODB; extra == 'ZODB' | |||
Provides-Extra: docs | |||
Requires-Dist: Sphinx; extra == 'docs' | |||
Requires-Dist: repoze.sphinx.autointerface; extra == 'docs' | |||
Provides-Extra: test | |||
Requires-Dist: transaction; extra == 'test' | |||
Requires-Dist: zope.testrunner; extra == 'test' | |||
``BTrees``: scalable persistent components | |||
=========================================== | |||
.. image:: https://travis-ci.org/zopefoundation/BTrees.svg?branch=master | |||
:target: https://travis-ci.org/zopefoundation/BTrees | |||
.. image:: https://ci.appveyor.com/api/projects/status/github/zopefoundation/BTrees?branch=master&svg=true | |||
:target: https://ci.appveyor.com/project/mgedmin/BTrees | |||
.. image:: https://coveralls.io/repos/github/zopefoundation/BTrees/badge.svg?branch=master | |||
:target: https://coveralls.io/github/zopefoundation/BTrees?branch=master | |||
.. image:: https://img.shields.io/pypi/v/BTrees.svg | |||
:target: https://pypi.org/project/BTrees/ | |||
:alt: Current version on PyPI | |||
.. image:: https://img.shields.io/pypi/pyversions/BTrees.svg | |||
:target: https://pypi.org/project/BTrees/ | |||
:alt: Supported Python versions | |||
This package contains a set of persistent object containers built around | |||
a modified BTree data structure. The trees are optimized for use inside | |||
ZODB's "optimistic concurrency" paradigm, and include explicit resolution | |||
of conflicts detected by that mechanism. | |||
Please see `the Sphinx documentation <http://btrees.readthedocs.io/>`_ for further | |||
information. | |||
``BTrees`` Changelog | |||
==================== | |||
4.5.1 (2018-08-09) | |||
------------------ | |||
- Produce binary wheels for Python 3.7. | |||
- Use pyproject.toml to specify build dependencies. This requires pip | |||
18 or later to build from source. | |||
4.5.0 (2018-04-23) | |||
------------------ | |||
- Add support for Python 3.6 and 3.7. | |||
- Drop support for Python 3.3. | |||
- Raise an ``ImportError`` consistently on Python 3 if the C extension for | |||
BTrees is used but the ``persistent`` C extension is not available. | |||
Previously this could result in an odd ``AttributeError``. See | |||
https://github.com/zopefoundation/BTrees/pull/55 | |||
- Fix the possibility of a rare crash in the C extension when | |||
deallocating items. See https://github.com/zopefoundation/BTrees/issues/75 | |||
- Respect the ``PURE_PYTHON`` environment variable at runtime even if | |||
the C extensions are available. See | |||
https://github.com/zopefoundation/BTrees/issues/78 | |||
- Always attempt to build the C extensions, but make their success | |||
optional. | |||
- Fix a ``DeprecationWarning`` that could come from I and L objects in | |||
Python 2 in pure-Python mode. See https://github.com/zopefoundation/BTrees/issues/79 | |||
4.4.1 (2017-01-24) | |||
------------------ | |||
Fixed a packaging bug that caused extra files to be included (some of | |||
which caused problems in some platforms). | |||
4.4.0 (2017-01-11) | |||
------------------ | |||
- Allow None as a special key (sorted smaller than all others). | |||
This is a bit of a return to BTrees 3 behavior in that Nones are | |||
allowed as keys again. Other objects with default ordering are | |||
still not allowed as keys. | |||
4.3.2 (2017-01-05) | |||
------------------ | |||
- Make the CPython implementation consistent with the pure-Python | |||
implementation and only check object keys for default comparison | |||
when setting keys. In Python 2 this makes it possible to remove keys | |||
that were added using a less restrictive version of BTrees. (In | |||
Python 3 keys that are unorderable still cannot be removed.) | |||
Likewise, all versions can unpickle trees that already had such | |||
keys. See: https://github.com/zopefoundation/BTrees/issues/53 and | |||
https://github.com/zopefoundation/BTrees/issues/51 | |||
- Make the Python implementation consistent with the CPython | |||
implementation and check object key identity before checking | |||
equality and performing comparisons. This can allow fixing trees | |||
that have keys that now have broken comparison functions. See | |||
https://github.com/zopefoundation/BTrees/issues/50 | |||
- Make the CPython implementation consistent with the pure-Python | |||
implementation and no longer raise ``TypeError`` for an object key | |||
(in object-keyed trees) with default comparison on ``__getitem__``, | |||
``get`` or ``in`` operations. Instead, the results will be a | |||
``KeyError``, the default value, and ``False``, respectively. | |||
Previously, CPython raised a ``TypeError`` in those cases, while the | |||
Python implementation behaved as specified. | |||
Likewise, non-integer keys in integer-keyed trees | |||
will raise ``KeyError``, return the default and return ``False``, | |||
respectively, in both implementations. Previously, pure-Python | |||
raised a ``KeyError``, returned the default, and raised a | |||
``TypeError``, while CPython raised ``TypeError`` in all three cases. | |||
4.3.1 (2016-05-16) | |||
------------------ | |||
- Packaging: fix password used to automate wheel creation on Travis. | |||
4.3.0 (2016-05-10) | |||
------------------ | |||
- Fix unexpected ``OverflowError`` when passing 64bit values to long | |||
keys / values on Win64. See: | |||
https://github.com/zopefoundation/BTrees/issues/32 | |||
- When testing ``PURE_PYTHON`` environments under ``tox``, avoid poisoning | |||
the user's global wheel cache. | |||
- Ensure that the pure-Python implementation, used on PyPy and when a C | |||
compiler isn't available for CPython, pickles identically to the C | |||
version. Unpickling will choose the best available implementation. | |||
This change prevents interoperability problems and database corruption if | |||
both implementations are in use. While it is no longer possible to | |||
pickle a Python implementation and have it unpickle to the Python | |||
implementation if the C implementation is available, existing Python | |||
pickles will still unpickle to the Python implementation (until | |||
pickled again). See: | |||
https://github.com/zopefoundation/BTrees/issues/19 | |||
- Avoid creating invalid objects when unpickling empty BTrees in a pure-Python | |||
environment. | |||
- Drop support for Python 2.6 and 3.2. | |||
4.2.0 (2015-11-13) | |||
------------------ | |||
- Add support for Python 3.5. | |||
4.1.4 (2015-06-02) | |||
------------------ | |||
- Ensure that pure-Python Bucket and Set objects have a human readable | |||
``__repr__`` like the C versions. | |||
4.1.3 (2015-05-19) | |||
------------------ | |||
- Fix ``_p_changed`` when removing items from small pure-Python | |||
BTrees/TreeSets and when adding items to small pure-Python Sets. See: | |||
https://github.com/zopefoundation/BTrees/issues/13 | |||
4.1.2 (2015-04-07) | |||
------------------ | |||
- Suppress testing 64-bit values in OLBTrees on 32 bit machines. | |||
See: https://github.com/zopefoundation/BTrees/issues/9 | |||
- Fix ``_p_changed`` when adding items to small pure-Python | |||
BTrees/TreeSets. See: | |||
https://github.com/zopefoundation/BTrees/issues/11 | |||
4.1.1 (2014-12-27) | |||
------------------ | |||
- Accomodate long values in pure-Python OLBTrees. | |||
4.1.0 (2014-12-26) | |||
------------------ | |||
- Add support for PyPy and PyPy3. | |||
- Add support for Python 3.4. | |||
- BTree subclasses can define ``max_leaf_size`` or ``max_internal_size`` | |||
to control maximum sizes for Bucket/Set and BTree/TreeSet nodes. | |||
- Detect integer overflow on 32-bit machines correctly under Python 3. | |||
- Update pure-Python and C trees / sets to accept explicit None to indicate | |||
max / min value for ``minKey``, ``maxKey``. (PR #3) | |||
- Update pure-Python trees / sets to accept explicit None to indicate | |||
open ranges for ``keys``, ``values``, ``items``. (PR #3) | |||
4.0.8 (2013-05-25) | |||
------------------ | |||
- Fix value-based comparison for objects under Py3k: addresses invalid | |||
merges of ``[OLI]OBTrees/OBuckets``. | |||
- Ensure that pure-Python implementation of ``OOBTree.byValue`` matches | |||
semantics (reversed-sort) of C implementation. | |||
4.0.7 (2013-05-22) | |||
------------------ | |||
- Issue #2: compilation error on 32-bit mode of OS/X. | |||
- Test ``PURE_PYTHON`` environment variable support: if set, the C | |||
extensions will not be built, imported, or tested. | |||
4.0.6 (2013-05-14) | |||
------------------ | |||
- Changed the ``ZODB`` extra to require only the real ``ZODB`` package, | |||
rather than the ``ZODB3`` metapackage: depending on the version used, | |||
the metapackage could pull in stale versions of **this** package and | |||
``persistent``. | |||
- Fixed Python version check in ``setup.py``. | |||
4.0.5 (2013-01-15) | |||
------------------ | |||
- Fit the ``repr`` of bucket objects, which could contain garbage | |||
characters. | |||
4.0.4 (2013-01-12) | |||
------------------ | |||
- Emulate the (private) iterators used by the C extension modules from | |||
pure Python. This change is "cosmetic" only: it prevents the ZCML | |||
``zope.app.security:permission.zcml`` from failing. The emulated | |||
classes are **not** functional, and should be considered implementation | |||
details. | |||
- Accomodate buildout to the fact that we no longer bundle a copy | |||
of 'persistent.h'. | |||
- Fix test failures on Windows: no longer rely on overflows from | |||
``sys.maxint``. | |||
4.0.3 (2013-01-04) | |||
------------------ | |||
- Added ``setup_requires==['persistent']``. | |||
4.0.2 (2013-01-03) | |||
------------------ | |||
- Updated Trove classifiers. | |||
- Added explicit support for Python 3.2, Python 3.3, and PyPy. | |||
Note that the C extensions are not (yet) available on PyPy. | |||
- Python reference implementations now tested separately from the C | |||
verions on all platforms. | |||
- 100% unit test coverage. | |||
4.0.1 (2012-10-21) | |||
------------------ | |||
- Provide local fallback for persistent C header inclusion if the | |||
persistent distribution isn't installed. This makes the winbot happy. | |||
4.0.0 (2012-10-20) | |||
------------------ | |||
Platform Changes | |||
################ | |||
- Dropped support for Python < 2.6. | |||
- Factored ``BTrees`` as a separate distribution. | |||
Testing Changes | |||
############### | |||
- All covered platforms tested under ``tox``. | |||
- Added support for continuous integration using ``tox`` and ``jenkins``. | |||
- Added ``setup.py dev`` alias (installs ``nose`` and ``coverage``). | |||
- Dropped dependency on ``zope.testing`` / ``zope.testrunner``: tests now | |||
run with ``setup.py test``. | |||
Documentation Changes | |||
##################### | |||
- Added API reference, generated via Spinx' autodoc. | |||
- Added Sphinx documentation based on ZODB Guide (snippets are exercised | |||
via 'tox'). | |||
- Added ``setup.py docs`` alias (installs ``Sphinx`` and | |||
``repoze.sphinx.autointerface``). | |||
@@ -0,0 +1,134 @@ | |||
BTrees-4.5.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 | |||
BTrees-4.5.1.dist-info/METADATA,sha256=Nb2ZkIBPwlBQFnLSaGd_6XWqJwn1DIzl5dOR5nri1Mk,11329 | |||
BTrees-4.5.1.dist-info/RECORD,, | |||
BTrees-4.5.1.dist-info/WHEEL,sha256=lx06sLWNl7U6OABNP9EWDqGNT2LbGyG1l5xyFnYYudo,109 | |||
BTrees-4.5.1.dist-info/entry_points.txt,sha256=OZFBvh0wrCZW2J7tzw2NztqnLWwpv5WcriQ9x7FELPY,6 | |||
BTrees-4.5.1.dist-info/top_level.txt,sha256=tyKTCytaCG_dQ7OVIUdgV-PZM9ltjveiOYk5u5FesbU,15 | |||
BTrees/BTreeItemsTemplate.c,sha256=u5_bDWVQljKp87eX_FIQbm1HoVK9QztsB4UkcVZhVKU,24825 | |||
BTrees/BTreeModuleTemplate.c,sha256=kO71qy4qU0nzS4PzTDzkvdn6cf-8DpzRFSt16WFp1O0,20611 | |||
BTrees/BTreeTemplate.c,sha256=qJJnOLO9GJZNNzbBLRO1AxloA62cGDtC-2QFFnupbUY,71551 | |||
BTrees/BucketTemplate.c,sha256=5sSFK-XD19B13bpIVN-rFS8TTcf6OCAYNecFbVdybqw,53068 | |||
BTrees/Development.txt,sha256=IJCMs8N362vMCr4H67mznKIr5Lb5BuYh5YRBzHDq8xk,16958 | |||
BTrees/IFBTree.py,sha256=xTd4UoaUmBhvf0wtco-_AZI2G_2bqymFWKRPDKIHX5g,3452 | |||
BTrees/IIBTree.py,sha256=43-TgRVrwHKzhwZ7myrbRbvUfRIL-EpXWmhKmYfXuZs,3426 | |||
BTrees/IOBTree.py,sha256=1PKMEHbm6Q0tOT-nEE66p6oiWUYtRXGs4ca6kVTtGck,2776 | |||
BTrees/Interfaces.py,sha256=-KxR8M0K5NVfWcvKbo5HkcjHZmPQQ0OXvLxXoaysX08,17831 | |||
BTrees/LFBTree.py,sha256=4qVsFCP6dm-ocZUBMTM97yNkI4MjB14xoBVJPnIpNLk,3453 | |||
BTrees/LLBTree.py,sha256=EyzLlsd-vdy12c6tVr57dshtR7yBRSq4ovXLbWnMoXw,3446 | |||
BTrees/LOBTree.py,sha256=yLR9OWzuytSDeb16vQjoJTCTpmZrAq1d5GMxXhRs9tk,2777 | |||
BTrees/Length.py,sha256=JX5_JLeRIwh79j_r87GnjFosExLwrj8H2eBjdvfb2IY,1937 | |||
BTrees/MergeTemplate.c,sha256=oLbW1QqeERrfnkg9YXfDUXU4I4OAJZ7jQ1OJcWQ7mqM,12070 | |||
BTrees/OIBTree.py,sha256=KvFHcH_80Cxy_Qclv8SlGrUVWapudsSGEKsEhOfcvcE,3338 | |||
BTrees/OLBTree.py,sha256=SXe4IJ0NOAfd3YJbV5L68VJkfdC3ZqzFOv58SRBXiKQ,3330 | |||
BTrees/OOBTree.py,sha256=n2dZCvzq83FJZ45lTywvYBvT2ozhJZZDraS6jGDZUaA,2524 | |||
BTrees/SetOpTemplate.c,sha256=T5hDNXFqSW6i1rKtCVErg6W-H8HOYkc5hCh1j6-8uW8,15571 | |||
BTrees/SetTemplate.c,sha256=GIW_9986HcjjwCXG2XnDz_BNHgsygf1umnv3FD92Gr8,10119 | |||
BTrees/TreeSetTemplate.c,sha256=z7sOs0RDVCgoufuDIiMb1oBKEKyJpgGtaUY063cF7ZE,8455 | |||
BTrees/_IFBTree.c,sha256=BIJrf9LcDM_DRKCtnXJqoAnwvDwhnh_3x_l7CQWzZ3g,1136 | |||
BTrees/_IFBTree.cpython-36m-darwin.so,sha256=-pDtbOXMyRLnyEzGPESPtNezHMVYjIkcI4_8LjbfQ90,186992 | |||
BTrees/_IIBTree.c,sha256=DXzS4WGNkVYneiEGtX9F0olC750rxFQfcAVzUlaBCT0,1130 | |||
BTrees/_IIBTree.cpython-36m-darwin.so,sha256=fY6YmOcdTiBl3DeGES-_KBdMLxDgCcGq7zFNfg8_dfc,182652 | |||
BTrees/_IOBTree.c,sha256=nL6yr4-u6rPo_y8A8TD1irmPVG0t4szMVh4NKumypgI,1141 | |||
BTrees/_IOBTree.cpython-36m-darwin.so,sha256=vNJgmpO6m0rYNxQfpSe5Qse1d5BNGEIovI72szY6Ogk,182500 | |||
BTrees/_LFBTree.c,sha256=vZ8O4_6sYU54cSwbcyy1FXUfbypQzuE59tYN6Po10GE,1208 | |||
BTrees/_LFBTree.cpython-36m-darwin.so,sha256=KuOsXfRQGAFZZjIKv9RCs_EEiiUXmrhZoVOSLZQQMms,187080 | |||
BTrees/_LLBTree.c,sha256=P31gcA6clVlSgAlBLzJ4hqdZMPqwV6q9pKfEexXRH9Q,1199 | |||
BTrees/_LLBTree.cpython-36m-darwin.so,sha256=WOhNkol3_nyCdjN7eUsLfz7hYw1ybbSi4Xz0AtvjmSc,186820 | |||
BTrees/_LOBTree.c,sha256=-bq0twqbE0oqlxyMS6chYBR5ObvLvFQAa0IyFK-l4ro,1178 | |||
BTrees/_LOBTree.cpython-36m-darwin.so,sha256=wLbc18-MMpOeFIrdQVm932uKHq-nb_2h3c25OfOieO4,182588 | |||
BTrees/_OIBTree.c,sha256=6SLJqZskZHEQB4OqCIlZZwGRwADi7MOvu-8W3hRJBo4,1141 | |||
BTrees/_OIBTree.cpython-36m-darwin.so,sha256=E4ToIjjUlcb1BXQ2XexI17hzxGpLkAhzFdTjZoOi4hA,178356 | |||
BTrees/_OLBTree.c,sha256=Hu0ylTuFWEPivGsL0ClS1YLdvx3fTIS9phtMpSt9zXo,1178 | |||
BTrees/_OLBTree.cpython-36m-darwin.so,sha256=qoKSvSbbNErXIMNVKYxyapIqDRNiU9M7VSXNxDE19m4,178428 | |||
BTrees/_OOBTree.c,sha256=e-Wd39TQyfwnX9X-o1mp9S_hIUpWGmUjmGvdH4f3__Q,1150 | |||
BTrees/_OOBTree.cpython-36m-darwin.so,sha256=7jOL1UtqyRz7YKvmBGma5asRslm4GlRKpf9sLxteCEk,174148 | |||
BTrees/__init__.py,sha256=SuUBN3IfVFkSbKLece09uEDFg76AwWWD2qx37AaYXxg,1871 | |||
BTrees/__pycache__/IFBTree.cpython-36.pyc,, | |||
BTrees/__pycache__/IIBTree.cpython-36.pyc,, | |||
BTrees/__pycache__/IOBTree.cpython-36.pyc,, | |||
BTrees/__pycache__/Interfaces.cpython-36.pyc,, | |||
BTrees/__pycache__/LFBTree.cpython-36.pyc,, | |||
BTrees/__pycache__/LLBTree.cpython-36.pyc,, | |||
BTrees/__pycache__/LOBTree.cpython-36.pyc,, | |||
BTrees/__pycache__/Length.cpython-36.pyc,, | |||
BTrees/__pycache__/OIBTree.cpython-36.pyc,, | |||
BTrees/__pycache__/OLBTree.cpython-36.pyc,, | |||
BTrees/__pycache__/OOBTree.cpython-36.pyc,, | |||
BTrees/__pycache__/__init__.cpython-36.pyc,, | |||
BTrees/__pycache__/_base.cpython-36.pyc,, | |||
BTrees/__pycache__/_compat.cpython-36.pyc,, | |||
BTrees/__pycache__/check.cpython-36.pyc,, | |||
BTrees/__pycache__/fsBTree.cpython-36.pyc,, | |||
BTrees/__pycache__/utils.cpython-36.pyc,, | |||
BTrees/_base.py,sha256=h6CFY1XeQHNJ9lkH6UWwMSTkTWOoOXNLUdUqvNbo1O4,49411 | |||
BTrees/_compat.h,sha256=7Juyq0L7R692-QxH7QDk3HD7AA3N0eAnwfP4zoqoGJg,1392 | |||
BTrees/_compat.py,sha256=30krwQo7chtAhl6V-LY0G4723rA43bZSHiERonPCHQc,2902 | |||
BTrees/_fsBTree.c,sha256=R2xeZeyvlUvJObF-66R2HJOFoPHLO87p3rVpe9byssY,4805 | |||
BTrees/_fsBTree.cpython-36m-darwin.so,sha256=3YzUd8gVpOREfkVJzM2NaY_cIYgZgmCULvLXSv01Rg8,174228 | |||
BTrees/check.py,sha256=73Wkf_42KfDQ_LjUlTC9hYC_v3e2gUXOJFkGbqwAAXM,15481 | |||
BTrees/floatvaluemacros.h,sha256=8--_OkpCuflybs5jaP3B54BWxw38u8vESph4IM545AI,899 | |||
BTrees/fsBTree.py,sha256=r1_iTqRLCBqKGPPsvj1D0mYoRzN85amNnrJJbjiHWj0,3185 | |||
BTrees/intkeymacros.h,sha256=M0lCFColrnPHK6qRbbgwqwAITplrCVfIDCcHp7bSE-I,1547 | |||
BTrees/intvaluemacros.h,sha256=oL_1993h7sS6O716DaKHytbC094xcBO4L1llPqX20Mo,1704 | |||
BTrees/objectkeymacros.h,sha256=BGhqG4nV122anWw1CNJE3dj2c5LhqKwDcvvx-Y_8QE0,1285 | |||
BTrees/objectvaluemacros.h,sha256=duuNoUjv-IwKbzEVxZjvvmDCETmzzASeQp1N2188BuM,460 | |||
BTrees/sorters.c,sha256=_RFLxqdW3ZhfJnMfo7tS0cQ1OuftmTmdwMLoJmH29p4,15218 | |||
BTrees/tests/__init__.py,sha256=as-9vgC96TSaF9bHM7EBWBANqSJOgJjwWtUT3lvHQZg,52 | |||
BTrees/tests/__pycache__/__init__.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/common.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/testBTrees.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/testBTreesUnicode.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/testConflict.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_IFBTree.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_IIBTree.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_IOBTree.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_LFBTree.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_LLBTree.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_LOBTree.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_Length.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_OIBTree.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_OLBTree.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_OOBTree.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test__base.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_btreesubclass.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_check.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_fsBTree.cpython-36.pyc,, | |||
BTrees/tests/__pycache__/test_utils.cpython-36.pyc,, | |||
BTrees/tests/common.py,sha256=ypzQE17QBOIpZNQmiRBtLMUA3nN68ZpMsjhjcnJmrhU,86610 | |||
BTrees/tests/testBTrees.py,sha256=9kHPN0-V9ZTYE05-KPocMChxWsrG3YtdGDxy_wfmZAg,17407 | |||
BTrees/tests/testBTreesUnicode.py,sha256=mPQraEu9VPV4mDlMcJRhpKJC0hvK2-IwJ-aZASP-DsM,2554 | |||
BTrees/tests/testConflict.py,sha256=O6bjf7ApdPaYzYCpmPcMC4yoEeS07dfvO280sKuNBZU,21323 | |||
BTrees/tests/test_IFBTree.py,sha256=lDac4rWXBtzaIYcbWWPcEshJKvLlwcJRzPzpltUC4rU,10641 | |||
BTrees/tests/test_IIBTree.py,sha256=ois9MwkJ33ZoryShqIRt6FE51rGPwBRT-fw8ceoZtmo,14197 | |||
BTrees/tests/test_IOBTree.py,sha256=LwwX727SNAWCf3tP18ZdY1rxNIYUseCKP2O1I860QE0,11531 | |||
BTrees/tests/test_LFBTree.py,sha256=RE2Fwo1jPPhSE4WI5R8Q0Q65wyRRydEJ5wMKcBcLK2w,8957 | |||
BTrees/tests/test_LLBTree.py,sha256=ECYj7Fn_em3RMwpd7PYb7Na5IR0FM5_lkrFbP1IkjP8,11760 | |||
BTrees/tests/test_LOBTree.py,sha256=RnAkGsl6prphDGpd-twb562MDlhYnn8Ka_EagQb1sEI,10124 | |||
BTrees/tests/test_Length.py,sha256=gyEXtVvSw7O_AMA9Yd2usemR_Qgie3sdr4lsCjkM0wY,3502 | |||
BTrees/tests/test_OIBTree.py,sha256=eeB1T_nlMb5DQMbD1ASmEwj-TEC7Pb8fSXwpVh-nyEU,10947 | |||
BTrees/tests/test_OLBTree.py,sha256=Bm6QqM9NdoqhrRqRykjurAqO9Sg8YzUQ4BrZWA8cc8A,10293 | |||
BTrees/tests/test_OOBTree.py,sha256=bfnhr0zQzvQVMW9FtL1QWT6J38Lt-b1nVYC4r7hZrS8,12573 | |||
BTrees/tests/test__base.py,sha256=7teTiKJfi-jc-SG1RL4KVhAYmy_H6oC-bCjY3J-2I2Q,114377 | |||
BTrees/tests/test_btreesubclass.py,sha256=bT0TwSCViQGoG8hRSzNLC6VAw3wlzaP1n80OZGjwREA,1646 | |||
BTrees/tests/test_check.py,sha256=UdeT_QGMF5ole6fya4853XcxrJSzeW3GSnz2J4RmngQ,12948 | |||
BTrees/tests/test_fsBTree.py,sha256=88hNt0wIFTizjPaUgHkz2msFO6-eFPOyHSEQKZKci-0,2136 | |||
BTrees/tests/test_utils.py,sha256=8B0lqFwYoHZalmEUyogHfDlN6IoERBaPjlr0TD-xu50,2703 | |||
BTrees/utils.py,sha256=Vnpv1X63hp3aBPUeS7EPuZo0NoGCBmuogfu8pAcLgSk,1442 | |||
terryfy/__init__.py,sha256=RBtPV4__9gzLYGnKiTMv1tGSXLxzFpIuVjARdyHHi0w,31 | |||
terryfy/__pycache__/__init__.cpython-36.pyc,, | |||
terryfy/__pycache__/bdist_wheel.cpython-36.pyc,, | |||
terryfy/__pycache__/cp_suff_real_libs.cpython-36.pyc,, | |||
terryfy/__pycache__/fuse_suff_real_libs.cpython-36.pyc,, | |||
terryfy/__pycache__/monkeyexec.cpython-36.pyc,, | |||
terryfy/__pycache__/repath_lib_names.cpython-36.pyc,, | |||
terryfy/__pycache__/test_travisparse.cpython-36.pyc,, | |||
terryfy/__pycache__/travisparse.cpython-36.pyc,, | |||
terryfy/__pycache__/wafutils.cpython-36.pyc,, | |||
terryfy/bdist_wheel.py,sha256=uMS-xIbyD81dC8nyKh6NuFNA3qVWY6TVXyzzsV5DBTc,811 | |||
terryfy/cp_suff_real_libs.py,sha256=11dwW6AePQ2sVB0C4wurs-tr4z1ytRgiHz_PsXGpFiw,720 | |||
terryfy/fuse_suff_real_libs.py,sha256=wfsxzciiEf1FgTga7MSJ0KXsulUyWvYaul5Z4lIfM0E,1099 | |||
terryfy/monkeyexec.py,sha256=QFZ5IB5JwsJK-Pc7IFnwrOD1pygE812zYn98kV-6V8A,1708 | |||
terryfy/repath_lib_names.py,sha256=kv2dHTRRYHjIwNbcfbFNna_6dHC6yyd-orO2BWMfNOk,1018 | |||
terryfy/test_travisparse.py,sha256=eGxYZSYdzwkxI6B9oYhZB-iGu4ifVla_F4i3Nkq7efo,2148 | |||
terryfy/travisparse.py,sha256=Io2vjJJz6iBHtSqZGMUm0YZ8RUO3d4y06bgYMC0qWT4,1763 | |||
terryfy/wafutils.py,sha256=FIxHuY991jWwdrGB_PVjSHLf3aeZpqKWoK_9rJbH8Vk,9118 |
@@ -0,0 +1,5 @@ | |||
Wheel-Version: 1.0 | |||
Generator: bdist_wheel (0.31.1) | |||
Root-Is-Purelib: false | |||
Tag: cp36-cp36m-macosx_10_6_intel | |||
@@ -0,0 +1 @@ | |||
@@ -0,0 +1,2 @@ | |||
BTrees | |||
terryfy |
@@ -0,0 +1,790 @@ | |||
/***************************************************************************** | |||
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 | |||
****************************************************************************/ | |||
#define BTREEITEMSTEMPLATE_C "$Id$\n" | |||
/* A BTreeItems struct is returned from calling .items(), .keys() or | |||
* .values() on a BTree-based data structure, and is also the result of | |||
* taking slices of those. It represents a contiguous slice of a BTree. | |||
* | |||
* The start of the slice is in firstbucket, at offset first. The end of | |||
* the slice is in lastbucket, at offset last. Both endpoints are inclusive. | |||
* It must possible to get from firstbucket to lastbucket via following | |||
* bucket 'next' pointers zero or more times. firstbucket, first, lastbucket, | |||
* and last are readonly after initialization. An empty slice is represented | |||
* by firstbucket == lastbucket == currentbucket == NULL. | |||
* | |||
* 'kind' determines whether this slice represents 'k'eys alone, 'v'alues | |||
* alone, or 'i'items (key+value pairs). 'kind' is also readonly after | |||
* initialization. | |||
* | |||
* The combination of currentbucket, currentoffset and pseudoindex acts as | |||
* a search finger. Offset currentoffset in bucket currentbucket is at index | |||
* pseudoindex, where pseudoindex==0 corresponds to offset first in bucket | |||
* firstbucket, and pseudoindex==-1 corresponds to offset last in bucket | |||
* lastbucket. The function BTreeItems_seek() can be used to set this combo | |||
* correctly for any in-bounds index, and uses this combo on input to avoid | |||
* needing to search from the start (or end) on each call. Calling | |||
* BTreeItems_seek() with consecutive larger positions is very efficent. | |||
* Calling it with consecutive smaller positions is more efficient than if | |||
* a search finger weren't being used at all, but is still quadratic time | |||
* in the number of buckets in the slice. | |||
*/ | |||
typedef struct | |||
{ | |||
PyObject_HEAD | |||
Bucket *firstbucket; /* First bucket */ | |||
Bucket *currentbucket; /* Current bucket (search finger) */ | |||
Bucket *lastbucket; /* Last bucket */ | |||
int currentoffset; /* Offset in currentbucket */ | |||
int pseudoindex; /* search finger index */ | |||
int first; /* Start offset in firstbucket */ | |||
int last; /* End offset in lastbucket */ | |||
char kind; /* 'k', 'v', 'i' */ | |||
} BTreeItems; | |||
#define ITEMS(O)((BTreeItems*)(O)) | |||
static PyObject * | |||
newBTreeItems(char kind, | |||
Bucket *lowbucket, int lowoffset, | |||
Bucket *highbucket, int highoffset); | |||
static void | |||
BTreeItems_dealloc(BTreeItems *self) | |||
{ | |||
Py_XDECREF(self->firstbucket); | |||
Py_XDECREF(self->lastbucket); | |||
Py_XDECREF(self->currentbucket); | |||
PyObject_DEL(self); | |||
} | |||
static Py_ssize_t | |||
BTreeItems_length_or_nonzero(BTreeItems *self, int nonzero) | |||
{ | |||
Py_ssize_t r; | |||
Bucket *b, *next; | |||
b = self->firstbucket; | |||
if (b == NULL) | |||
return 0; | |||
r = self->last + 1 - self->first; | |||
if (nonzero && r > 0) | |||
/* Short-circuit if all we care about is nonempty */ | |||
return 1; | |||
if (b == self->lastbucket) | |||
return r; | |||
Py_INCREF(b); | |||
PER_USE_OR_RETURN(b, -1); | |||
while ((next = b->next)) | |||
{ | |||
r += b->len; | |||
if (nonzero && r > 0) | |||
/* Short-circuit if all we care about is nonempty */ | |||
break; | |||
if (next == self->lastbucket) | |||
break; /* we already counted the last bucket */ | |||
Py_INCREF(next); | |||
PER_UNUSE(b); | |||
Py_DECREF(b); | |||
b = next; | |||
PER_USE_OR_RETURN(b, -1); | |||
} | |||
PER_UNUSE(b); | |||
Py_DECREF(b); | |||
return r >= 0 ? r : 0; | |||
} | |||
static Py_ssize_t | |||
BTreeItems_length(BTreeItems *self) | |||
{ | |||
return BTreeItems_length_or_nonzero(self, 0); | |||
} | |||
/* | |||
** BTreeItems_seek | |||
** | |||
** Find the ith position in the BTreeItems. | |||
** | |||
** Arguments: self The BTree | |||
** i the index to seek to, in 0 .. len(self)-1, or in | |||
** -len(self) .. -1, as for indexing a Python sequence. | |||
** | |||
** | |||
** Returns 0 if successful, -1 on failure to seek (like out-of-bounds). | |||
** Upon successful return, index i is at offset self->currentoffset in bucket | |||
** self->currentbucket. | |||
*/ | |||
static int | |||
BTreeItems_seek(BTreeItems *self, Py_ssize_t i) | |||
{ | |||
int delta, pseudoindex, currentoffset; | |||
Bucket *b, *currentbucket; | |||
int error; | |||
pseudoindex = self->pseudoindex; | |||
currentoffset = self->currentoffset; | |||
currentbucket = self->currentbucket; | |||
if (currentbucket == NULL) | |||
goto no_match; | |||
delta = i - pseudoindex; | |||
while (delta > 0) /* move right */ | |||
{ | |||
int max; | |||
/* Want to move right delta positions; the most we can move right in | |||
* this bucket is currentbucket->len - currentoffset - 1 positions. | |||
*/ | |||
PER_USE_OR_RETURN(currentbucket, -1); | |||
max = currentbucket->len - currentoffset - 1; | |||
b = currentbucket->next; | |||
PER_UNUSE(currentbucket); | |||
if (delta <= max) | |||
{ | |||
currentoffset += delta; | |||
pseudoindex += delta; | |||
if (currentbucket == self->lastbucket | |||
&& currentoffset > self->last) | |||
goto no_match; | |||
break; | |||
} | |||
/* Move to start of next bucket. */ | |||
if (currentbucket == self->lastbucket || b == NULL) | |||
goto no_match; | |||
currentbucket = b; | |||
pseudoindex += max + 1; | |||
delta -= max + 1; | |||
currentoffset = 0; | |||
} | |||
while (delta < 0) /* move left */ | |||
{ | |||
int status; | |||
/* Want to move left -delta positions; the most we can move left in | |||
* this bucket is currentoffset positions. | |||
*/ | |||
if ((-delta) <= currentoffset) | |||
{ | |||
currentoffset += delta; | |||
pseudoindex += delta; | |||
if (currentbucket == self->firstbucket | |||
&& currentoffset < self->first) | |||
goto no_match; | |||
break; | |||
} | |||
/* Move to end of previous bucket. */ | |||
if (currentbucket == self->firstbucket) | |||
goto no_match; | |||
status = PreviousBucket(¤tbucket, self->firstbucket); | |||
if (status == 0) | |||
goto no_match; | |||
else if (status < 0) | |||
return -1; | |||
pseudoindex -= currentoffset + 1; | |||
delta += currentoffset + 1; | |||
PER_USE_OR_RETURN(currentbucket, -1); | |||
currentoffset = currentbucket->len - 1; | |||
PER_UNUSE(currentbucket); | |||
} | |||
assert(pseudoindex == i); | |||
/* Alas, the user may have mutated the bucket since the last time we | |||
* were called, and if they deleted stuff, we may be pointing into | |||
* trash memory now. | |||
*/ | |||
PER_USE_OR_RETURN(currentbucket, -1); | |||
error = currentoffset < 0 || currentoffset >= currentbucket->len; | |||
PER_UNUSE(currentbucket); | |||
if (error) | |||
{ | |||
PyErr_SetString(PyExc_RuntimeError, | |||
"the bucket being iterated changed size"); | |||
return -1; | |||
} | |||
Py_INCREF(currentbucket); | |||
Py_DECREF(self->currentbucket); | |||
self->currentbucket = currentbucket; | |||
self->currentoffset = currentoffset; | |||
self->pseudoindex = pseudoindex; | |||
return 0; | |||
no_match: | |||
IndexError(i); | |||
return -1; | |||
} | |||
/* Return the right kind ('k','v','i') of entry from bucket b at offset i. | |||
* b must be activated. Returns NULL on error. | |||
*/ | |||
static PyObject * | |||
getBucketEntry(Bucket *b, int i, char kind) | |||
{ | |||
PyObject *result = NULL; | |||
assert(b); | |||
assert(0 <= i && i < b->len); | |||
switch (kind) | |||
{ | |||
case 'k': | |||
COPY_KEY_TO_OBJECT(result, b->keys[i]); | |||
break; | |||
case 'v': | |||
COPY_VALUE_TO_OBJECT(result, b->values[i]); | |||
break; | |||
case 'i': | |||
{ | |||
PyObject *key; | |||
PyObject *value;; | |||
COPY_KEY_TO_OBJECT(key, b->keys[i]); | |||
if (!key) | |||
break; | |||
COPY_VALUE_TO_OBJECT(value, b->values[i]); | |||
if (!value) | |||
{ | |||
Py_DECREF(key); | |||
break; | |||
} | |||
result = PyTuple_New(2); | |||
if (result) | |||
{ | |||
PyTuple_SET_ITEM(result, 0, key); | |||
PyTuple_SET_ITEM(result, 1, value); | |||
} | |||
else | |||
{ | |||
Py_DECREF(key); | |||
Py_DECREF(value); | |||
} | |||
break; | |||
} | |||
default: | |||
PyErr_SetString(PyExc_AssertionError, | |||
"getBucketEntry: unknown kind"); | |||
break; | |||
} | |||
return result; | |||
} | |||
/* | |||
** BTreeItems_item | |||
** | |||
** Arguments: self a BTreeItems structure | |||
** i Which item to inspect | |||
** | |||
** Returns: the BTreeItems_item_BTree of self->kind, i | |||
** (ie pulls the ith item out) | |||
*/ | |||
static PyObject * | |||
BTreeItems_item(BTreeItems *self, Py_ssize_t i) | |||
{ | |||
PyObject *result; | |||
if (BTreeItems_seek(self, i) < 0) | |||
return NULL; | |||
PER_USE_OR_RETURN(self->currentbucket, NULL); | |||
result = getBucketEntry(self->currentbucket, self->currentoffset, | |||
self->kind); | |||
PER_UNUSE(self->currentbucket); | |||
return result; | |||
} | |||
/* | |||
** BTreeItems_slice | |||
** | |||
** Creates a new BTreeItems structure representing the slice | |||
** between the low and high range | |||
** | |||
** Arguments: self The old BTreeItems structure | |||
** ilow The start index | |||
** ihigh The end index | |||
** | |||
** Returns: BTreeItems item | |||
*/ | |||
static PyObject * | |||
BTreeItems_slice(BTreeItems *self, Py_ssize_t ilow, Py_ssize_t ihigh) | |||
{ | |||
Bucket *lowbucket; | |||
Bucket *highbucket; | |||
int lowoffset; | |||
int highoffset; | |||
Py_ssize_t length = -1; /* len(self), but computed only if needed */ | |||
/* Complications: | |||
* A Python slice never raises IndexError, but BTreeItems_seek does. | |||
* Python did only part of index normalization before calling this: | |||
* ilow may be < 0 now, and ihigh may be arbitrarily large. It's | |||
* our responsibility to clip them. | |||
* A Python slice is exclusive of the high index, but a BTreeItems | |||
* struct is inclusive on both ends. | |||
*/ | |||
/* First adjust ilow and ihigh to be legit endpoints in the Python | |||
* sense (ilow inclusive, ihigh exclusive). This block duplicates the | |||
* logic from Python's list_slice function (slicing for builtin lists). | |||
*/ | |||
if (ilow < 0) | |||
ilow = 0; | |||
else | |||
{ | |||
if (length < 0) | |||
length = BTreeItems_length(self); | |||
if (ilow > length) | |||
ilow = length; | |||
} | |||
if (ihigh < ilow) | |||
ihigh = ilow; | |||
else | |||
{ | |||
if (length < 0) | |||
length = BTreeItems_length(self); | |||
if (ihigh > length) | |||
ihigh = length; | |||
} | |||
assert(0 <= ilow && ilow <= ihigh); | |||
assert(length < 0 || ihigh <= length); | |||
/* Now adjust for that our struct is inclusive on both ends. This is | |||
* easy *except* when the slice is empty: there's no good way to spell | |||
* that in an inclusive-on-both-ends scheme. For example, if the | |||
* slice is btree.items([:0]), ilow == ihigh == 0 at this point, and if | |||
* we were to subtract 1 from ihigh that would get interpreted by | |||
* BTreeItems_seek as meaning the *entire* set of items. Setting ilow==1 | |||
* and ihigh==0 doesn't work either, as BTreeItems_seek raises IndexError | |||
* if we attempt to seek to ilow==1 when the underlying sequence is empty. | |||
* It seems simplest to deal with empty slices as a special case here. | |||
*/ | |||
if (ilow == ihigh) /* empty slice */ | |||
{ | |||
lowbucket = highbucket = NULL; | |||
lowoffset = 1; | |||
highoffset = 0; | |||
} | |||
else | |||
{ | |||
assert(ilow < ihigh); | |||
--ihigh; /* exclusive -> inclusive */ | |||
if (BTreeItems_seek(self, ilow) < 0) | |||
return NULL; | |||
lowbucket = self->currentbucket; | |||
lowoffset = self->currentoffset; | |||
if (BTreeItems_seek(self, ihigh) < 0) | |||
return NULL; | |||
highbucket = self->currentbucket; | |||
highoffset = self->currentoffset; | |||
} | |||
return newBTreeItems(self->kind, | |||
lowbucket, lowoffset, highbucket, highoffset); | |||
} | |||
static PyObject * | |||
BTreeItems_subscript(BTreeItems *self, PyObject* subscript) | |||
{ | |||
Py_ssize_t len = BTreeItems_length_or_nonzero(self, 0); | |||
if (PyIndex_Check(subscript)) | |||
{ | |||
Py_ssize_t i = PyNumber_AsSsize_t(subscript, PyExc_IndexError); | |||
if (i == -1 && PyErr_Occurred()) | |||
return NULL; | |||
if (i < 0) | |||
i += len; | |||
return BTreeItems_item(self, i); | |||
} | |||
if (PySlice_Check(subscript)) | |||
{ | |||
Py_ssize_t start, stop, step, slicelength; | |||
#ifdef PY3K | |||
#define SLICEOBJ(x) (x) | |||
#else | |||
#define SLICEOBJ(x) (PySliceObject*)(x) | |||
#endif | |||
if (PySlice_GetIndicesEx(SLICEOBJ(subscript), len, | |||
&start, &stop, &step, &slicelength) < 0) | |||
{ | |||
return NULL; | |||
} | |||
if (step != 1) | |||
{ | |||
PyErr_SetString(PyExc_RuntimeError, | |||
"slices must have step size of 1"); | |||
return NULL; | |||
} | |||
return BTreeItems_slice(self, start, stop); | |||
} | |||
PyErr_SetString(PyExc_RuntimeError, | |||
"Unknown index type: must be int or slice"); | |||
return NULL; | |||
} | |||
/* Py3K doesn't honor sequence slicing, so implement via mapping */ | |||
static PyMappingMethods BTreeItems_as_mapping = { | |||
(lenfunc)BTreeItems_length, /* mp_length */ | |||
(binaryfunc)BTreeItems_subscript, /* mp_subscript */ | |||
}; | |||
static PySequenceMethods BTreeItems_as_sequence = | |||
{ | |||
(lenfunc) BTreeItems_length, /* sq_length */ | |||
(binaryfunc)0, /* sq_concat */ | |||
(ssizeargfunc)0, /* sq_repeat */ | |||
(ssizeargfunc) BTreeItems_item, /* sq_item */ | |||
#ifndef PY3K | |||
/* Py3K doesn't honor this slot */ | |||
(ssizessizeargfunc) BTreeItems_slice, /* sq_slice */ | |||
#endif | |||
}; | |||
/* Number Method items (just for nb_nonzero!) */ | |||
static int | |||
BTreeItems_nonzero(BTreeItems *self) | |||
{ | |||
return BTreeItems_length_or_nonzero(self, 1); | |||
} | |||
static PyNumberMethods BTreeItems_as_number_for_nonzero = { | |||
0, /* nb_add */ | |||
0, /* nb_subtract */ | |||
0, /* nb_multiply */ | |||
#ifndef PY3K | |||
0, /* nb_divide */ | |||
#endif | |||
0, /* nb_remainder */ | |||
0, /* nb_divmod */ | |||
0, /* nb_power */ | |||
0, /* nb_negative */ | |||
0, /* nb_positive */ | |||
0, /* nb_absolute */ | |||
(inquiry)BTreeItems_nonzero /* nb_nonzero */ | |||
}; | |||
static PyTypeObject BTreeItemsType = { | |||
PyVarObject_HEAD_INIT(NULL, 0) | |||
MOD_NAME_PREFIX "BTreeItems", /* tp_name */ | |||
sizeof(BTreeItems), /* tp_basicsize */ | |||
0, /* tp_itemsize */ | |||
/* methods */ | |||
(destructor) BTreeItems_dealloc, /* tp_dealloc */ | |||
0, /* tp_print */ | |||
0, /* obsolete tp_getattr */ | |||
0, /* obsolete tp_setattr */ | |||
0, /* tp_compare */ | |||
0, /* tp_repr */ | |||
&BTreeItems_as_number_for_nonzero, /* tp_as_number */ | |||
&BTreeItems_as_sequence, /* tp_as_sequence */ | |||
&BTreeItems_as_mapping, /* tp_as_mapping */ | |||
(hashfunc)0, /* tp_hash */ | |||
(ternaryfunc)0, /* tp_call */ | |||
(reprfunc)0, /* tp_str */ | |||
0, /* tp_getattro */ | |||
0, /* tp_setattro */ | |||
/* Space for future expansion */ | |||
0L,0L, | |||
"Sequence type used to iterate over BTree items." /* Documentation string */ | |||
}; | |||
/* Returns a new BTreeItems object representing the contiguous slice from | |||
* offset lowoffset in bucket lowbucket through offset highoffset in bucket | |||
* highbucket, inclusive. Pass lowbucket == NULL for an empty slice. | |||
* The currentbucket is set to lowbucket, currentoffset ot lowoffset, and | |||
* pseudoindex to 0. kind is 'k', 'v' or 'i' (see BTreeItems struct docs). | |||
*/ | |||
static PyObject * | |||
newBTreeItems(char kind, | |||
Bucket *lowbucket, int lowoffset, | |||
Bucket *highbucket, int highoffset) | |||
{ | |||
BTreeItems *self; | |||
UNLESS (self = PyObject_NEW(BTreeItems, &BTreeItemsType)) | |||
return NULL; | |||
self->kind=kind; | |||
self->first=lowoffset; | |||
self->last=highoffset; | |||
if (! lowbucket || ! highbucket | |||
|| (lowbucket == highbucket && lowoffset > highoffset)) | |||
{ | |||
self->firstbucket = 0; | |||
self->lastbucket = 0; | |||
self->currentbucket = 0; | |||
} | |||
else | |||
{ | |||
Py_INCREF(lowbucket); | |||
self->firstbucket = lowbucket; | |||
Py_INCREF(highbucket); | |||
self->lastbucket = highbucket; | |||
Py_INCREF(lowbucket); | |||
self->currentbucket = lowbucket; | |||
} | |||
self->currentoffset = lowoffset; | |||
self->pseudoindex = 0; | |||
return OBJECT(self); | |||
} | |||
static int | |||
nextBTreeItems(SetIteration *i) | |||
{ | |||
if (i->position >= 0) | |||
{ | |||
if (i->position) | |||
{ | |||
DECREF_KEY(i->key); | |||
DECREF_VALUE(i->value); | |||
} | |||
if (BTreeItems_seek(ITEMS(i->set), i->position) >= 0) | |||
{ | |||
Bucket *currentbucket; | |||
currentbucket = BUCKET(ITEMS(i->set)->currentbucket); | |||
UNLESS(PER_USE(currentbucket)) | |||
{ | |||
/* Mark iteration terminated, so that finiSetIteration doesn't | |||
* try to redundantly decref the key and value | |||
*/ | |||
i->position = -1; | |||
return -1; | |||
} | |||
COPY_KEY(i->key, currentbucket->keys[ITEMS(i->set)->currentoffset]); | |||
INCREF_KEY(i->key); | |||
COPY_VALUE(i->value, | |||
currentbucket->values[ITEMS(i->set)->currentoffset]); | |||
INCREF_VALUE(i->value); | |||
i->position ++; | |||
PER_UNUSE(currentbucket); | |||
} | |||
else | |||
{ | |||
i->position = -1; | |||
PyErr_Clear(); | |||
} | |||
} | |||
return 0; | |||
} | |||
static int | |||
nextTreeSetItems(SetIteration *i) | |||
{ | |||
if (i->position >= 0) | |||
{ | |||
if (i->position) | |||
{ | |||
DECREF_KEY(i->key); | |||
} | |||
if (BTreeItems_seek(ITEMS(i->set), i->position) >= 0) | |||
{ | |||
Bucket *currentbucket; | |||
currentbucket = BUCKET(ITEMS(i->set)->currentbucket); | |||
UNLESS(PER_USE(currentbucket)) | |||
{ | |||
/* Mark iteration terminated, so that finiSetIteration doesn't | |||
* try to redundantly decref the key and value | |||
*/ | |||
i->position = -1; | |||
return -1; | |||
} | |||
COPY_KEY(i->key, currentbucket->keys[ITEMS(i->set)->currentoffset]); | |||
INCREF_KEY(i->key); | |||
i->position ++; | |||
PER_UNUSE(currentbucket); | |||
} | |||
else | |||
{ | |||
i->position = -1; | |||
PyErr_Clear(); | |||
} | |||
} | |||
return 0; | |||
} | |||
/* Support for the iteration protocol new in Python 2.2. */ | |||
static PyTypeObject BTreeIter_Type; | |||
/* The type of iterator objects, returned by e.g. iter(IIBTree()). */ | |||
typedef struct | |||
{ | |||
PyObject_HEAD | |||
/* We use a BTreeItems object because it's convenient and flexible. | |||
* We abuse it two ways: | |||
* 1. We set currentbucket to NULL when the iteration is finished. | |||
* 2. We don't bother keeping pseudoindex in synch. | |||
*/ | |||
BTreeItems *pitems; | |||
} BTreeIter; | |||
/* Return a new iterator object, to traverse the keys and/or values | |||
* represented by pitems. pitems must not be NULL. Returns NULL if error. | |||
*/ | |||
static BTreeIter * | |||
BTreeIter_new(BTreeItems *pitems) | |||
{ | |||
BTreeIter *result; | |||
assert(pitems != NULL); | |||
result = PyObject_New(BTreeIter, &BTreeIter_Type); | |||
if (result) | |||
{ | |||
Py_INCREF(pitems); | |||
result->pitems = pitems; | |||
} | |||
return result; | |||
} | |||
/* The iterator's tp_dealloc slot. */ | |||
static void | |||
BTreeIter_dealloc(BTreeIter *bi) | |||
{ | |||
Py_DECREF(bi->pitems); | |||
PyObject_Del(bi); | |||
} | |||
/* The implementation of the iterator's tp_iternext slot. Returns "the next" | |||
* item; returns NULL if error; returns NULL without setting an error if the | |||
* iteration is exhausted (that's the way to terminate the iteration protocol). | |||
*/ | |||
static PyObject * | |||
BTreeIter_next(BTreeIter *bi, PyObject *args) | |||
{ | |||
PyObject *result = NULL; /* until proven innocent */ | |||
BTreeItems *items = bi->pitems; | |||
int i = items->currentoffset; | |||
Bucket *bucket = items->currentbucket; | |||
if (bucket == NULL) /* iteration termination is sticky */ | |||
return NULL; | |||
PER_USE_OR_RETURN(bucket, NULL); | |||
if (i >= bucket->len) | |||
{ | |||
/* We never leave this routine normally with i >= len: somebody | |||
* else mutated the current bucket. | |||
*/ | |||
PyErr_SetString(PyExc_RuntimeError, | |||
"the bucket being iterated changed size"); | |||
/* Arrange for that this error is sticky too. */ | |||
items->currentoffset = INT_MAX; | |||
goto Done; | |||
} | |||
/* Build the result object, from bucket at offset i. */ | |||
result = getBucketEntry(bucket, i, items->kind); | |||
/* Advance position for next call. */ | |||
if (bucket == items->lastbucket && i >= items->last) | |||
{ | |||
/* Next call should terminate the iteration. */ | |||
Py_DECREF(items->currentbucket); | |||
items->currentbucket = NULL; | |||
} | |||
else | |||
{ | |||
++i; | |||
if (i >= bucket->len) | |||
{ | |||
Py_XINCREF(bucket->next); | |||
items->currentbucket = bucket->next; | |||
Py_DECREF(bucket); | |||
i = 0; | |||
} | |||
items->currentoffset = i; | |||
} | |||
Done: | |||
PER_UNUSE(bucket); | |||
return result; | |||
} | |||
static PyObject * | |||
BTreeIter_getiter(PyObject *it) | |||
{ | |||
Py_INCREF(it); | |||
return it; | |||
} | |||
static PyTypeObject BTreeIter_Type = { | |||
PyVarObject_HEAD_INIT(NULL, 0) | |||
MODULE_NAME MOD_NAME_PREFIX "TreeIterator", /* tp_name */ | |||
sizeof(BTreeIter), /* tp_basicsize */ | |||
0, /* tp_itemsize */ | |||
/* methods */ | |||
(destructor)BTreeIter_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 */ | |||
0, /*PyObject_GenericGetAttr,*/ /* tp_getattro */ | |||
0, /* tp_setattro */ | |||
0, /* tp_as_buffer */ | |||
Py_TPFLAGS_DEFAULT, /* tp_flags */ | |||
0, /* tp_doc */ | |||
0, /* tp_traverse */ | |||
0, /* tp_clear */ | |||
0, /* tp_richcompare */ | |||
0, /* tp_weaklistoffset */ | |||
(getiterfunc)BTreeIter_getiter, /* tp_iter */ | |||
(iternextfunc)BTreeIter_next, /* tp_iternext */ | |||
0, /* tp_methods */ | |||
0, /* tp_members */ | |||
0, /* tp_getset */ | |||
0, /* tp_base */ | |||
0, /* tp_dict */ | |||
0, /* tp_descr_get */ | |||
0, /* tp_descr_set */ | |||
}; |
@@ -0,0 +1,671 @@ | |||
/***************************************************************************** | |||
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 | |||
****************************************************************************/ | |||
#include "Python.h" | |||
/* include structmember.h for offsetof */ | |||
#include "structmember.h" | |||
#include "bytesobject.h" | |||
#ifdef PERSISTENT | |||
#include "persistent/cPersistence.h" | |||
#else | |||
#define PER_USE_OR_RETURN(self, NULL) | |||
#define PER_ALLOW_DEACTIVATION(self) | |||
#define PER_PREVENT_DEACTIVATION(self) | |||
#define PER_DEL(self) | |||
#define PER_USE(O) 1 | |||
#define PER_ACCESSED(O) 1 | |||
#endif | |||
#include "_compat.h" | |||
/* So sue me. This pair gets used all over the place, so much so that it | |||
* interferes with understanding non-persistence parts of algorithms. | |||
* PER_UNUSE can be used after a successul PER_USE or PER_USE_OR_RETURN. | |||
* It allows the object to become ghostified, and tells the persistence | |||
* machinery that the object's fields were used recently. | |||
*/ | |||
#define PER_UNUSE(OBJ) do { \ | |||
PER_ALLOW_DEACTIVATION(OBJ); \ | |||
PER_ACCESSED(OBJ); \ | |||
} while (0) | |||
/* The tp_name slots of the various BTree types contain the fully | |||
* qualified names of the types, e.g. zodb.btrees.OOBTree.OOBTree. | |||
* The full name is usd to support pickling and because it is not | |||
* possible to modify the __module__ slot of a type dynamically. (This | |||
* may be a bug in Python 2.2). | |||
* | |||
* The MODULE_NAME here used to be "BTrees._". We actually want the module | |||
* name to point to the Python module rather than the C, so the underline | |||
* is now removed. | |||
*/ | |||
#define MODULE_NAME "BTrees." MOD_NAME_PREFIX "BTree." | |||
static PyObject *sort_str, *reverse_str, *__setstate___str; | |||
static PyObject *_bucket_type_str, *max_internal_size_str, *max_leaf_size_str; | |||
static PyObject *ConflictError = NULL; | |||
static void PyVar_Assign(PyObject **v, PyObject *e) { Py_XDECREF(*v); *v=e;} | |||
#define ASSIGN(V,E) PyVar_Assign(&(V),(E)) | |||
#define UNLESS(E) if (!(E)) | |||
#define OBJECT(O) ((PyObject*)(O)) | |||
#define MIN_BUCKET_ALLOC 16 | |||
#define SameType_Check(O1, O2) (Py_TYPE((O1))==Py_TYPE((O2))) | |||
#define ASSERT(C, S, R) if (! (C)) { \ | |||
PyErr_SetString(PyExc_AssertionError, (S)); return (R); } | |||
#ifdef NEED_LONG_LONG_SUPPORT | |||
/* Helper code used to support long long instead of int. */ | |||
#ifndef PY_LONG_LONG | |||
#error "PY_LONG_LONG required but not defined" | |||
#endif | |||
#ifdef NEED_LONG_LONG_KEYS | |||
static int | |||
longlong_check(PyObject *ob) | |||
{ | |||
if (INT_CHECK(ob)) | |||
return 1; | |||
if (PyLong_Check(ob)) { | |||
int overflow; | |||
(void)PyLong_AsLongLongAndOverflow(ob, &overflow); | |||
if (overflow) | |||
goto overflow; | |||
return 1; | |||
} | |||
return 0; | |||
overflow: | |||
PyErr_SetString(PyExc_ValueError, | |||
"longlong_check: long integer out of range"); | |||
return 0; | |||
} | |||
#endif | |||
static PyObject * | |||
longlong_as_object(PY_LONG_LONG val) | |||
{ | |||
if ((val > LONG_MAX) || (val < LONG_MIN)) | |||
return PyLong_FromLongLong(val); | |||
return INT_FROM_LONG((long)val); | |||
} | |||
static int | |||
longlong_convert(PyObject *ob, PY_LONG_LONG *value) | |||
{ | |||
#ifndef PY3K | |||
if (PyInt_Check(ob)) | |||
{ | |||
(*value) = (PY_LONG_LONG)PyInt_AS_LONG(ob); | |||
return 1; | |||
} | |||
#endif | |||
if (!PyLong_Check(ob)) | |||
{ | |||
PyErr_SetString(PyExc_TypeError, "expected integer key"); | |||
return 0; | |||
} | |||
else | |||
{ | |||
PY_LONG_LONG val; | |||
int overflow; | |||
val = PyLong_AsLongLongAndOverflow(ob, &overflow); | |||
if (overflow) | |||
goto overflow; | |||
(*value) = val; | |||
return 1; | |||
} | |||
overflow: | |||
PyErr_SetString(PyExc_ValueError, "long integer out of range"); | |||
return 0; | |||
} | |||
#endif /* NEED_LONG_LONG_SUPPORT */ | |||
/* Various kinds of BTree and Bucket structs are instances of | |||
* "sized containers", and have a common initial layout: | |||
* The stuff needed for all Python objects, or all Persistent objects. | |||
* int size: The maximum number of things that could be contained | |||
* without growing the container. | |||
* int len: The number of things currently contained. | |||
* | |||
* Invariant: 0 <= len <= size. | |||
* | |||
* A sized container typically goes on to declare one or more pointers | |||
* to contiguous arrays with 'size' elements each, the initial 'len' of | |||
* which are currently in use. | |||
*/ | |||
#ifdef PERSISTENT | |||
#define sizedcontainer_HEAD \ | |||
cPersistent_HEAD \ | |||
int size; \ | |||
int len; | |||
#else | |||
#define sizedcontainer_HEAD \ | |||
PyObject_HEAD \ | |||
int size; \ | |||
int len; | |||
#endif | |||
/* Nothing is actually of type Sized, but (pointers to) BTree nodes and | |||
* Buckets can be cast to Sized* in contexts that only need to examine | |||
* the members common to all sized containers. | |||
*/ | |||
typedef struct Sized_s { | |||
sizedcontainer_HEAD | |||
} Sized; | |||
#define SIZED(O) ((Sized*)(O)) | |||
/* A Bucket wraps contiguous vectors of keys and values. Keys are unique, | |||
* and stored in sorted order. The 'values' pointer may be NULL if the | |||
* Bucket is used to implement a set. Buckets serving as leafs of BTrees | |||
* are chained together via 'next', so that the entire BTree contents | |||
* can be traversed in sorted order quickly and easily. | |||
*/ | |||
typedef struct Bucket_s { | |||
sizedcontainer_HEAD | |||
struct Bucket_s *next; /* the bucket with the next-larger keys */ | |||
KEY_TYPE *keys; /* 'len' keys, in increasing order */ | |||
VALUE_TYPE *values; /* 'len' corresponding values; NULL if a set */ | |||
} Bucket; | |||
#define BUCKET(O) ((Bucket*)(O)) | |||
/* A BTree is complicated. See Maintainer.txt. | |||
*/ | |||
typedef struct BTreeItem_s { | |||
KEY_TYPE key; | |||
Sized *child; /* points to another BTree, or to a Bucket of some sort */ | |||
} BTreeItem; | |||
typedef struct BTree_s { | |||
sizedcontainer_HEAD | |||
/* firstbucket points to the bucket containing the smallest key in | |||
* the BTree. This is found by traversing leftmost child pointers | |||
* (data[0].child) until reaching a Bucket. | |||
*/ | |||
Bucket *firstbucket; | |||
/* The BTree points to 'len' children, via the "child" fields of the data | |||
* array. There are len-1 keys in the 'key' fields, stored in increasing | |||
* order. data[0].key is unused. For i in 0 .. len-1, all keys reachable | |||
* from data[i].child are >= data[i].key and < data[i+1].key, at the | |||
* endpoints pretending that data[0].key is minus infinity and | |||
* data[len].key is positive infinity. | |||
*/ | |||
BTreeItem *data; | |||
long max_internal_size; | |||
long max_leaf_size; | |||
} BTree; | |||
static PyTypeObject BTreeType; | |||
static PyTypeObject BucketType; | |||
#define BTREE(O) ((BTree*)(O)) | |||
/* Use BTREE_SEARCH to find which child pointer to follow. | |||
* RESULT An int lvalue to hold the index i such that SELF->data[i].child | |||
* is the correct node to search next. | |||
* SELF A pointer to a BTree node. | |||
* KEY The key you're looking for, of type KEY_TYPE. | |||
* ONERROR What to do if key comparison raises an exception; for example, | |||
* perhaps 'return NULL'. | |||
* | |||
* See Maintainer.txt for discussion: this is optimized in subtle ways. | |||
* It's recommended that you call this at the start of a routine, waiting | |||
* to check for self->len == 0 after. | |||
*/ | |||
#define BTREE_SEARCH(RESULT, SELF, KEY, ONERROR) { \ | |||
int _lo = 0; \ | |||
int _hi = (SELF)->len; \ | |||
int _i, _cmp; \ | |||
for (_i = _hi >> 1; _i > _lo; _i = (_lo + _hi) >> 1) { \ | |||
TEST_KEY_SET_OR(_cmp, (SELF)->data[_i].key, (KEY)) \ | |||
ONERROR; \ | |||
if (_cmp < 0) _lo = _i; \ | |||
else if (_cmp > 0) _hi = _i; \ | |||
else /* equal */ break; \ | |||
} \ | |||
(RESULT) = _i; \ | |||
} | |||
/* SetIteration structs are used in the internal set iteration protocol. | |||
* When you want to iterate over a set or bucket or BTree (even an | |||
* individual key!), | |||
* 1. Declare a new iterator: | |||
* SetIteration si = {0,0,0}; | |||
* Using "{0,0,0}" or "{0,0}" appear most common. Only one {0} is | |||
* necssary. At least one must be given so that finiSetIteration() works | |||
* correctly even if you don't get around to calling initSetIteration(). | |||
* 2. Initialize it via | |||
* initSetIteration(&si, PyObject *s, useValues) | |||
* It's an error if that returns an int < 0. In case of error on the | |||
* init call, calling finiSetIteration(&si) is optional. But if the | |||
* init call succeeds, you must eventually call finiSetIteration(), | |||
* and whether or not subsequent calls to si.next() fail. | |||
* 3. Get the first element: | |||
* if (si.next(&si) < 0) { there was an error } | |||
* If the set isn't empty, this sets si.position to an int >= 0, | |||
* si.key to the element's key (of type KEY_TYPE), and maybe si.value to | |||
* the element's value (of type VALUE_TYPE). si.value is defined | |||
* iff si.usesValue is true. | |||
* 4. Process all the elements: | |||
* while (si.position >= 0) { | |||
* do something with si.key and/or si.value; | |||
* if (si.next(&si) < 0) { there was an error; } | |||
* } | |||
* 5. Finalize the SetIterator: | |||
* finiSetIteration(&si); | |||
* This is mandatory! si may contain references to iterator objects, | |||
* keys and values, and they must be cleaned up else they'll leak. If | |||
* this were C++ we'd hide that in the destructor, but in C you have to | |||
* do it by hand. | |||
*/ | |||
typedef struct SetIteration_s | |||
{ | |||
PyObject *set; /* the set, bucket, BTree, ..., being iterated */ | |||
int position; /* initialized to 0; set to -1 by next() when done */ | |||
int usesValue; /* true iff 'set' has values & we iterate them */ | |||
KEY_TYPE key; /* next() sets to next key */ | |||
VALUE_TYPE value; /* next() may set to next value */ | |||
int (*next)(struct SetIteration_s*); /* function to get next key+value */ | |||
} SetIteration; | |||
/* Finish the set iteration protocol. This MUST be called by everyone | |||
* who starts a set iteration, unless the initial call to initSetIteration | |||
* failed; in that case, and only that case, calling finiSetIteration is | |||
* optional. | |||
*/ | |||
static void | |||
finiSetIteration(SetIteration *i) | |||
{ | |||
assert(i != NULL); | |||
if (i->set == NULL) | |||
return; | |||
Py_DECREF(i->set); | |||
i->set = NULL; /* so it doesn't hurt to call this again */ | |||
if (i->position > 0) { | |||
/* next() was called at least once, but didn't finish iterating | |||
* (else position would be negative). So the cached key and | |||
* value need to be cleaned up. | |||
*/ | |||
DECREF_KEY(i->key); | |||
if (i->usesValue) { | |||
DECREF_VALUE(i->value); | |||
} | |||
} | |||
i->position = -1; /* stop any stray next calls from doing harm */ | |||
} | |||
static PyObject * | |||
IndexError(int i) | |||
{ | |||
PyObject *v; | |||
v = INT_FROM_LONG(i); | |||
if (!v) { | |||
v = Py_None; | |||
Py_INCREF(v); | |||
} | |||
PyErr_SetObject(PyExc_IndexError, v); | |||
Py_DECREF(v); | |||
return NULL; | |||
} | |||
/* Search for the bucket immediately preceding *current, in the bucket chain | |||
* starting at first. current, *current and first must not be NULL. | |||
* | |||
* Return: | |||
* 1 *current holds the correct bucket; this is a borrowed reference | |||
* 0 no such bucket exists; *current unaltered | |||
* -1 error; *current unaltered | |||
*/ | |||
static int | |||
PreviousBucket(Bucket **current, Bucket *first) | |||
{ | |||
Bucket *trailing = NULL; /* first travels; trailing follows it */ | |||
int result = 0; | |||
assert(current && *current && first); | |||
if (first == *current) | |||
return 0; | |||
do { | |||
trailing = first; | |||
PER_USE_OR_RETURN(first, -1); | |||
first = first->next; | |||
((trailing)->state==cPersistent_STICKY_STATE | |||
&& | |||
((trailing)->state=cPersistent_UPTODATE_STATE)); | |||
PER_ACCESSED(trailing); | |||
if (first == *current) { | |||
*current = trailing; | |||
result = 1; | |||
break; | |||
} | |||
} while (first); | |||
return result; | |||
} | |||
static void * | |||
BTree_Malloc(size_t sz) | |||
{ | |||
void *r; | |||
ASSERT(sz > 0, "non-positive size malloc", NULL); | |||
r = malloc(sz); | |||
if (r) | |||
return r; | |||
PyErr_NoMemory(); | |||
return NULL; | |||
} | |||
static void * | |||
BTree_Realloc(void *p, size_t sz) | |||
{ | |||
void *r; | |||
ASSERT(sz > 0, "non-positive size realloc", NULL); | |||
if (p) | |||
r = realloc(p, sz); | |||
else | |||
r = malloc(sz); | |||
UNLESS (r) | |||
PyErr_NoMemory(); | |||
return r; | |||
} | |||
/* Shared keyword-argument list for BTree/Bucket | |||
* (iter)?(keys|values|items) | |||
*/ | |||
static char *search_keywords[] = {"min", "max", | |||
"excludemin", "excludemax", | |||
0}; | |||
#include "BTreeItemsTemplate.c" | |||
#include "BucketTemplate.c" | |||
#include "SetTemplate.c" | |||
#include "BTreeTemplate.c" | |||
#include "TreeSetTemplate.c" | |||
#include "SetOpTemplate.c" | |||
#include "MergeTemplate.c" | |||
static struct PyMethodDef module_methods[] = { | |||
{"difference", (PyCFunction) difference_m, METH_VARARGS, | |||
"difference(o1, o2) -- " | |||
"compute the difference between o1 and o2" | |||
}, | |||
{"union", (PyCFunction) union_m, METH_VARARGS, | |||
"union(o1, o2) -- compute the union of o1 and o2\n" | |||
}, | |||
{"intersection", (PyCFunction) intersection_m, METH_VARARGS, | |||
"intersection(o1, o2) -- " | |||
"compute the intersection of o1 and o2" | |||
}, | |||
#ifdef MERGE | |||
{"weightedUnion", (PyCFunction) wunion_m, METH_VARARGS, | |||
"weightedUnion(o1, o2 [, w1, w2]) -- compute the union of o1 and o2\n" | |||
"\nw1 and w2 are weights." | |||
}, | |||
{"weightedIntersection", (PyCFunction) wintersection_m, METH_VARARGS, | |||
"weightedIntersection(o1, o2 [, w1, w2]) -- " | |||
"compute the intersection of o1 and o2\n" | |||
"\nw1 and w2 are weights." | |||
}, | |||
#endif | |||
#ifdef MULTI_INT_UNION | |||
{"multiunion", (PyCFunction) multiunion_m, METH_VARARGS, | |||
"multiunion(seq) -- compute union of a sequence of integer sets.\n" | |||
"\n" | |||
"Each element of seq must be an integer set, or convertible to one\n" | |||
"via the set iteration protocol. The union returned is an IISet." | |||
}, | |||
#endif | |||
{NULL, NULL} /* sentinel */ | |||
}; | |||
static char BTree_module_documentation[] = | |||
"\n" | |||
MASTER_ID | |||
BTREEITEMSTEMPLATE_C | |||
"$Id$\n" | |||
BTREETEMPLATE_C | |||
BUCKETTEMPLATE_C | |||
KEYMACROS_H | |||
MERGETEMPLATE_C | |||
SETOPTEMPLATE_C | |||
SETTEMPLATE_C | |||
TREESETTEMPLATE_C | |||
VALUEMACROS_H | |||
BTREEITEMSTEMPLATE_C | |||
; | |||
int | |||
init_persist_type(PyTypeObject *type) | |||
{ | |||
#ifdef PY3K | |||
((PyObject*)type)->ob_type = &PyType_Type; | |||
#else | |||
type->ob_type = &PyType_Type; | |||
#endif | |||
type->tp_base = cPersistenceCAPI->pertype; | |||
if (PyType_Ready(type) < 0) | |||
return 0; | |||
return 1; | |||
} | |||
#ifdef PY3K | |||
static struct PyModuleDef moduledef = { | |||
PyModuleDef_HEAD_INIT, | |||
"_" MOD_NAME_PREFIX "BTree", /* m_name */ | |||
BTree_module_documentation, /* m_doc */ | |||
-1, /* m_size */ | |||
module_methods, /* m_methods */ | |||
NULL, /* m_reload */ | |||
NULL, /* m_traverse */ | |||
NULL, /* m_clear */ | |||
NULL, /* m_free */ | |||
}; | |||
#endif | |||
static PyObject* | |||
module_init(void) | |||
{ | |||
PyObject *module, *mod_dict, *interfaces, *conflicterr; | |||
#ifdef KEY_TYPE_IS_PYOBJECT | |||
object_ = PyTuple_GetItem(Py_TYPE(Py_None)->tp_bases, 0); | |||
if (object_ == NULL) | |||
return NULL; | |||
#endif | |||
sort_str = INTERN("sort"); | |||
if (!sort_str) | |||
return NULL; | |||
reverse_str = INTERN("reverse"); | |||
if (!reverse_str) | |||
return NULL; | |||
__setstate___str = INTERN("__setstate__"); | |||
if (!__setstate___str) | |||
return NULL; | |||
_bucket_type_str = INTERN("_bucket_type"); | |||
if (!_bucket_type_str) | |||
return NULL; | |||
max_internal_size_str = INTERN("max_internal_size"); | |||
if (! max_internal_size_str) | |||
return NULL; | |||
max_leaf_size_str = INTERN("max_leaf_size"); | |||
if (! max_leaf_size_str) | |||
return NULL; | |||
/* Grab the ConflictError class */ | |||
interfaces = PyImport_ImportModule("BTrees.Interfaces"); | |||
if (interfaces != NULL) | |||
{ | |||
conflicterr = PyObject_GetAttrString(interfaces, "BTreesConflictError"); | |||
if (conflicterr != NULL) | |||
ConflictError = conflicterr; | |||
Py_DECREF(interfaces); | |||
} | |||
if (ConflictError == NULL) | |||
{ | |||
Py_INCREF(PyExc_ValueError); | |||
ConflictError=PyExc_ValueError; | |||
} | |||
/* Initialize the PyPersist_C_API and the type objects. */ | |||
#ifdef PY3K | |||
cPersistenceCAPI = (cPersistenceCAPIstruct *)PyCapsule_Import( | |||
"persistent.cPersistence.CAPI", 0); | |||
#else | |||
cPersistenceCAPI = (cPersistenceCAPIstruct *)PyCObject_Import( | |||
"persistent.cPersistence", "CAPI"); | |||
#endif | |||
if (cPersistenceCAPI == NULL) { | |||
/* The Capsule API attempts to import 'persistent' and then | |||
* walk down to the specified attribute using getattr. If the C | |||
* extensions aren't available, this can result in an | |||
* AttributeError being raised. Let that percolate up as an | |||
* ImportError so it can be caught in the expected way. | |||
*/ | |||
if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_ImportError)) { | |||
PyErr_SetString(PyExc_ImportError, "persistent C extension unavailable"); | |||
} | |||
return NULL; | |||
} | |||
#ifdef PY3K | |||
#define _SET_TYPE(typ) ((PyObject*)(&typ))->ob_type = &PyType_Type | |||
#else | |||
#define _SET_TYPE(typ) (typ).ob_type = &PyType_Type | |||
#endif | |||
_SET_TYPE(BTreeItemsType); | |||
_SET_TYPE(BTreeIter_Type); | |||
BTreeIter_Type.tp_getattro = PyObject_GenericGetAttr; | |||
BucketType.tp_new = PyType_GenericNew; | |||
SetType.tp_new = PyType_GenericNew; | |||
BTreeType.tp_new = PyType_GenericNew; | |||
TreeSetType.tp_new = PyType_GenericNew; | |||
if (!init_persist_type(&BucketType)) | |||
return NULL; | |||
if (!init_persist_type(&BTreeType)) | |||
return NULL; | |||
if (!init_persist_type(&SetType)) | |||
return NULL; | |||
if (!init_persist_type(&TreeSetType)) | |||
return NULL; | |||
if (PyDict_SetItem(BTreeType.tp_dict, _bucket_type_str, | |||
(PyObject *)&BucketType) < 0) | |||
{ | |||
fprintf(stderr, "btree failed\n"); | |||
return NULL; | |||
} | |||
if (PyDict_SetItem(TreeSetType.tp_dict, _bucket_type_str, | |||
(PyObject *)&SetType) < 0) | |||
{ | |||
fprintf(stderr, "bucket failed\n"); | |||
return NULL; | |||
} | |||
/* Create the module and add the functions */ | |||
#ifdef PY3K | |||
module = PyModule_Create(&moduledef); | |||
#else | |||
module = Py_InitModule4("_" MOD_NAME_PREFIX "BTree", | |||
module_methods, BTree_module_documentation, | |||
(PyObject *)NULL, PYTHON_API_VERSION); | |||
#endif | |||
/* Add some symbolic constants to the module */ | |||
mod_dict = PyModule_GetDict(module); | |||
if (PyDict_SetItemString(mod_dict, MOD_NAME_PREFIX "Bucket", | |||
(PyObject *)&BucketType) < 0) | |||
return NULL; | |||
if (PyDict_SetItemString(mod_dict, MOD_NAME_PREFIX "BTree", | |||
(PyObject *)&BTreeType) < 0) | |||
return NULL; | |||
if (PyDict_SetItemString(mod_dict, MOD_NAME_PREFIX "Set", | |||
(PyObject *)&SetType) < 0) | |||
return NULL; | |||
if (PyDict_SetItemString(mod_dict, MOD_NAME_PREFIX "TreeSet", | |||
(PyObject *)&TreeSetType) < 0) | |||
return NULL; | |||
if (PyDict_SetItemString(mod_dict, MOD_NAME_PREFIX "TreeIterator", | |||
(PyObject *)&BTreeIter_Type) < 0) | |||
return NULL; | |||
/* We also want to be able to access these constants without the prefix | |||
* so that code can more easily exchange modules (particularly the integer | |||
* and long modules, but also others). The TreeIterator is only internal, | |||
* so we don't bother to expose that. | |||
*/ | |||
if (PyDict_SetItemString(mod_dict, "Bucket", | |||
(PyObject *)&BucketType) < 0) | |||
return NULL; | |||
if (PyDict_SetItemString(mod_dict, "BTree", | |||
(PyObject *)&BTreeType) < 0) | |||
return NULL; | |||
if (PyDict_SetItemString(mod_dict, "Set", | |||
(PyObject *)&SetType) < 0) | |||
return NULL; | |||
if (PyDict_SetItemString(mod_dict, "TreeSet", | |||
(PyObject *)&TreeSetType) < 0) | |||
return NULL; | |||
#if defined(ZODB_64BIT_INTS) && defined(NEED_LONG_LONG_SUPPORT) | |||
if (PyDict_SetItemString(mod_dict, "using64bits", Py_True) < 0) | |||
return NULL; | |||
#else | |||
if (PyDict_SetItemString(mod_dict, "using64bits", Py_False) < 0) | |||
return NULL; | |||
#endif | |||
return module; | |||
} | |||
#ifdef PY3K | |||
PyMODINIT_FUNC INITMODULE(void) | |||
{ | |||
return module_init(); | |||
} | |||
#else | |||
PyMODINIT_FUNC INITMODULE(void) | |||
{ | |||
module_init(); | |||
} | |||
#endif |
@@ -0,0 +1,429 @@ | |||
===================== | |||
Developer Information | |||
===================== | |||
This document provides information for developers who maintain or extend | |||
`BTrees`. | |||
Macros | |||
====== | |||
`BTrees` are defined using a "template", roughly akin to a C++ template. To | |||
create a new family of `BTrees`, create a source file that defines macros used | |||
to handle differences in key and value types: | |||
Configuration Macros | |||
-------------------- | |||
``MASTER_ID`` | |||
A string to hold an RCS/CVS Id key to be included in compiled binaries. | |||
``MOD_NAME_PREFIX`` | |||
A string (like "IO" or "OO") that provides the prefix used for the module. | |||
This gets used to generate type names and the internal module name string. | |||
``DEFAULT_MAX_BUCKET_SIZE`` | |||
An int giving the maximum bucket size (number of key/value pairs). When a | |||
bucket gets larger than this due to an insertion *into a BTREE*, it | |||
splits. Inserting into a bucket directly doesn't split, and functions | |||
that produce a bucket output (e.g., ``union()``) also have no bound on how | |||
large a bucket may get. Someday this will be tunable on `BTree`. | |||
instances. | |||
``DEFAULT_MAX_BTREE_SIZE`` | |||
An ``int`` giving the maximum size (number of children) of an internal | |||
btree node. Someday this will be tunable on ``BTree`` instances. | |||
Macros for Keys | |||
--------------- | |||
``KEY_TYPE`` | |||
The C type declaration for keys (e.g., ``int`` or ``PyObject*``). | |||
``KEY_TYPE_IS_PYOBJECT`` | |||
Define if ``KEY_TYPE`` is a ``PyObject*`, else ``undef``. | |||
``KEY_CHECK(K)`` | |||
Tests whether the ``PyObject* K`` can be converted to the (``C``) key type | |||
(``KEY_TYPE``). The macro should return a boolean (zero for false, | |||
non-zero for true). When it returns false, its caller should probably set | |||
a ``TypeError`` exception. | |||
``KEY_CHECK_ON_SET(K)`` | |||
Like ``KEY_CHECK``, but only checked during ``__setitem__``. | |||
``TEST_KEY_SET_OR(V, K, T)`` | |||
Like Python's ``cmp()``. Compares K(ey) to T(arget), where ``K`` | |||
and ``T`` are ``C`` values of type `KEY_TYPE`. ``V`` is assigned an `int` | |||
value depending on the outcome:: | |||
< 0 if K < T | |||
== 0 if K == T | |||
> 0 if K > T | |||
This macro acts like an ``if``, where the following statement is executed | |||
only if a Python exception has been raised because the values could not be | |||
compared. | |||
``DECREF_KEY(K)`` | |||
``K`` is a value of ``KEY_TYPE``. If ``KEY_TYPE`` is a flavor of | |||
``PyObject*``, write this to do ``Py_DECREF(K)``. Else (e.g., | |||
``KEY_TYPE`` is ``int``) make it a nop. | |||
``INCREF_KEY(K)`` | |||
``K`` is a value of `KEY_TYPE`. If `KEY_TYPE` is a flavor of | |||
``PyObject*``, write this to do ``Py_INCREF(K)``. Else (e.g., `KEY_TYPE` | |||
is ``int``) make it a nop. | |||
``COPY_KEY(K, E)`` | |||
Like ``K=E``. Copy a key from ``E`` to ``K``, both of ``KEY_TYPE``. Note | |||
that this doesn't ``decref K`` or ``incref E`` when ``KEY_TYPE`` is a | |||
``PyObject*``; the caller is responsible for keeping refcounts straight. | |||
``COPY_KEY_TO_OBJECT(O, K)`` | |||
Roughly like ``O=K``. ``O`` is a ``PyObject*``, and the macro must build | |||
a Python object form of ``K``, assign it to ``O``, and ensure that ``O`` | |||
owns the reference to its new value. It may do this by creating a new | |||
Python object based on ``K`` (e.g., ``PyInt_FromLong(K)`` when | |||
``KEY_TYPE`` is ``int``), or simply by doing ``Py_INCREF(K)`` if | |||
``KEY_TYPE`` is a ``PyObject*``. | |||
``COPY_KEY_FROM_ARG(TARGET, ARG, STATUS)`` | |||
Copy an argument to the target without creating a new reference to | |||
``ARG``. ``ARG`` is a ``PyObject*``, and ``TARGET`` is of type | |||
``KEY_TYPE``. If this can't be done (for example, ``KEY_CHECK(ARG)`` | |||
returns false), set a Python error and set status to ``0``. If there is | |||
no error, leave status alone. | |||
Macros for Values | |||
----------------- | |||
``VALUE_TYPE`` | |||
The C type declaration for values (e.g., ``int`` or ``PyObject*``). | |||
``VALUE_TYPE_IS_PYOBJECT`` | |||
Define if ``VALUE_TYPE`` is a ``PyObject*``, else ``undef``. | |||
``TEST_VALUE(X, Y)`` | |||
Like Python's ``cmp()``. Compares ``X`` to ``Y``, where ``X`` & ``Y`` are | |||
``C`` values of type ``VALUE_TYPE``. The macro returns an ``int``, with | |||
value:: | |||
< 0 if X < Y | |||
== 0 if X == Y | |||
> 0 if X > Y | |||
Bug: There is no provision for determining whether the comparison attempt | |||
failed (set a Python exception). | |||
``DECREF_VALUE(K)`` | |||
Like ``DECREF_KEY``, except applied to values of ``VALUE_TYPE``. | |||
``INCREF_VALUE(K)`` | |||
Like ``INCREF_KEY``, except applied to values of ``VALUE_TYPE``. | |||
``COPY_VALUE(K, E)`` | |||
Like ``COPY_KEY``, except applied to values of ``VALUE_TYPE``. | |||
``COPY_VALUE_TO_OBJECT(O, K)`` | |||
Like ``COPY_KEY_TO_OBJECT``, except applied to values of ``VALUE_TYPE``. | |||
``COPY_VALUE_FROM_ARG(TARGET, ARG, STATUS)`` | |||
Like ``COPY_KEY_FROM_ARG``, except applied to values of ``VALUE_TYPE``. | |||
``NORMALIZE_VALUE(V, MIN)`` | |||
Normalize the value, ``V``, using the parameter ``MIN``. This is almost | |||
certainly a YAGNI. It is a no-op for most types. For integers, ``V`` is | |||
replaced by ``V/MIN`` only if ``MIN > 0``. | |||
Macros for Set Operations | |||
------------------------- | |||
``MERGE_DEFAULT`` | |||
A value of ``VALUE_TYPE`` specifying the value to associate with set | |||
elements when sets are merged with mappings via weighed union or weighted | |||
intersection. | |||
``MERGE(O1, w1, O2, w2)`` | |||
Performs a weighted merge of two values, ``O1`` and ``O2``, using weights | |||
``w1`` and ``w2``. The result must be of ``VALUE_TYPE``. Note that | |||
weighted unions and weighted intersections are not enabled if this macro | |||
is left undefined. | |||
``MERGE_WEIGHT(O, w)`` | |||
Computes a weighted value for ``O``. The result must be of | |||
``VALUE_TYPE``. This is used for "filling out" weighted unions, i.e. to | |||
compute a weighted value for keys that appear in only one of the input | |||
mappings. If left undefined, ``MERGE_WEIGHT`` defaults to:: | |||
#define MERGE_WEIGHT(O, w) (O) | |||
``MULTI_INT_UNION`` | |||
The value doesn't matter. If defined, `SetOpTemplate.c` compiles code for | |||
a ``multiunion()`` function (compute a union of many input sets at high | |||
speed). This currently makes sense only for structures with integer keys. | |||
BTree Clues | |||
=========== | |||
More or less random bits of helpful info. | |||
+ In papers and textbooks, this flavor of BTree is usually called a B+-Tree, | |||
where "+" is a superscript. | |||
+ All keys and all values live in the bucket leaf nodes. Keys in interior | |||
(BTree) nodes merely serve to guide a search efficiently toward the correct | |||
leaf. | |||
+ When a key is deleted, it's physically removed from the bucket it's in, but | |||
this doesn't propagate back up the tree: since keys in interior nodes only | |||
serve to guide searches, it's OK-- and saves time --to leave "stale" keys in | |||
interior nodes. | |||
+ No attempt is made to rebalance the tree after a deletion, unless a bucket | |||
thereby becomes entirely empty. "Classic BTrees" do rebalance, keeping all | |||
buckets at least half full (provided there are enough keys in the entire | |||
tree to fill half a bucket). The tradeoffs are murky. Pathological cases | |||
in the presence of deletion do exist. Pathologies include trees tending | |||
toward only one key per bucket, and buckets at differing depths (all buckets | |||
are at the same depth in a classic BTree). | |||
+ ``DEFAULT_MAX_BUCKET_SIZE`` and ``DEFAULT_MAX_BTREE_SIZE`` are chosen mostly | |||
to "even out" pickle sizes in storage. That's why, e.g., an `IIBTree` has | |||
larger values than an `OOBTree`: pickles store ints more efficiently than | |||
they can store arbitrary Python objects. | |||
+ In a non-empty BTree, every bucket node contains at least one key, and every | |||
BTree node contains at least one child and a non-NULL firstbucket pointer. | |||
However, a BTree node may not contain any keys. | |||
+ An empty BTree consists solely of a BTree node with ``len==0`` and | |||
``firstbucket==NULL``. | |||
+ Although a BTree can become unbalanced under a mix of inserts and deletes | |||
(meaning both that there's nothing stronger that can be said about buckets | |||
than that they're not empty, and that buckets can appear at different | |||
depths), a BTree node always has children of the same kind: they're all | |||
buckets, or they're all BTree nodes. | |||
The ``BTREE_SEARCH`` Macro | |||
========================== | |||
For notational ease, consider a fixed BTree node ``x``, and let | |||
:: | |||
K(i) mean x->data.key[i] | |||
C(i) mean all the keys reachable from x->data.child[i] | |||
For each ``i`` in ``0`` to ``x->len-1`` inclusive, | |||
:: | |||
K(i) <= C(i) < K(i+1) | |||
is a BTree node invariant, where we pretend that ``K(0)`` holds a key smaller | |||
than any possible key, and ``K(x->len)`` holds a key larger than any possible | |||
key. (Note that ``K(x->len)`` doesn't actually exist, and ``K(0)`` is never | |||
used although space for it exists in non-empty BTree nodes.) | |||
When searching for a key ``k``, then, the child pointer we want to follow is | |||
the one at index ``i`` such that ``K(i) <= k < K(i+1)``. There can be at most | |||
one such ``i``, since the ``K(i)`` are strictly increasing. And there is at | |||
least one such ``i`` provided the tree isn't empty (so that ``0 < len``). For | |||
the moment, assume the tree isn't empty (we'll get back to that later). | |||
The macro's chief loop invariant is | |||
:: | |||
K(lo) < k < K(hi) | |||
This holds trivially at the start, since ``lo`` is set to ``0``, and ``hi`` to | |||
``x->len``, and we pretend ``K(0)`` is minus infinity and ``K(len)`` is plus | |||
infinity. Inside the loop, if ``K(i) < k`` we set ``lo`` to ``i``, and if | |||
``K(i) > k`` we set ``hi`` to ``i``. These obviously preserve the invariant. | |||
If ``K(i) == k``, the loop breaks and sets the result to ``i``, and since | |||
``K(i) == k`` in that case ``i`` is obviously the correct result. | |||
Other cases depend on how ``i = floor((lo + hi)/2)`` works, exactly. Suppose | |||
``lo + d = hi`` for some ``d >= 0``. Then ``i = floor((lo + lo + d)/2) = | |||
floor(lo + d/2) = lo + floor(d/2)``. So: | |||
a. ``[d == 0] (lo == i == hi)`` if and only if ``(lo == hi)``. | |||
b. ``[d == 1] (lo == i < hi)`` if and only if ``(lo+1 == hi)``. | |||
c. ``[d > 1] (lo < i < hi)`` if and only if ``(lo+1 < hi)``. | |||
If the node is empty ``(x->len == 0)``, then ``lo==i==hi==0`` at the start, | |||
and the loop exits immediately (the first ``i > lo`` test fails), without | |||
entering the body. | |||
Else ``lo < hi`` at the start, and the invariant ``K(lo) < k < K(hi)`` holds. | |||
If ``lo+1 < hi``, we're in case (c): ``i`` is strictly between ``lo`` and | |||
``hi``, so the loop body is entered, and regardless of whether the body sets | |||
the new ``lo`` or the new ``hi`` to ``i``, the new ``lo`` is strictly less | |||
than the new ``hi``, and the difference between the new ``lo`` and new ``hi`` | |||
is strictly less than the difference between the old ``lo`` and old ``hi``. | |||
So long as the new ``lo + 1`` remains < the new ``hi``, we stay in this case. | |||
We can't stay in this case forever, though: because ``hi-lo`` decreases on | |||
each trip but remains > ``0``, ``lo+1 == hi`` must eventually become true. | |||
(In fact, it becomes true quickly, in about ``log2(x->len)`` trips; the point | |||
is more that ``lo`` doesn't equal ``hi`` when the loop ends, it has to end | |||
with ``lo+1==hi`` and ``i==lo``). | |||
Then we're in case (b): ``i==lo==hi-1`` then, and the loop exits. The | |||
invariant still holds, with ``lo==i`` and ``hi==lo+1==i+1``:: | |||
K(i) < k < K(i+1) | |||
so ``i`` is again the correct answer. | |||
Optimization points: | |||
-------------------- | |||
+ Division by 2 is done via shift rather via "/2". These are signed ints, and | |||
almost all C compilers treat signed int division as truncating, and shifting | |||
is not the same as truncation for signed int division. The compiler has no | |||
way to know these values aren't negative, so has to generate longer-winded | |||
code for "/2". But we know these values aren't negative, and exploit it. | |||
+ The order of _cmp comparisons matters. We're in an interior BTree node, and | |||
are looking at only a tiny fraction of all the keys that exist. So finding | |||
the key exactly in this node is unlikely, and checking ``_cmp == 0`` is a | |||
waste of time to the same extent. It doesn't matter whether we check for | |||
``_cmp < 0`` or ``_cmp > 0`` first, so long as we do both before worrying | |||
about equality. | |||
+ At the start of a routine, it's better to run this macro even if ``x->len`` | |||
is ``0`` (check for that afterwards). We just called a function and so | |||
probably drained the pipeline. If the first thing we do then is read up | |||
``self->len`` and check it against ``0``, we just sit there waiting for the | |||
data to get read up, and then another immediate test-and-branch, and for a | |||
very unlikely case (BTree nodes are rarely empty). It's better to get into | |||
the loop right away so the normal case makes progress ASAP. | |||
The ``BUCKET_SEARCH`` Macro | |||
=========================== | |||
This has a different job than ``BTREE_SEARCH``: the key ``0`` slot is | |||
legitimate in a bucket, and we want to find the index at which the key | |||
belongs. If the key is larger than the bucket's largest key, a new slot at | |||
index len is where it belongs, else it belongs at the smallest ``i`` with | |||
``keys[i]`` >= the key we're looking for. We also need to know whether or not | |||
the key is present (``BTREE_SEARCH`` didn't care; it only wanted to find the | |||
next node to search). | |||
The mechanics of the search are quite similar, though. The primary | |||
loop invariant changes to (say we're searching for key ``k``):: | |||
K(lo-1) < k < K(hi) | |||
where ``K(i)`` means ``keys[i]``, and we pretend ``K(-1)`` is minus infinity | |||
and ``K(len)`` is plus infinity. | |||
If the bucket is empty, ``lo=hi=i=0`` at the start, the loop body is never | |||
entered, and the macro sets ``INDEX`` to 0 and ``ABSENT`` to true. That's why | |||
``_cmp`` is initialized to 1 (``_cmp`` becomes ``ABSENT``). | |||
Else the bucket is not empty, lo<hi at the start, and the loop body is | |||
entered. The invariant is obviously satisfied then, as ``lo=0`` and | |||
``hi=len``. | |||
If ``K[i]<k``, ``lo`` is set to ``i+1``, preserving that ``K(lo-1) = K[i] < | |||
k``. | |||
If ``K[i]>k``, ``hi`` is set to ``i``, preserving that ``K[hi] = K[i] > k``. | |||
If the loop exits after either of those, ``_cmp != 0``, so ``ABSENT`` becomes | |||
true. | |||
If ``K[i]=k``, the loop breaks, so that ``INDEX`` becomes ``i``, and | |||
``ABSENT`` becomes false (``_cmp=0`` in this case). | |||
The same case analysis for ``BTREE_SEARCH`` on ``lo`` and ``hi`` holds here: | |||
a. ``(lo == i == hi)`` if and only if ``(lo == hi)``. | |||
b. ``(lo == i < hi)`` if and only if ``(lo+1 == hi)``. | |||
c. ``(lo < i < hi)`` if and only if ``(lo+1 < hi)``. | |||
So long as ``lo+1 < hi``, we're in case (c), and either break with equality | |||
(in which case the right results are obviously computed) or narrow the range. | |||
If equality doesn't obtain, the range eventually narrows to cases (a) or (b). | |||
To go from (c) to (a), we must have ``lo+2==hi`` at the start, and | |||
``K[i]=K[lo+1]<k``. Then the new lo gets set to ``i+1 = lo+2 = hi``, and the | |||
loop exits with ``lo=hi=i`` and ``_cmp<0``. This is correct, because we know | |||
that ``k != K(i)`` (loop invariant! we actually know something stronger, that | |||
``k < K(hi)``; since ``i=hi``, this implies ``k != K(i)``). | |||
Else (c) eventually falls into case (b), ``lo+1==hi`` and ``i==lo``. The | |||
invariant tells us ``K(lo-1) < k < K(hi) = K(lo+1)``, so if the key is present | |||
it must be at ``K(lo)``. ``i==lo`` in this case, so we test ``K(lo)`` against | |||
``k``. As always, if equality obtains we do the right thing, else case #b | |||
becomes case (a). | |||
When (b) becomes (a), the last comparison was non-equal, so ``_cmp`` is | |||
non-zero, and the loop exits because ``lo==hi==i`` in case (a). The invariant | |||
then tells us ``K(lo-1) < k < K(lo)``, so the key is in fact not present, it's | |||
correct to exit with ``_cmp`` non-zero, and ``i==lo`` is again the index at | |||
which ``k`` belongs. | |||
Optimization points: | |||
-------------------- | |||
+ As for ``BTREE_SEARCH``, shifting of signed ints is cheaper than division. | |||
+ Unlike as for ``BTREE_SEARCH``, there's nothing special about searching an | |||
empty bucket, and the macro computes thoroughly sensible results in that | |||
case. | |||
+ The order of ``_cmp`` comparisons differs from ``BTREE_SEARCH``. When | |||
searching a bucket, it's much more likely (than when searching a BTree node) | |||
that the key is present, so testing ``__cmp==0`` isn't a systematic waste of | |||
cycles. At the extreme, if all searches are successful (key present), on | |||
average this saves one comparison per search, against leaving the | |||
determination of ``_cmp==0`` implicit (as ``BTREE_SEARCH`` does). But even | |||
on successful searches, ``__cmp != 0`` is a more popular outcome than | |||
``__cmp == 0`` across iterations (unless the bucket has only a few keys), so | |||
it's important to check one of the inequality cases first. It turns out | |||
it's better on average to check ``K(i) < key`` (than to check ``K(i) > | |||
key``), because when it pays it narrows the range more (we get a little | |||
boost from setting ``lo=i+1`` in this case; the other case sets ``hi=i``, | |||
which isn't as much of a narrowing). |
@@ -0,0 +1,112 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2001-2012 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE | |||
# | |||
############################################################################## | |||
__all__ = ('Bucket', 'Set', 'BTree', 'TreeSet', | |||
'IFBucket', 'IFSet', 'IFBTree', 'IFTreeSet', | |||
'union', 'intersection', 'difference', | |||
'weightedUnion', 'weightedIntersection', 'multiunion', | |||
) | |||
from zope.interface import moduleProvides | |||
from .Interfaces import IIntegerFloatBTreeModule | |||
from ._base import Bucket | |||
from ._base import MERGE | |||
from ._base import MERGE_WEIGHT_numeric | |||
from ._base import MERGE_DEFAULT_float | |||
from ._base import Set | |||
from ._base import Tree as BTree | |||
from ._base import TreeSet | |||
from ._base import _TreeIterator | |||
from ._base import difference as _difference | |||
from ._base import intersection as _intersection | |||
from ._base import multiunion as _multiunion | |||
from ._base import set_operation as _set_operation | |||
from ._base import to_int as _to_key | |||
from ._base import to_float as _to_value | |||
from ._base import union as _union | |||
from ._base import weightedIntersection as _weightedIntersection | |||
from ._base import weightedUnion as _weightedUnion | |||
from ._base import _fix_pickle | |||
from ._compat import import_c_extension | |||
_BUCKET_SIZE = 120 | |||
_TREE_SIZE = 500 | |||
using64bits = False | |||
class IFBucketPy(Bucket): | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class IFSetPy(Set): | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class IFBTreePy(BTree): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class IFTreeSetPy(TreeSet): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class IFTreeIteratorPy(_TreeIterator): | |||
pass | |||
# Can't declare forward refs, so fix up afterwards: | |||
IFBucketPy._mapping_type = IFBucketPy._bucket_type = IFBucketPy | |||
IFBucketPy._set_type = IFSetPy | |||
IFSetPy._mapping_type = IFBucketPy | |||
IFSetPy._set_type = IFSetPy._bucket_type = IFSetPy | |||
IFBTreePy._mapping_type = IFBTreePy._bucket_type = IFBucketPy | |||
IFBTreePy._set_type = IFSetPy | |||
IFTreeSetPy._mapping_type = IFBucketPy | |||
IFTreeSetPy._set_type = IFTreeSetPy._bucket_type = IFSetPy | |||
differencePy = _set_operation(_difference, IFSetPy) | |||
unionPy = _set_operation(_union, IFSetPy) | |||
intersectionPy = _set_operation(_intersection, IFSetPy) | |||
multiunionPy = _set_operation(_multiunion, IFSetPy) | |||
weightedUnionPy = _set_operation(_weightedUnion, IFSetPy) | |||
weightedIntersectionPy = _set_operation(_weightedIntersection, IFSetPy) | |||
import_c_extension(globals()) | |||
_fix_pickle(globals(), __name__) | |||
moduleProvides(IIntegerFloatBTreeModule) |
@@ -0,0 +1,113 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2001-2012 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE | |||
# | |||
############################################################################## | |||
__all__ = ('Bucket', 'Set', 'BTree', 'TreeSet', | |||
'IIBucket', 'IISet', 'IIBTree', 'IITreeSet', | |||
'union', 'intersection', 'difference', | |||
'weightedUnion', 'weightedIntersection', 'multiunion', | |||
) | |||
from zope.interface import moduleProvides | |||
from .Interfaces import IIntegerIntegerBTreeModule | |||
from ._base import Bucket | |||
from ._base import MERGE | |||
from ._base import MERGE_WEIGHT_numeric | |||
from ._base import MERGE_DEFAULT_int | |||
from ._base import Set | |||
from ._base import Tree as BTree | |||
from ._base import TreeSet | |||
from ._base import _TreeIterator | |||
from ._base import difference as _difference | |||
from ._base import intersection as _intersection | |||
from ._base import multiunion as _multiunion | |||
from ._base import set_operation as _set_operation | |||
from ._base import to_int as _to_key | |||
_to_value = _to_key | |||
from ._base import union as _union | |||
from ._base import weightedIntersection as _weightedIntersection | |||
from ._base import weightedUnion as _weightedUnion | |||
from ._base import _fix_pickle | |||
from ._compat import import_c_extension | |||
_BUCKET_SIZE = 120 | |||
_TREE_SIZE = 500 | |||
using64bits = False | |||
class IIBucketPy(Bucket): | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class IISetPy(Set): | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class IIBTreePy(BTree): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class IITreeSetPy(TreeSet): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class IITreeIteratorPy(_TreeIterator): | |||
pass | |||
# Can't declare forward refs, so fix up afterwards: | |||
IIBucketPy._mapping_type = IIBucketPy._bucket_type = IIBucketPy | |||
IIBucketPy._set_type = IISetPy | |||
IISetPy._mapping_type = IIBucketPy | |||
IISetPy._set_type = IISetPy._bucket_type = IISetPy | |||
IIBTreePy._mapping_type = IIBTreePy._bucket_type = IIBucketPy | |||
IIBTreePy._set_type = IISetPy | |||
IITreeSetPy._mapping_type = IIBucketPy | |||
IITreeSetPy._set_type = IITreeSetPy._bucket_type = IISetPy | |||
differencePy = _set_operation(_difference, IISetPy) | |||
unionPy = _set_operation(_union, IISetPy) | |||
intersectionPy = _set_operation(_intersection, IISetPy) | |||
multiunionPy = _set_operation(_multiunion, IISetPy) | |||
weightedUnionPy = _set_operation(_weightedUnion, IISetPy) | |||
weightedIntersectionPy = _set_operation(_weightedIntersection, IISetPy) | |||
import_c_extension(globals()) | |||
_fix_pickle(globals(), __name__) | |||
moduleProvides(IIntegerIntegerBTreeModule) |
@@ -0,0 +1,95 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2001-2012 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE | |||
# | |||
############################################################################## | |||
__all__ = ('Bucket', 'Set', 'BTree', 'TreeSet', | |||
'IOBucket', 'IOSet', 'IOBTree', 'IOTreeSet', | |||
'union', 'intersection', 'difference', 'multiunion', | |||
) | |||
from zope.interface import moduleProvides | |||
from .Interfaces import IIntegerObjectBTreeModule | |||
from ._base import Bucket | |||
from ._base import MERGE_WEIGHT_default | |||
from ._base import Set | |||
from ._base import Tree as BTree | |||
from ._base import TreeSet | |||
from ._base import _TreeIterator | |||
from ._base import difference as _difference | |||
from ._base import intersection as _intersection | |||
from ._base import multiunion as _multiunion | |||
from ._base import set_operation as _set_operation | |||
from ._base import to_int as _to_key | |||
from ._base import to_ob as _to_value | |||
from ._base import union as _union | |||
from ._base import _fix_pickle | |||
from ._compat import import_c_extension | |||
_BUCKET_SIZE = 60 | |||
_TREE_SIZE = 500 | |||
using64bits = False | |||
class IOBucketPy(Bucket): | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE_WEIGHT = MERGE_WEIGHT_default | |||
class IOSetPy(Set): | |||
_to_key = _to_key | |||
class IOBTreePy(BTree): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE_WEIGHT = MERGE_WEIGHT_default | |||
class IOTreeSetPy(TreeSet): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
class IOTreeIteratorPy(_TreeIterator): | |||
pass | |||
# Can't declare forward refs, so fix up afterwards: | |||
IOBucketPy._mapping_type = IOBucketPy._bucket_type = IOBucketPy | |||
IOBucketPy._set_type = IOSetPy | |||
IOSetPy._mapping_type = IOBucketPy | |||
IOSetPy._set_type = IOSetPy._bucket_type = IOSetPy | |||
IOBTreePy._mapping_type = IOBTreePy._bucket_type = IOBucketPy | |||
IOBTreePy._set_type = IOSetPy | |||
IOTreeSetPy._mapping_type = IOBucketPy | |||
IOTreeSetPy._set_type = IOTreeSetPy._bucket_type = IOSetPy | |||
differencePy = _set_operation(_difference, IOSetPy) | |||
unionPy = _set_operation(_union, IOSetPy) | |||
intersectionPy = _set_operation(_intersection, IOSetPy) | |||
multiunionPy = _set_operation(_multiunion, IOSetPy) | |||
import_c_extension(globals()) | |||
_fix_pickle(globals(), __name__) | |||
moduleProvides(IIntegerObjectBTreeModule) |
@@ -0,0 +1,527 @@ | |||
############################################################################## | |||
# | |||
# 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 | |||
# | |||
############################################################################## | |||
from zope.interface import Interface, Attribute | |||
class ICollection(Interface): | |||
def clear(): | |||
"""Remove all of the items from the collection.""" | |||
def __nonzero__(): | |||
"""Check if the collection is non-empty. | |||
Return a true value if the collection is non-empty and a | |||
false value otherwise. | |||
""" | |||
class IReadSequence(Interface): | |||
def __getitem__(index): | |||
"""Return the value at the given index. | |||
An IndexError is raised if the index cannot be found. | |||
""" | |||
def __getslice__(index1, index2): | |||
"""Return a subsequence from the original sequence. | |||
The subsequence includes the items from index1 up to, but not | |||
including, index2. | |||
""" | |||
class IKeyed(ICollection): | |||
def has_key(key): | |||
"""Check whether the object has an item with the given key. | |||
Return a true value if the key is present, else a false value. | |||
""" | |||
def keys(min=None, max=None, excludemin=False, excludemax=False): | |||
"""Return an IReadSequence containing the keys in the collection. | |||
The type of the IReadSequence is not specified. It could be a list | |||
or a tuple or some other type. | |||
All arguments are optional, and may be specified as keyword | |||
arguments, or by position. | |||
If a min is specified, then output is constrained to keys greater | |||
than or equal to the given min, and, if excludemin is specified and | |||
true, is further constrained to keys strictly greater than min. A | |||
min value of None is ignored. If min is None or not specified, and | |||
excludemin is true, the smallest key is excluded. | |||
If a max is specified, then output is constrained to keys less than | |||
or equal to the given max, and, if excludemax is specified and | |||
true, is further constrained to keys strictly less than max. A max | |||
value of None is ignored. If max is None or not specified, and | |||
excludemax is true, the largest key is excluded. | |||
""" | |||
def maxKey(key=None): | |||
"""Return the maximum key. | |||
If a key argument if provided and not None, return the largest key | |||
that is less than or equal to the argument. Raise an exception if | |||
no such key exists. | |||
""" | |||
def minKey(key=None): | |||
"""Return the minimum key. | |||
If a key argument if provided and not None, return the smallest key | |||
that is greater than or equal to the argument. Raise an exception | |||
if no such key exists. | |||
""" | |||
class ISetMutable(IKeyed): | |||
def insert(key): | |||
"""Add the key (value) to the set. | |||
If the key was already in the set, return 0, otherwise return 1. | |||
""" | |||
def remove(key): | |||
"""Remove the key from the set. | |||
Raises KeyError if key is not in the set. | |||
""" | |||
def update(seq): | |||
"""Add the items from the given sequence to the set.""" | |||
class ISized(Interface): | |||
"""An object that supports __len__.""" | |||
def __len__(): | |||
"""Return the number of items in the container.""" | |||
class IKeySequence(IKeyed, ISized): | |||
def __getitem__(index): | |||
"""Return the key in the given index position. | |||
This allows iteration with for loops and use in functions, | |||
like map and list, that read sequences. | |||
""" | |||
class ISet(IKeySequence, ISetMutable): | |||
pass | |||
class ITreeSet(IKeyed, ISetMutable): | |||
pass | |||
class IMinimalDictionary(ISized, IKeyed): | |||
def get(key, default): | |||
"""Get the value associated with the given key. | |||
Return the default if has_key(key) is false. | |||
""" | |||
def __getitem__(key): | |||
"""Get the value associated with the given key. | |||
Raise KeyError if has_key(key) is false. | |||
""" | |||
def __setitem__(key, value): | |||
"""Set the value associated with the given key.""" | |||
def __delitem__(key): | |||
"""Delete the value associated with the given key. | |||
Raise KeyError if has_key(key) is false. | |||
""" | |||
def values(min=None, max=None, excludemin=False, excludemax=False): | |||
"""Return an IReadSequence containing the values in the collection. | |||
The type of the IReadSequence is not specified. It could be a list | |||
or a tuple or some other type. | |||
All arguments are optional, and may be specified as keyword | |||
arguments, or by position. | |||
If a min is specified, then output is constrained to values whose | |||
keys are greater than or equal to the given min, and, if excludemin | |||
is specified and true, is further constrained to values whose keys | |||
are strictly greater than min. A min value of None is ignored. If | |||
min is None or not specified, and excludemin is true, the value | |||
corresponding to the smallest key is excluded. | |||
If a max is specified, then output is constrained to values whose | |||
keys are less than or equal to the given max, and, if excludemax is | |||
specified and true, is further constrained to values whose keys are | |||
strictly less than max. A max value of None is ignored. If max is | |||
None or not specified, and excludemax is true, the value | |||
corresponding to the largest key is excluded. | |||
""" | |||
def items(min=None, max=None, excludemin=False, excludemax=False): | |||
"""Return an IReadSequence containing the items in the collection. | |||
An item is a 2-tuple, a (key, value) pair. | |||
The type of the IReadSequence is not specified. It could be a list | |||
or a tuple or some other type. | |||
All arguments are optional, and may be specified as keyword | |||
arguments, or by position. | |||
If a min is specified, then output is constrained to items whose | |||
keys are greater than or equal to the given min, and, if excludemin | |||
is specified and true, is further constrained to items whose keys | |||
are strictly greater than min. A min value of None is ignored. If | |||
min is None or not specified, and excludemin is true, the item with | |||
the smallest key is excluded. | |||
If a max is specified, then output is constrained to items whose | |||
keys are less than or equal to the given max, and, if excludemax is | |||
specified and true, is further constrained to items whose keys are | |||
strictly less than max. A max value of None is ignored. If max is | |||
None or not specified, and excludemax is true, the item with the | |||
largest key is excluded. | |||
""" | |||
class IDictionaryIsh(IMinimalDictionary): | |||
def update(collection): | |||
"""Add the items from the given collection object to the collection. | |||
The input collection must be a sequence of (key, value) 2-tuples, | |||
or an object with an 'items' method that returns a sequence of | |||
(key, value) pairs. | |||
""" | |||
def byValue(minValue): | |||
"""Return a sequence of (value, key) pairs, sorted by value. | |||
Values < minValue are omitted and other values are "normalized" by | |||
the minimum value. This normalization may be a noop, but, for | |||
integer values, the normalization is division. | |||
""" | |||
def setdefault(key, d): | |||
"""D.setdefault(k, d) -> D.get(k, d), also set D[k]=d if k not in D. | |||
Return the value like get() except that if key is missing, d is both | |||
returned and inserted into the dictionary as the value of k. | |||
Note that, unlike as for Python's dict.setdefault(), d is not | |||
optional. Python defaults d to None, but that doesn't make sense | |||
for mappings that can't have None as a value (for example, an | |||
IIBTree can have only integers as values). | |||
""" | |||
def pop(key, d): | |||
"""D.pop(k[, d]) -> v, remove key and return the corresponding value. | |||
If key is not found, d is returned if given, otherwise KeyError is | |||
raised. | |||
""" | |||
class IBTree(IDictionaryIsh): | |||
def insert(key, value): | |||
"""Insert a key and value into the collection. | |||
If the key was already in the collection, then there is no | |||
change and 0 is returned. | |||
If the key was not already in the collection, then the item is | |||
added and 1 is returned. | |||
This method is here to allow one to generate random keys and | |||
to insert and test whether the key was there in one operation. | |||
A standard idiom for generating new keys will be:: | |||
key = generate_key() | |||
while not t.insert(key, value): | |||
key=generate_key() | |||
""" | |||
class IMerge(Interface): | |||
"""Object with methods for merging sets, buckets, and trees. | |||
These methods are supplied in modules that define collection | |||
classes with particular key and value types. The operations apply | |||
only to collections from the same module. For example, the | |||
IIBTree.union can only be used with IIBTree.IIBTree, | |||
IIBTree.IIBucket, IIBTree.IISet, and IIBTree.IITreeSet. | |||
The implementing module has a value type. The IOBTree and OOBTree | |||
modules have object value type. The IIBTree and OIBTree modules | |||
have integer value types. Other modules may be defined in the | |||
future that have other value types. | |||
The individual types are classified into set (Set and TreeSet) and | |||
mapping (Bucket and BTree) types. | |||
""" | |||
def difference(c1, c2): | |||
"""Return the keys or items in c1 for which there is no key in c2. | |||
If c1 is None, then None is returned. If c2 is None, then c1 | |||
is returned. | |||
If neither c1 nor c2 is None, the output is a Set if c1 is a Set or | |||
TreeSet, and is a Bucket if c1 is a Bucket or BTree. | |||
""" | |||
def union(c1, c2): | |||
"""Compute the Union of c1 and c2. | |||
If c1 is None, then c2 is returned, otherwise, if c2 is None, | |||
then c1 is returned. | |||
The output is a Set containing keys from the input | |||
collections. | |||
""" | |||
def intersection(c1, c2): | |||
"""Compute the intersection of c1 and c2. | |||
If c1 is None, then c2 is returned, otherwise, if c2 is None, | |||
then c1 is returned. | |||
The output is a Set containing matching keys from the input | |||
collections. | |||
""" | |||
class IBTreeModule(Interface): | |||
"""These are available in all modules (IOBTree, OIBTree, OOBTree, IIBTree, | |||
IFBTree, LFBTree, LOBTree, OLBTree, and LLBTree). | |||
""" | |||
BTree = Attribute( | |||
"""The IBTree for this module. | |||
Also available as [prefix]BTree, as in IOBTree.""") | |||
Bucket = Attribute( | |||
"""The leaf-node data buckets used by the BTree. | |||
(IBucket is not currently defined in this file, but is essentially | |||
IDictionaryIsh, with the exception of __nonzero__, as of this | |||
writing.) | |||
Also available as [prefix]Bucket, as in IOBucket.""") | |||
TreeSet = Attribute( | |||
"""The ITreeSet for this module. | |||
Also available as [prefix]TreeSet, as in IOTreeSet.""") | |||
Set = Attribute( | |||
"""The ISet for this module: the leaf-node data buckets used by the | |||
TreeSet. | |||
Also available as [prefix]BTree, as in IOSet.""") | |||
class IIMerge(IMerge): | |||
"""Merge collections with integer value type. | |||
A primary intent is to support operations with no or integer | |||
values, which are used as "scores" to rate indiviual keys. That | |||
is, in this context, a BTree or Bucket is viewed as a set with | |||
scored keys, using integer scores. | |||
""" | |||
def weightedUnion(c1, c2, weight1=1, weight2=1): | |||
"""Compute the weighted union of c1 and c2. | |||
If c1 and c2 are None, the output is (0, None). | |||
If c1 is None and c2 is not None, the output is (weight2, c2). | |||
If c1 is not None and c2 is None, the output is (weight1, c1). | |||
Else, and hereafter, c1 is not None and c2 is not None. | |||
If c1 and c2 are both sets, the output is 1 and the (unweighted) | |||
union of the sets. | |||
Else the output is 1 and a Bucket whose keys are the union of c1 and | |||
c2's keys, and whose values are:: | |||
v1*weight1 + v2*weight2 | |||
where: | |||
v1 is 0 if the key is not in c1 | |||
1 if the key is in c1 and c1 is a set | |||
c1[key] if the key is in c1 and c1 is a mapping | |||
v2 is 0 if the key is not in c2 | |||
1 if the key is in c2 and c2 is a set | |||
c2[key] if the key is in c2 and c2 is a mapping | |||
Note that c1 and c2 must be collections. | |||
""" | |||
def weightedIntersection(c1, c2, weight1=1, weight2=1): | |||
"""Compute the weighted intersection of c1 and c2. | |||
If c1 and c2 are None, the output is (0, None). | |||
If c1 is None and c2 is not None, the output is (weight2, c2). | |||
If c1 is not None and c2 is None, the output is (weight1, c1). | |||
Else, and hereafter, c1 is not None and c2 is not None. | |||
If c1 and c2 are both sets, the output is the sum of the weights | |||
and the (unweighted) intersection of the sets. | |||
Else the output is 1 and a Bucket whose keys are the intersection of | |||
c1 and c2's keys, and whose values are:: | |||
v1*weight1 + v2*weight2 | |||
where: | |||
v1 is 1 if c1 is a set | |||
c1[key] if c1 is a mapping | |||
v2 is 1 if c2 is a set | |||
c2[key] if c2 is a mapping | |||
Note that c1 and c2 must be collections. | |||
""" | |||
class IMergeIntegerKey(IMerge): | |||
"""IMerge-able objects with integer keys. | |||
Concretely, this means the types in IOBTree and IIBTree. | |||
""" | |||
def multiunion(seq): | |||
"""Return union of (zero or more) integer sets, as an integer set. | |||
seq is a sequence of objects each convertible to an integer set. | |||
These objects are convertible to an integer set: | |||
+ An integer, which is added to the union. | |||
+ A Set or TreeSet from the same module (for example, an | |||
IIBTree.TreeSet for IIBTree.multiunion()). The elements of the | |||
set are added to the union. | |||
+ A Bucket or BTree from the same module (for example, an | |||
IOBTree.IOBTree for IOBTree.multiunion()). The keys of the | |||
mapping are added to the union. | |||
The union is returned as a Set from the same module (for example, | |||
IIBTree.multiunion() returns an IIBTree.IISet). | |||
The point to this method is that it can run much faster than | |||
doing a sequence of two-input union() calls. Under the covers, | |||
all the integers in all the inputs are sorted via a single | |||
linear-time radix sort, then duplicates are removed in a second | |||
linear-time pass. | |||
""" | |||
class IBTreeFamily(Interface): | |||
"""the 64-bit or 32-bit family""" | |||
IO = Attribute('The IIntegerObjectBTreeModule for this family') | |||
OI = Attribute('The IObjectIntegerBTreeModule for this family') | |||
II = Attribute('The IIntegerIntegerBTreeModule for this family') | |||
IF = Attribute('The IIntegerFloatBTreeModule for this family') | |||
OO = Attribute('The IObjectObjectBTreeModule for this family') | |||
maxint = Attribute('The maximum integer storable in this family') | |||
minint = Attribute('The minimum integer storable in this family') | |||
class IIntegerObjectBTreeModule(IBTreeModule, IMerge): | |||
"""keys, or set values, are integers; values are objects. | |||
describes IOBTree and LOBTree""" | |||
family = Attribute('The IBTreeFamily of this module') | |||
class IObjectIntegerBTreeModule(IBTreeModule, IIMerge): | |||
"""keys, or set values, are objects; values are integers. | |||
Object keys (and set values) must sort reliably (for instance, *not* on | |||
object id)! Homogenous key types recommended. | |||
describes OIBTree and LOBTree""" | |||
family = Attribute('The IBTreeFamily of this module') | |||
class IIntegerIntegerBTreeModule(IBTreeModule, IIMerge, IMergeIntegerKey): | |||
"""keys, or set values, are integers; values are also integers. | |||
describes IIBTree and LLBTree""" | |||
family = Attribute('The IBTreeFamily of this module') | |||
class IObjectObjectBTreeModule(IBTreeModule, IMerge): | |||
"""keys, or set values, are objects; values are also objects. | |||
Object keys (and set values) must sort reliably (for instance, *not* on | |||
object id)! Homogenous key types recommended. | |||
describes OOBTree""" | |||
# Note that there's no ``family`` attribute; all families include | |||
# the OO flavor of BTrees. | |||
class IIntegerFloatBTreeModule(IBTreeModule, IMerge): | |||
"""keys, or set values, are integers; values are floats. | |||
describes IFBTree and LFBTree""" | |||
family = Attribute('The IBTreeFamily of this module') | |||
try: | |||
from ZODB.POSException import BTreesConflictError | |||
except ImportError: | |||
class BTreesConflictError(ValueError): | |||
@property | |||
def reason(self): | |||
return self.args[-1] | |||
############################################################### | |||
# IMPORTANT NOTE | |||
# | |||
# Getting the length of a BTree, TreeSet, or output of keys, | |||
# values, or items of same is expensive. If you need to get the | |||
# length, you need to maintain this separately. | |||
# | |||
# Eventually, I need to express this through the interfaces. | |||
# | |||
################################################################ |
@@ -0,0 +1,113 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2001-2012 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE | |||
# | |||
############################################################################## | |||
__all__ = ('Bucket', 'Set', 'BTree', 'TreeSet', | |||
'LFBucket', 'LFSet', 'LFBTree', 'LFTreeSet', | |||
'union', 'intersection', 'difference', | |||
'weightedUnion', 'weightedIntersection', 'multiunion', | |||
) | |||
from zope.interface import moduleProvides | |||
from .Interfaces import IIntegerFloatBTreeModule | |||
from ._base import Bucket | |||
from ._base import MERGE | |||
from ._base import MERGE_WEIGHT_numeric | |||
from ._base import MERGE_DEFAULT_float | |||
from ._base import Set | |||
from ._base import Tree as BTree | |||
from ._base import TreeSet | |||
from ._base import _TreeIterator | |||
from ._base import difference as _difference | |||
from ._base import intersection as _intersection | |||
from ._base import multiunion as _multiunion | |||
from ._base import set_operation as _set_operation | |||
from ._base import to_long as _to_key | |||
from ._base import to_float as _to_value | |||
from ._base import union as _union | |||
from ._base import weightedIntersection as _weightedIntersection | |||
from ._base import weightedUnion as _weightedUnion | |||
from ._base import _fix_pickle | |||
from ._compat import import_c_extension | |||
_BUCKET_SIZE = 120 | |||
_TREE_SIZE = 500 | |||
using64bits = True | |||
class LFBucketPy(Bucket): | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class LFSetPy(Set): | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class LFBTreePy(BTree): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class LFTreeSetPy(TreeSet): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class LFTreeIteratorPy(_TreeIterator): | |||
pass | |||
# Can't declare forward refs, so fix up afterwards: | |||
LFBucketPy._mapping_type = LFBucketPy._bucket_type = LFBucketPy | |||
LFBucketPy._set_type = LFSetPy | |||
LFSetPy._mapping_type = LFBucketPy | |||
LFSetPy._set_type = LFSetPy._bucket_type = LFSetPy | |||
LFBTreePy._mapping_type = LFBTreePy._bucket_type = LFBucketPy | |||
LFBTreePy._set_type = LFSetPy | |||
LFTreeSetPy._mapping_type = LFBucketPy | |||
LFTreeSetPy._set_type = LFTreeSetPy._bucket_type = LFSetPy | |||
differencePy = _set_operation(_difference, LFSetPy) | |||
unionPy = _set_operation(_union, LFSetPy) | |||
intersectionPy = _set_operation(_intersection, LFSetPy) | |||
multiunionPy = _set_operation(_multiunion, LFSetPy) | |||
weightedUnionPy = _set_operation(_weightedUnion, LFSetPy) | |||
weightedIntersectionPy = _set_operation(_weightedIntersection, LFSetPy) | |||
import_c_extension(globals()) | |||
_fix_pickle(globals(), __name__) | |||
moduleProvides(IIntegerFloatBTreeModule) |
@@ -0,0 +1,113 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2001-2012 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE | |||
# | |||
############################################################################## | |||
__all__ = ('Bucket', 'Set', 'BTree', 'TreeSet', | |||
'LLBucket', 'LLSet', 'LLBTree', 'LLTreeSet', | |||
'union', 'intersection', 'difference', | |||
'weightedUnion', 'weightedIntersection', 'multiunion', | |||
) | |||
from zope.interface import moduleProvides | |||
from .Interfaces import IIntegerIntegerBTreeModule | |||
from ._base import Bucket | |||
from ._base import MERGE | |||
from ._base import MERGE_WEIGHT_numeric | |||
from ._base import MERGE_DEFAULT_int | |||
from ._base import Set | |||
from ._base import Tree as BTree | |||
from ._base import TreeSet | |||
from ._base import _TreeIterator | |||
from ._base import difference as _difference | |||
from ._base import intersection as _intersection | |||
from ._base import multiunion as _multiunion | |||
from ._base import set_operation as _set_operation | |||
from ._base import to_long as _to_key | |||
from ._base import to_long as _to_value | |||
from ._base import union as _union | |||
from ._base import weightedIntersection as _weightedIntersection | |||
from ._base import weightedUnion as _weightedUnion | |||
from ._base import _fix_pickle | |||
from ._compat import import_c_extension | |||
_BUCKET_SIZE = 120 | |||
_TREE_SIZE = 500 | |||
using64bits = True | |||
class LLBucketPy(Bucket): | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class LLSetPy(Set): | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class LLBTreePy(BTree): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class LLTreeSetPy(TreeSet): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class LLTreeIteratorPy(_TreeIterator): | |||
pass | |||
# Can't declare forward refs, so fix up afterwards: | |||
LLBucketPy._mapping_type = LLBucketPy._bucket_type = LLBucketPy | |||
LLBucketPy._set_type = LLSetPy | |||
LLSetPy._mapping_type = LLBucketPy | |||
LLSetPy._set_type = LLSetPy._bucket_type = LLSetPy | |||
LLBTreePy._mapping_type = LLBTreePy._bucket_type = LLBucketPy | |||
LLBTreePy._set_type = LLSetPy | |||
LLTreeSetPy._mapping_type = LLBucketPy | |||
LLTreeSetPy._set_type = LLTreeSetPy._bucket_type = LLSetPy | |||
differencePy = _set_operation(_difference, LLSetPy) | |||
unionPy = _set_operation(_union, LLSetPy) | |||
intersectionPy = _set_operation(_intersection, LLSetPy) | |||
multiunionPy = _set_operation(_multiunion, LLSetPy) | |||
weightedUnionPy = _set_operation(_weightedUnion, LLSetPy) | |||
weightedIntersectionPy = _set_operation(_weightedIntersection, LLSetPy) | |||
import_c_extension(globals()) | |||
_fix_pickle(globals(), __name__) | |||
moduleProvides(IIntegerIntegerBTreeModule) |
@@ -0,0 +1,96 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2001-2012 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE | |||
# | |||
############################################################################## | |||
__all__ = ('Bucket', 'Set', 'BTree', 'TreeSet', | |||
'LOBucket', 'LOSet', 'LOBTree', 'LOTreeSet', | |||
'union', 'intersection', 'difference', 'multiunion', | |||
) | |||
from zope.interface import moduleProvides | |||
from .Interfaces import IIntegerObjectBTreeModule | |||
from ._base import Bucket | |||
from ._base import MERGE_WEIGHT_default | |||
from ._base import Set | |||
from ._base import Tree as BTree | |||
from ._base import TreeSet | |||
from ._base import _TreeIterator | |||
from ._base import difference as _difference | |||
from ._base import intersection as _intersection | |||
from ._base import multiunion as _multiunion | |||
from ._base import set_operation as _set_operation | |||
from ._base import to_long as _to_key | |||
from ._base import to_ob as _to_value | |||
from ._base import union as _union | |||
from ._base import _fix_pickle | |||
from ._compat import import_c_extension | |||
_BUCKET_SIZE = 60 | |||
_TREE_SIZE = 500 | |||
using64bits = True | |||
class LOBucketPy(Bucket): | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE_WEIGHT = MERGE_WEIGHT_default | |||
class LOSetPy(Set): | |||
_to_key = _to_key | |||
class LOBTreePy(BTree): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE_WEIGHT = MERGE_WEIGHT_default | |||
class LOTreeSetPy(TreeSet): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
class LOTreeIteratorPy(_TreeIterator): | |||
pass | |||
# Can't declare forward refs, so fix up afterwards: | |||
LOBucketPy._mapping_type = LOBucketPy._bucket_type = LOBucketPy | |||
LOBucketPy._set_type = LOSetPy | |||
LOSetPy._mapping_type = LOBucketPy | |||
LOSetPy._set_type = LOSetPy._bucket_type = LOSetPy | |||
LOBTreePy._mapping_type = LOBTreePy._bucket_type = LOBucketPy | |||
LOBTreePy._set_type = LOSetPy | |||
LOTreeSetPy._mapping_type = LOBucketPy | |||
LOTreeSetPy._set_type = LOTreeSetPy._bucket_type = LOSetPy | |||
differencePy = _set_operation(_difference, LOSetPy) | |||
unionPy = _set_operation(_union, LOSetPy) | |||
intersectionPy = _set_operation(_intersection, LOSetPy) | |||
multiunionPy = _set_operation(_multiunion, LOSetPy) | |||
import_c_extension(globals()) | |||
_fix_pickle(globals(), __name__) | |||
moduleProvides(IIntegerObjectBTreeModule) |
@@ -0,0 +1,58 @@ | |||
############################################################################## | |||
# | |||
# 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 | |||
# | |||
############################################################################## | |||
import persistent | |||
class Length(persistent.Persistent): | |||
"""BTree lengths are often too expensive to compute. | |||
Objects that use BTrees need to keep track of lengths themselves. | |||
This class provides an object for doing this. | |||
As a bonus, the object support application-level conflict | |||
resolution. | |||
It is tempting to to assign length objects to __len__ attributes | |||
to provide instance-specific __len__ methods. However, this no | |||
longer works as expected, because new-style classes cache | |||
class-defined slot methods (like __len__) in C type slots. Thus, | |||
instance-defined slot fillers are ignored. | |||
""" | |||
# class-level default required to keep copy.deepcopy happy -- see | |||
# https://bugs.launchpad.net/zodb/+bug/516653 | |||
value = 0 | |||
def __init__(self, v=0): | |||
self.value = v | |||
def __getstate__(self): | |||
return self.value | |||
def __setstate__(self, v): | |||
self.value = v | |||
def set(self, v): | |||
"Set the length value to v." | |||
self.value = v | |||
def _p_resolveConflict(self, old, s1, s2): | |||
return s1 + s2 - old | |||
def change(self, delta): | |||
"Add delta to the length value." | |||
self.value += delta | |||
def __call__(self, *args): | |||
"Return the current length value." | |||
return self.value |
@@ -0,0 +1,349 @@ | |||
/***************************************************************************** | |||
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 | |||
****************************************************************************/ | |||
#define MERGETEMPLATE_C "$Id$\n" | |||
/**************************************************************************** | |||
Set operations | |||
****************************************************************************/ | |||
static int | |||
merge_output(Bucket *r, SetIteration *i, int mapping) | |||
{ | |||
if (r->len >= r->size && Bucket_grow(r, -1, !mapping) < 0) | |||
return -1; | |||
COPY_KEY(r->keys[r->len], i->key); | |||
INCREF_KEY(r->keys[r->len]); | |||
if (mapping) { | |||
COPY_VALUE(r->values[r->len], i->value); | |||
INCREF_VALUE(r->values[r->len]); | |||
} | |||
r->len++; | |||
return 0; | |||
} | |||
/* The "reason" argument is a little integer giving "a reason" for the | |||
* error. In the Zope3 codebase, these are mapped to explanatory strings | |||
* via zodb/btrees/interfaces.py. | |||
*/ | |||
static PyObject * | |||
merge_error(int p1, int p2, int p3, int reason) | |||
{ | |||
PyObject *r; | |||
UNLESS (r=Py_BuildValue("iiii", p1, p2, p3, reason)) r=Py_None; | |||
if (ConflictError == NULL) { | |||
ConflictError = PyExc_ValueError; | |||
Py_INCREF(ConflictError); | |||
} | |||
PyErr_SetObject(ConflictError, r); | |||
if (r != Py_None) | |||
{ | |||
Py_DECREF(r); | |||
} | |||
return NULL; | |||
} | |||
/* It's hard to explain "the rules" for bucket_merge, in large part because | |||
* any automatic conflict-resolution scheme is going to be incorrect for | |||
* some endcases of *some* app. The scheme here is pretty conservative, | |||
* and should be OK for most apps. It's easier to explain what the code | |||
* allows than what it forbids: | |||
* | |||
* Leaving things alone: it's OK if both s2 and s3 leave a piece of s1 | |||
* alone (don't delete the key, and don't change the value). | |||
* | |||
* Key deletion: a transaction (s2 or s3) can delete a key (from s1), but | |||
* only if the other transaction (of s2 and s3) doesn't delete the same key. | |||
* However, it's not OK for s2 and s3 to, between them, end up deleting all | |||
* the keys. This is a higher-level constraint, due to that the caller of | |||
* bucket_merge() doesn't have enough info to unlink the resulting empty | |||
* bucket from its BTree correctly. It's also not OK if s2 or s3 are empty, | |||
* because the transaction that emptied the bucket unlinked the bucket from | |||
* the tree, and nothing we do here can get it linked back in again. | |||
* | |||
* Key insertion: s2 or s3 can add a new key, provided the other transaction | |||
* doesn't insert the same key. It's not OK even if they insert the same | |||
* <key, value> pair. | |||
* | |||
* Mapping value modification: s2 or s3 can modify the value associated | |||
* with a key in s1, provided the other transaction doesn't make a | |||
* modification of the same key to a different value. It's OK if s2 and s3 | |||
* both give the same new value to the key while it's hard to be precise about | |||
* why, this doesn't seem consistent with that it's *not* OK for both to add | |||
* a new key mapping to the same value). | |||
*/ | |||
static PyObject * | |||
bucket_merge(Bucket *s1, Bucket *s2, Bucket *s3) | |||
{ | |||
Bucket *r=0; | |||
PyObject *s; | |||
SetIteration i1 = {0,0,0}, i2 = {0,0,0}, i3 = {0,0,0}; | |||
int cmp12, cmp13, cmp23, mapping, set; | |||
/* If either "after" bucket is empty, punt. */ | |||
if (s2->len == 0 || s3->len == 0) | |||
{ | |||
merge_error(-1, -1, -1, 12); | |||
goto err; | |||
} | |||
if (initSetIteration(&i1, OBJECT(s1), 1) < 0) | |||
goto err; | |||
if (initSetIteration(&i2, OBJECT(s2), 1) < 0) | |||
goto err; | |||
if (initSetIteration(&i3, OBJECT(s3), 1) < 0) | |||
goto err; | |||
mapping = i1.usesValue | i2.usesValue | i3.usesValue; | |||
set = !mapping; | |||
if (mapping) | |||
r = (Bucket *)PyObject_CallObject((PyObject *)&BucketType, NULL); | |||
else | |||
r = (Bucket *)PyObject_CallObject((PyObject *)&SetType, NULL); | |||
if (r == NULL) | |||
goto err; | |||
if (i1.next(&i1) < 0) | |||
goto err; | |||
if (i2.next(&i2) < 0) | |||
goto err; | |||
if (i3.next(&i3) < 0) | |||
goto err; | |||
/* Consult zodb/btrees/interfaces.py for the meaning of the last | |||
* argument passed to merge_error(). | |||
*/ | |||
/* TODO: This isn't passing on errors raised by value comparisons. */ | |||
while (i1.position >= 0 && i2.position >= 0 && i3.position >= 0) | |||
{ | |||
TEST_KEY_SET_OR(cmp12, i1.key, i2.key) goto err; | |||
TEST_KEY_SET_OR(cmp13, i1.key, i3.key) goto err; | |||
if (cmp12==0) | |||
{ | |||
if (cmp13==0) | |||
{ | |||
if (set || (TEST_VALUE(i1.value, i2.value) == 0)) | |||
{ /* change in i3 value or all same */ | |||
if (merge_output(r, &i3, mapping) < 0) goto err; | |||
} | |||
else if (set || (TEST_VALUE(i1.value, i3.value) == 0)) | |||
{ /* change in i2 value */ | |||
if (merge_output(r, &i2, mapping) < 0) goto err; | |||
} | |||
else | |||
{ /* conflicting value changes in i2 and i3 */ | |||
merge_error(i1.position, i2.position, i3.position, 1); | |||
goto err; | |||
} | |||
if (i1.next(&i1) < 0) goto err; | |||
if (i2.next(&i2) < 0) goto err; | |||
if (i3.next(&i3) < 0) goto err; | |||
} | |||
else if (cmp13 > 0) | |||
{ /* insert i3 */ | |||
if (merge_output(r, &i3, mapping) < 0) goto err; | |||
if (i3.next(&i3) < 0) goto err; | |||
} | |||
else if (set || (TEST_VALUE(i1.value, i2.value) == 0)) | |||
{ /* deleted in i3 */ | |||
if (i3.position == 1) | |||
{ | |||
/* Deleted the first item. This will modify the | |||
parent node, so we don't know if merging will be | |||
safe | |||
*/ | |||
merge_error(i1.position, i2.position, i3.position, 13); | |||
goto err; | |||
} | |||
if (i1.next(&i1) < 0) goto err; | |||
if (i2.next(&i2) < 0) goto err; | |||
} | |||
else | |||
{ /* conflicting del in i3 and change in i2 */ | |||
merge_error(i1.position, i2.position, i3.position, 2); | |||
goto err; | |||
} | |||
} | |||
else if (cmp13 == 0) | |||
{ | |||
if (cmp12 > 0) | |||
{ /* insert i2 */ | |||
if (merge_output(r, &i2, mapping) < 0) goto err; | |||
if (i2.next(&i2) < 0) goto err; | |||
} | |||
else if (set || (TEST_VALUE(i1.value, i3.value) == 0)) | |||
{ /* deleted in i2 */ | |||
if (i2.position == 1) | |||
{ | |||
/* Deleted the first item. This will modify the | |||
parent node, so we don't know if merging will be | |||
safe | |||
*/ | |||
merge_error(i1.position, i2.position, i3.position, 13); | |||
goto err; | |||
} | |||
if (i1.next(&i1) < 0) goto err; | |||
if (i3.next(&i3) < 0) goto err; | |||
} | |||
else | |||
{ /* conflicting del in i2 and change in i3 */ | |||
merge_error(i1.position, i2.position, i3.position, 3); | |||
goto err; | |||
} | |||
} | |||
else | |||
{ /* Both keys changed */ | |||
TEST_KEY_SET_OR(cmp23, i2.key, i3.key) goto err; | |||
if (cmp23==0) | |||
{ /* dueling inserts or deletes */ | |||
merge_error(i1.position, i2.position, i3.position, 4); | |||
goto err; | |||
} | |||
if (cmp12 > 0) | |||
{ /* insert i2 */ | |||
if (cmp23 > 0) | |||
{ /* insert i3 first */ | |||
if (merge_output(r, &i3, mapping) < 0) goto err; | |||
if (i3.next(&i3) < 0) goto err; | |||
} | |||
else | |||
{ /* insert i2 first */ | |||
if (merge_output(r, &i2, mapping) < 0) goto err; | |||
if (i2.next(&i2) < 0) goto err; | |||
} | |||
} | |||
else if (cmp13 > 0) | |||
{ /* Insert i3 */ | |||
if (merge_output(r, &i3, mapping) < 0) goto err; | |||
if (i3.next(&i3) < 0) goto err; | |||
} | |||
else | |||
{ /* 1<2 and 1<3: both deleted 1.key */ | |||
merge_error(i1.position, i2.position, i3.position, 5); | |||
goto err; | |||
} | |||
} | |||
} | |||
while (i2.position >= 0 && i3.position >= 0) | |||
{ /* New inserts */ | |||
TEST_KEY_SET_OR(cmp23, i2.key, i3.key) goto err; | |||
if (cmp23==0) | |||
{ /* dueling inserts */ | |||
merge_error(i1.position, i2.position, i3.position, 6); | |||
goto err; | |||
} | |||
if (cmp23 > 0) | |||
{ /* insert i3 */ | |||
if (merge_output(r, &i3, mapping) < 0) goto err; | |||
if (i3.next(&i3) < 0) goto err; | |||
} | |||
else | |||
{ /* insert i2 */ | |||
if (merge_output(r, &i2, mapping) < 0) goto err; | |||
if (i2.next(&i2) < 0) goto err; | |||
} | |||
} | |||
while (i1.position >= 0 && i2.position >= 0) | |||
{ /* remainder of i1 deleted in i3 */ | |||
TEST_KEY_SET_OR(cmp12, i1.key, i2.key) goto err; | |||
if (cmp12 > 0) | |||
{ /* insert i2 */ | |||
if (merge_output(r, &i2, mapping) < 0) goto err; | |||
if (i2.next(&i2) < 0) goto err; | |||
} | |||
else if (cmp12==0 && (set || (TEST_VALUE(i1.value, i2.value) == 0))) | |||
{ /* delete i3 */ | |||
if (i1.next(&i1) < 0) goto err; | |||
if (i2.next(&i2) < 0) goto err; | |||
} | |||
else | |||
{ /* Dueling deletes or delete and change */ | |||
merge_error(i1.position, i2.position, i3.position, 7); | |||
goto err; | |||
} | |||
} | |||
while (i1.position >= 0 && i3.position >= 0) | |||
{ /* remainder of i1 deleted in i2 */ | |||
TEST_KEY_SET_OR(cmp13, i1.key, i3.key) goto err; | |||
if (cmp13 > 0) | |||
{ /* insert i3 */ | |||
if (merge_output(r, &i3, mapping) < 0) goto err; | |||
if (i3.next(&i3) < 0) goto err; | |||
} | |||
else if (cmp13==0 && (set || (TEST_VALUE(i1.value, i3.value) == 0))) | |||
{ /* delete i2 */ | |||
if (i1.next(&i1) < 0) goto err; | |||
if (i3.next(&i3) < 0) goto err; | |||
} | |||
else | |||
{ /* Dueling deletes or delete and change */ | |||
merge_error(i1.position, i2.position, i3.position, 8); | |||
goto err; | |||
} | |||
} | |||
if (i1.position >= 0) | |||
{ /* Dueling deletes */ | |||
merge_error(i1.position, i2.position, i3.position, 9); | |||
goto err; | |||
} | |||
while (i2.position >= 0) | |||
{ /* Inserting i2 at end */ | |||
if (merge_output(r, &i2, mapping) < 0) goto err; | |||
if (i2.next(&i2) < 0) goto err; | |||
} | |||
while (i3.position >= 0) | |||
{ /* Inserting i3 at end */ | |||
if (merge_output(r, &i3, mapping) < 0) goto err; | |||
if (i3.next(&i3) < 0) goto err; | |||
} | |||
/* If the output bucket is empty, conflict resolution doesn't have | |||
* enough info to unlink it from its containing BTree correctly. | |||
*/ | |||
if (r->len == 0) | |||
{ | |||
merge_error(-1, -1, -1, 10); | |||
goto err; | |||
} | |||
finiSetIteration(&i1); | |||
finiSetIteration(&i2); | |||
finiSetIteration(&i3); | |||
if (s1->next) | |||
{ | |||
Py_INCREF(s1->next); | |||
r->next = s1->next; | |||
} | |||
s = bucket_getstate(r); | |||
Py_DECREF(r); | |||
return s; | |||
err: | |||
finiSetIteration(&i1); | |||
finiSetIteration(&i2); | |||
finiSetIteration(&i3); | |||
Py_XDECREF(r); | |||
return NULL; | |||
} |
@@ -0,0 +1,110 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2001-2012 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE | |||
# | |||
############################################################################## | |||
__all__ = ('Bucket', 'Set', 'BTree', 'TreeSet', | |||
'OIBucket', 'OISet', 'OIBTree', 'OITreeSet', | |||
'union', 'intersection', 'difference', | |||
'weightedUnion', 'weightedIntersection', | |||
) | |||
from zope.interface import moduleProvides | |||
from .Interfaces import IObjectIntegerBTreeModule | |||
from ._base import Bucket | |||
from ._base import MERGE | |||
from ._base import MERGE_WEIGHT_numeric | |||
from ._base import MERGE_DEFAULT_float | |||
from ._base import Set | |||
from ._base import Tree as BTree | |||
from ._base import TreeSet | |||
from ._base import _TreeIterator | |||
from ._base import difference as _difference | |||
from ._base import intersection as _intersection | |||
from ._base import set_operation as _set_operation | |||
from ._base import to_ob as _to_key | |||
from ._base import to_int as _to_value | |||
from ._base import union as _union | |||
from ._base import weightedIntersection as _weightedIntersection | |||
from ._base import weightedUnion as _weightedUnion | |||
from ._base import _fix_pickle | |||
from ._compat import import_c_extension | |||
_BUCKET_SIZE = 60 | |||
_TREE_SIZE = 250 | |||
using64bits = True | |||
class OIBucketPy(Bucket): | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class OISetPy(Set): | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class OIBTreePy(BTree): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class OITreeSetPy(TreeSet): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_float | |||
class OITreeIteratorPy(_TreeIterator): | |||
pass | |||
# Can't declare forward refs, so fix up afterwards: | |||
OIBucketPy._mapping_type = OIBucketPy._bucket_type = OIBucketPy | |||
OIBucketPy._set_type = OISetPy | |||
OISetPy._mapping_type = OIBucketPy | |||
OISetPy._set_type = OISetPy._bucket_type = OISetPy | |||
OIBTreePy._mapping_type = OIBTreePy._bucket_type = OIBucketPy | |||
OIBTreePy._set_type = OISetPy | |||
OITreeSetPy._mapping_type = OIBucketPy | |||
OITreeSetPy._set_type = OITreeSetPy._bucket_type = OISetPy | |||
differencePy = _set_operation(_difference, OISetPy) | |||
unionPy = _set_operation(_union, OISetPy) | |||
intersectionPy = _set_operation(_intersection, OISetPy) | |||
weightedUnionPy = _set_operation(_weightedUnion, OISetPy) | |||
weightedIntersectionPy = _set_operation(_weightedIntersection, OISetPy) | |||
import_c_extension(globals()) | |||
_fix_pickle(globals(), __name__) | |||
moduleProvides(IObjectIntegerBTreeModule) |
@@ -0,0 +1,111 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2001-2012 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE | |||
# | |||
############################################################################## | |||
__all__ = ('Bucket', 'Set', 'BTree', 'TreeSet', | |||
'OLBucket', 'OLSet', 'OLBTree', 'OLTreeSet', | |||
'union', 'intersection', 'difference', | |||
'weightedUnion', 'weightedIntersection', | |||
) | |||
from zope.interface import moduleProvides | |||
from .Interfaces import IObjectIntegerBTreeModule | |||
from ._base import Bucket | |||
from ._base import MERGE | |||
from ._base import MERGE_WEIGHT_numeric | |||
from ._base import MERGE_DEFAULT_int | |||
from ._base import Set | |||
from ._base import Tree as BTree | |||
from ._base import TreeSet | |||
from ._base import _TreeIterator | |||
from ._base import difference as _difference | |||
from ._base import intersection as _intersection | |||
from ._base import set_operation as _set_operation | |||
from ._base import to_ob as _to_key | |||
from ._base import to_long as _to_value | |||
from ._base import union as _union | |||
from ._base import weightedIntersection as _weightedIntersection | |||
from ._base import weightedUnion as _weightedUnion | |||
from ._base import _fix_pickle | |||
from ._compat import import_c_extension | |||
_BUCKET_SIZE = 60 | |||
_TREE_SIZE = 250 | |||
using64bits = True | |||
class OLBucketPy(Bucket): | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class OLSetPy(Set): | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class OLBTreePy(BTree): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class OLTreeSetPy(TreeSet): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
MERGE = MERGE | |||
MERGE_WEIGHT = MERGE_WEIGHT_numeric | |||
MERGE_DEFAULT = MERGE_DEFAULT_int | |||
class OLTreeIteratorPy(_TreeIterator): | |||
pass | |||
# Can't declare forward refs, so fix up afterwards: | |||
OLBucketPy._mapping_type = OLBucketPy._bucket_type = OLBucketPy | |||
OLBucketPy._set_type = OLSetPy | |||
OLSetPy._mapping_type = OLBucketPy | |||
OLSetPy._set_type = OLSetPy._bucket_type = OLSetPy | |||
OLBTreePy._mapping_type = OLBTreePy._bucket_type = OLBucketPy | |||
OLBTreePy._set_type = OLSetPy | |||
OLTreeSetPy._mapping_type = OLBucketPy | |||
OLTreeSetPy._set_type = OLTreeSetPy._bucket_type = OLSetPy | |||
differencePy = _set_operation(_difference, OLSetPy) | |||
unionPy = _set_operation(_union, OLSetPy) | |||
intersectionPy = _set_operation(_intersection, OLSetPy) | |||
weightedUnionPy = _set_operation(_weightedUnion, OLSetPy) | |||
weightedIntersectionPy = _set_operation(_weightedIntersection, OLSetPy) | |||
import_c_extension(globals()) | |||
_fix_pickle(globals(), __name__) | |||
moduleProvides(IObjectIntegerBTreeModule) |
@@ -0,0 +1,91 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2001-2012 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE | |||
# | |||
############################################################################## | |||
__all__ = ('Bucket', 'Set', 'BTree', 'TreeSet', | |||
'OOBucket', 'OOSet', 'OOBTree', 'OOTreeSet', | |||
'union', 'intersection','difference', | |||
) | |||
from zope.interface import moduleProvides | |||
from .Interfaces import IObjectObjectBTreeModule | |||
from ._base import Bucket | |||
from ._base import Set | |||
from ._base import Tree as BTree | |||
from ._base import TreeSet | |||
from ._base import _TreeIterator | |||
from ._base import difference as _difference | |||
from ._base import intersection as _intersection | |||
from ._base import set_operation as _set_operation | |||
from ._base import to_ob as _to_key | |||
_to_value = _to_key | |||
from ._base import union as _union | |||
from ._base import _fix_pickle | |||
from ._compat import import_c_extension | |||
_BUCKET_SIZE = 30 | |||
_TREE_SIZE = 250 | |||
using64bits = False | |||
class OOBucketPy(Bucket): | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
class OOSetPy(Set): | |||
_to_key = _to_key | |||
class OOBTreePy(BTree): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
_to_value = _to_value | |||
class OOTreeSetPy(TreeSet): | |||
max_leaf_size = _BUCKET_SIZE | |||
max_internal_size = _TREE_SIZE | |||
_to_key = _to_key | |||
class OOTreeIteratorPy(_TreeIterator): | |||
pass | |||
# Can't declare forward refs, so fix up afterwards: | |||
OOBucketPy._mapping_type = OOBucketPy._bucket_type = OOBucketPy | |||
OOBucketPy._set_type = OOSetPy | |||
OOSetPy._mapping_type = OOBucketPy | |||
OOSetPy._set_type = OOSetPy._bucket_type = OOSetPy | |||
OOBTreePy._mapping_type = OOBTreePy._bucket_type = OOBucketPy | |||
OOBTreePy._set_type = OOSetPy | |||
OOTreeSetPy._mapping_type = OOBucketPy | |||
OOTreeSetPy._set_type = OOTreeSetPy._bucket_type = OOSetPy | |||
differencePy = _set_operation(_difference, OOSetPy) | |||
unionPy = _set_operation(_union, OOSetPy) | |||
intersectionPy = _set_operation(_intersection, OOSetPy) | |||
import_c_extension(globals()) | |||
_fix_pickle(globals(), __name__) | |||
moduleProvides(IObjectObjectBTreeModule) |
@@ -0,0 +1,557 @@ | |||
/***************************************************************************** | |||
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 | |||
****************************************************************************/ | |||
/**************************************************************************** | |||
Set operations | |||
****************************************************************************/ | |||
#define SETOPTEMPLATE_C "$Id$\n" | |||
#ifdef KEY_CHECK | |||
static int | |||
nextKeyAsSet(SetIteration *i) | |||
{ | |||
if (i->position >= 0) { | |||
if (i->position) { | |||
DECREF_KEY(i->key); | |||
i->position = -1; | |||
} | |||
else | |||
i->position = 1; | |||
} | |||
return 0; | |||
} | |||
#endif | |||
/* initSetIteration | |||
* | |||
* Start the set iteration protocol. See the comments at struct SetIteration. | |||
* | |||
* Arguments | |||
* i The address of a SetIteration control struct. | |||
* s The address of the set, bucket, BTree, ..., to be iterated. | |||
* useValues Boolean; if true, and s has values (is a mapping), copy | |||
* them into i->value each time i->next() is called; else | |||
* ignore s's values even if s is a mapping. | |||
* | |||
* Return | |||
* 0 on success; -1 and an exception set if error. | |||
* i.usesValue is set to 1 (true) if s has values and useValues was | |||
* true; else usesValue is set to 0 (false). | |||
* i.set gets a new reference to s, or to some other object used to | |||
* iterate over s. | |||
* i.position is set to 0. | |||
* i.next is set to an appropriate iteration function. | |||
* i.key and i.value are left alone. | |||
* | |||
* Internal | |||
* i.position < 0 means iteration terminated. | |||
* i.position = 0 means iteration hasn't yet begun (next() hasn't | |||
* been called yet). | |||
* In all other cases, i.key, and possibly i.value, own references. | |||
* These must be cleaned up, either by next() routines, or by | |||
* finiSetIteration. | |||
* next() routines must ensure the above. They should return without | |||
* doing anything when i.position < 0. | |||
* It's the responsibility of {init, fini}setIteration to clean up | |||
* the reference in i.set, and to ensure that no stale references | |||
* live in i.key or i.value if iteration terminates abnormally. | |||
* A SetIteration struct has been cleaned up iff i.set is NULL. | |||
*/ | |||
static int | |||
initSetIteration(SetIteration *i, PyObject *s, int useValues) | |||
{ | |||
i->set = NULL; | |||
i->position = -1; /* set to 0 only on normal return */ | |||
i->usesValue = 0; /* assume it's a set or that values aren't iterated */ | |||
if (PyObject_IsInstance(s, (PyObject *)&BucketType)) | |||
{ | |||
i->set = s; | |||
Py_INCREF(s); | |||
if (useValues) | |||
{ | |||
i->usesValue = 1; | |||
i->next = nextBucket; | |||
} | |||
else | |||
i->next = nextSet; | |||
} | |||
else if (PyObject_IsInstance(s, (PyObject *)&SetType)) | |||
{ | |||
i->set = s; | |||
Py_INCREF(s); | |||
i->next = nextSet; | |||
} | |||
else if (PyObject_IsInstance(s, (PyObject *)&BTreeType)) | |||
{ | |||
i->set = BTree_rangeSearch(BTREE(s), NULL, NULL, 'i'); | |||
UNLESS(i->set) return -1; | |||
if (useValues) | |||
{ | |||
i->usesValue = 1; | |||
i->next = nextBTreeItems; | |||
} | |||
else | |||
i->next = nextTreeSetItems; | |||
} | |||
else if (PyObject_IsInstance(s, (PyObject *)&TreeSetType)) | |||
{ | |||
i->set = BTree_rangeSearch(BTREE(s), NULL, NULL, 'k'); | |||
UNLESS(i->set) return -1; | |||
i->next = nextTreeSetItems; | |||
} | |||
#ifdef KEY_CHECK | |||
else if (KEY_CHECK(s)) | |||
{ | |||
int copied = 1; | |||
COPY_KEY_FROM_ARG(i->key, s, copied); | |||
UNLESS (copied) return -1; | |||
INCREF_KEY(i->key); | |||
i->set = s; | |||
Py_INCREF(s); | |||
i->next = nextKeyAsSet; | |||
} | |||
#endif | |||
else | |||
{ | |||
PyErr_SetString(PyExc_TypeError, "invalid argument"); | |||
return -1; | |||
} | |||
i->position = 0; | |||
return 0; | |||
} | |||
#ifndef MERGE_WEIGHT | |||
#define MERGE_WEIGHT(O, w) (O) | |||
#endif | |||
static int | |||
copyRemaining(Bucket *r, SetIteration *i, int merge, | |||
/* See comment # 42 */ | |||
#ifdef MERGE | |||
VALUE_TYPE w) | |||
#else | |||
int w) | |||
#endif | |||
{ | |||
while (i->position >= 0) | |||
{ | |||
if(r->len >= r->size && Bucket_grow(r, -1, ! merge) < 0) return -1; | |||
COPY_KEY(r->keys[r->len], i->key); | |||
INCREF_KEY(r->keys[r->len]); | |||
if (merge) | |||
{ | |||
COPY_VALUE(r->values[r->len], MERGE_WEIGHT(i->value, w)); | |||
INCREF_VALUE(r->values[r->len]); | |||
} | |||
r->len++; | |||
if (i->next(i) < 0) return -1; | |||
} | |||
return 0; | |||
} | |||
/* This is the workhorse for all set merge operations: the weighted and | |||
* unweighted flavors of union and intersection, and set difference. The | |||
* algorithm is conceptually simple but the code is complicated due to all | |||
* the options. | |||
* | |||
* s1, s2 | |||
* The input collections to be merged. | |||
* | |||
* usevalues1, usevalues2 | |||
* Booleans. In the output, should values from s1 (or s2) be used? This | |||
* only makes sense when an operation intends to support mapping outputs; | |||
* these should both be false for operations that want pure set outputs. | |||
* | |||
* w1, w2 | |||
* If usevalues1(2) are true, these are the weights to apply to the | |||
* input values. | |||
* | |||
* c1 | |||
* Boolean. Should keys that appear in c1 but not c2 appear in the output? | |||
* c12 | |||
* Boolean. Should keys that appear in both inputs appear in the output? | |||
* c2 | |||
* Boolean. Should keys that appear in c2 but not c1 appear in the output? | |||
* | |||
* Returns NULL if error, else a Set or Bucket, depending on whether a set or | |||
* mapping was requested. | |||
*/ | |||
static PyObject * | |||
set_operation(PyObject *s1, PyObject *s2, | |||
int usevalues1, int usevalues2, | |||
/* Comment # 42 | |||
The following ifdef works around a template/type problem | |||
Weights are passed as integers. In particular, the weight passed by | |||
difference is one. This works fine in the int value and float value | |||
cases but makes no sense in the object value case. In the object | |||
value case, we don't do merging, so we don't use the weights, so it | |||
doesn't matter what they are. | |||
*/ | |||
#ifdef MERGE | |||
VALUE_TYPE w1, VALUE_TYPE w2, | |||
#else | |||
int w1, int w2, | |||
#endif | |||
int c1, int c12, int c2) | |||
{ | |||
Bucket *r=0; | |||
SetIteration i1 = {0,0,0}, i2 = {0,0,0}; | |||
int cmp, merge; | |||
if (initSetIteration(&i1, s1, usevalues1) < 0) goto err; | |||
if (initSetIteration(&i2, s2, usevalues2) < 0) goto err; | |||
merge = i1.usesValue | i2.usesValue; | |||
if (merge) | |||
{ | |||
#ifndef MERGE | |||
if (c12 && i1.usesValue && i2.usesValue) goto invalid_set_operation; | |||
#endif | |||
if (! i1.usesValue&& i2.usesValue) | |||
{ | |||
SetIteration t; | |||
int i; | |||
/* See comment # 42 above */ | |||
#ifdef MERGE | |||
VALUE_TYPE v; | |||
#else | |||
int v; | |||
#endif | |||
t=i1; i1=i2; i2=t; | |||
i=c1; c1=c2; c2=i; | |||
v=w1; w1=w2; w2=v; | |||
} | |||
#ifdef MERGE_DEFAULT | |||
i1.value=MERGE_DEFAULT; | |||
i2.value=MERGE_DEFAULT; | |||
#else | |||
if (i1.usesValue) | |||
{ | |||
if (! i2.usesValue && c2) goto invalid_set_operation; | |||
} | |||
else | |||
{ | |||
if (c1 || c12) goto invalid_set_operation; | |||
} | |||
#endif | |||
UNLESS(r=BUCKET(PyObject_CallObject(OBJECT(&BucketType), NULL))) | |||
goto err; | |||
} | |||
else | |||
{ | |||
UNLESS(r=BUCKET(PyObject_CallObject(OBJECT(&SetType), NULL))) | |||
goto err; | |||
} | |||
if (i1.next(&i1) < 0) goto err; | |||
if (i2.next(&i2) < 0) goto err; | |||
while (i1.position >= 0 && i2.position >= 0) | |||
{ | |||
TEST_KEY_SET_OR(cmp, i1.key, i2.key) goto err; | |||
if(cmp < 0) | |||
{ | |||
if(c1) | |||
{ | |||
if(r->len >= r->size && Bucket_grow(r, -1, ! merge) < 0) goto err; | |||
COPY_KEY(r->keys[r->len], i1.key); | |||
INCREF_KEY(r->keys[r->len]); | |||
if (merge) | |||
{ | |||
COPY_VALUE(r->values[r->len], MERGE_WEIGHT(i1.value, w1)); | |||
INCREF_VALUE(r->values[r->len]); | |||
} | |||
r->len++; | |||
} | |||
if (i1.next(&i1) < 0) goto err; | |||
} | |||
else if(cmp==0) | |||
{ | |||
if(c12) | |||
{ | |||
if(r->len >= r->size && Bucket_grow(r, -1, ! merge) < 0) goto err; | |||
COPY_KEY(r->keys[r->len], i1.key); | |||
INCREF_KEY(r->keys[r->len]); | |||
if (merge) | |||
{ | |||
#ifdef MERGE | |||
r->values[r->len] = MERGE(i1.value, w1, i2.value, w2); | |||
#else | |||
COPY_VALUE(r->values[r->len], i1.value); | |||
INCREF_VALUE(r->values[r->len]); | |||
#endif | |||
} | |||
r->len++; | |||
} | |||
if (i1.next(&i1) < 0) goto err; | |||
if (i2.next(&i2) < 0) goto err; | |||
} | |||
else | |||
{ | |||
if(c2) | |||
{ | |||
if(r->len >= r->size && Bucket_grow(r, -1, ! merge) < 0) goto err; | |||
COPY_KEY(r->keys[r->len], i2.key); | |||
INCREF_KEY(r->keys[r->len]); | |||
if (merge) | |||
{ | |||
COPY_VALUE(r->values[r->len], MERGE_WEIGHT(i2.value, w2)); | |||
INCREF_VALUE(r->values[r->len]); | |||
} | |||
r->len++; | |||
} | |||
if (i2.next(&i2) < 0) goto err; | |||
} | |||
} | |||
if(c1 && copyRemaining(r, &i1, merge, w1) < 0) goto err; | |||
if(c2 && copyRemaining(r, &i2, merge, w2) < 0) goto err; | |||
finiSetIteration(&i1); | |||
finiSetIteration(&i2); | |||
return OBJECT(r); | |||
#ifndef MERGE_DEFAULT | |||
invalid_set_operation: | |||
PyErr_SetString(PyExc_TypeError, "invalid set operation"); | |||
#endif | |||
err: | |||
finiSetIteration(&i1); | |||
finiSetIteration(&i2); | |||
Py_XDECREF(r); | |||
return NULL; | |||
} | |||
static PyObject * | |||
difference_m(PyObject *ignored, PyObject *args) | |||
{ | |||
PyObject *o1, *o2; | |||
UNLESS(PyArg_ParseTuple(args, "OO", &o1, &o2)) return NULL; | |||
if (o1 == Py_None || o2 == Py_None) | |||
{ | |||
/* difference(None, X) -> None; difference(X, None) -> X */ | |||
Py_INCREF(o1); | |||
return o1; | |||
} | |||
return set_operation(o1, o2, 1, 0, /* preserve values from o1, ignore o2's */ | |||
1, 0, /* o1's values multiplied by 1 */ | |||
1, 0, 0); /* take only keys unique to o1 */ | |||
} | |||
static PyObject * | |||
union_m(PyObject *ignored, PyObject *args) | |||
{ | |||
PyObject *o1, *o2; | |||
UNLESS(PyArg_ParseTuple(args, "OO", &o1, &o2)) return NULL; | |||
if (o1 == Py_None) | |||
{ | |||
Py_INCREF(o2); | |||
return o2; | |||
} | |||
else if (o2 == Py_None) | |||
{ | |||
Py_INCREF(o1); | |||
return o1; | |||
} | |||
return set_operation(o1, o2, 0, 0, /* ignore values in both */ | |||
1, 1, /* the weights are irrelevant */ | |||
1, 1, 1); /* take all keys */ | |||
} | |||
static PyObject * | |||
intersection_m(PyObject *ignored, PyObject *args) | |||
{ | |||
PyObject *o1, *o2; | |||
UNLESS(PyArg_ParseTuple(args, "OO", &o1, &o2)) return NULL; | |||
if (o1 == Py_None) | |||
{ | |||
Py_INCREF(o2); | |||
return o2; | |||
} | |||
else if (o2 == Py_None) | |||
{ | |||
Py_INCREF(o1); | |||
return o1; | |||
} | |||
return set_operation(o1, o2, 0, 0, /* ignore values in both */ | |||
1, 1, /* the weights are irrelevant */ | |||
0, 1, 0); /* take only keys common to both */ | |||
} | |||
#ifdef MERGE | |||
static PyObject * | |||
wunion_m(PyObject *ignored, PyObject *args) | |||
{ | |||
PyObject *o1, *o2; | |||
VALUE_TYPE w1 = 1, w2 = 1; | |||
UNLESS(PyArg_ParseTuple(args, "OO|" VALUE_PARSE VALUE_PARSE, | |||
&o1, &o2, &w1, &w2) | |||
) return NULL; | |||
if (o1 == Py_None) | |||
return Py_BuildValue(VALUE_PARSE "O", (o2 == Py_None ? 0 : w2), o2); | |||
else if (o2 == Py_None) | |||
return Py_BuildValue(VALUE_PARSE "O", w1, o1); | |||
o1 = set_operation(o1, o2, 1, 1, w1, w2, 1, 1, 1); | |||
if (o1) | |||
ASSIGN(o1, Py_BuildValue(VALUE_PARSE "O", (VALUE_TYPE)1, o1)); | |||
return o1; | |||
} | |||
static PyObject * | |||
wintersection_m(PyObject *ignored, PyObject *args) | |||
{ | |||
PyObject *o1, *o2; | |||
VALUE_TYPE w1 = 1, w2 = 1; | |||
UNLESS(PyArg_ParseTuple(args, "OO|" VALUE_PARSE VALUE_PARSE, | |||
&o1, &o2, &w1, &w2) | |||
) return NULL; | |||
if (o1 == Py_None) | |||
return Py_BuildValue(VALUE_PARSE "O", (o2 == Py_None ? 0 : w2), o2); | |||
else if (o2 == Py_None) | |||
return Py_BuildValue(VALUE_PARSE "O", w1, o1); | |||
o1 = set_operation(o1, o2, 1, 1, w1, w2, 0, 1, 0); | |||
if (o1) | |||
ASSIGN(o1, Py_BuildValue(VALUE_PARSE "O", | |||
((o1->ob_type == (PyTypeObject*)(&SetType)) ? w2+w1 : 1), | |||
o1)); | |||
return o1; | |||
} | |||
#endif | |||
#ifdef MULTI_INT_UNION | |||
#include "sorters.c" | |||
/* Input is a sequence of integer sets (or convertible to sets by the | |||
set iteration protocol). Output is the union of the sets. The point | |||
is to run much faster than doing pairs of unions. | |||
*/ | |||
static PyObject * | |||
multiunion_m(PyObject *ignored, PyObject *args) | |||
{ | |||
PyObject *seq; /* input sequence */ | |||
int n; /* length of input sequence */ | |||
PyObject *set = NULL; /* an element of the input sequence */ | |||
Bucket *result; /* result set */ | |||
SetIteration setiter = {0}; | |||
int i; | |||
UNLESS(PyArg_ParseTuple(args, "O", &seq)) | |||
return NULL; | |||
n = PyObject_Length(seq); | |||
if (n < 0) | |||
return NULL; | |||
/* Construct an empty result set. */ | |||
result = BUCKET(PyObject_CallObject(OBJECT(&SetType), NULL)); | |||
if (result == NULL) | |||
return NULL; | |||
/* For each set in the input sequence, append its elements to the result | |||
set. At this point, we ignore the possibility of duplicates. */ | |||
for (i = 0; i < n; ++i) { | |||
set = PySequence_GetItem(seq, i); | |||
if (set == NULL) | |||
goto Error; | |||
/* If set is a bucket, do a straight resize + memcpy. */ | |||
if (set->ob_type == (PyTypeObject*)&SetType || | |||
set->ob_type == (PyTypeObject*)&BucketType) | |||
{ | |||
Bucket *b = BUCKET(set); | |||
int status = 0; | |||
UNLESS (PER_USE(b)) goto Error; | |||
if (b->len) | |||
status = bucket_append(result, b, 0, b->len, 0, i < n-1); | |||
PER_UNUSE(b); | |||
if (status < 0) goto Error; | |||
} | |||
else { | |||
/* No cheap way: iterate over set's elements one at a time. */ | |||
if (initSetIteration(&setiter, set, 0) < 0) goto Error; | |||
if (setiter.next(&setiter) < 0) goto Error; | |||
while (setiter.position >= 0) { | |||
if (result->len >= result->size && Bucket_grow(result, -1, 1) < 0) | |||
goto Error; | |||
COPY_KEY(result->keys[result->len], setiter.key); | |||
++result->len; | |||
/* We know the key is an int, so no need to incref it. */ | |||
if (setiter.next(&setiter) < 0) goto Error; | |||
} | |||
finiSetIteration(&setiter); | |||
} | |||
Py_DECREF(set); | |||
set = NULL; | |||
} | |||
/* Combine, sort, remove duplicates, and reset the result's len. | |||
If the set shrinks (which happens if and only if there are | |||
duplicates), no point to realloc'ing the set smaller, as we | |||
expect the result set to be short-lived. | |||
*/ | |||
if (result->len > 0) { | |||
size_t newlen; /* number of elements in final result set */ | |||
newlen = sort_int_nodups(result->keys, (size_t)result->len); | |||
result->len = (int)newlen; | |||
} | |||
return (PyObject *)result; | |||
Error: | |||
Py_DECREF(result); | |||
Py_XDECREF(set); | |||
finiSetIteration(&setiter); | |||
return NULL; | |||
} | |||
#endif |
@@ -0,0 +1,381 @@ | |||
/***************************************************************************** | |||
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 | |||
****************************************************************************/ | |||
#include "_compat.h" | |||
#define SETTEMPLATE_C "$Id$\n" | |||
static PyObject * | |||
Set_insert(Bucket *self, PyObject *args) | |||
{ | |||
PyObject *key; | |||
int i; | |||
UNLESS (PyArg_ParseTuple(args, "O", &key)) | |||
return NULL; | |||
if ( (i=_bucket_set(self, key, Py_None, 1, 1, 0)) < 0) | |||
return NULL; | |||
return INT_FROM_LONG(i); | |||
} | |||
/* _Set_update and _TreeSet_update are identical except for the | |||
function they call to add the element to the set. | |||
*/ | |||
static int | |||
_Set_update(Bucket *self, PyObject *seq) | |||
{ | |||
int n=0, ind=0; | |||
PyObject *iter, *v; | |||
iter = PyObject_GetIter(seq); | |||
if (iter == NULL) | |||
return -1; | |||
while (1) { | |||
v = PyIter_Next(iter); | |||
if (v == NULL) { | |||
if (PyErr_Occurred()) | |||
goto err; | |||
else | |||
break; | |||
} | |||
ind = _bucket_set(self, v, Py_None, 1, 1, 0); | |||
Py_DECREF(v); | |||
if (ind < 0) | |||
goto err; | |||
else | |||
n += ind; | |||
} | |||
err: | |||
Py_DECREF(iter); | |||
if (ind < 0) | |||
return -1; | |||
return n; | |||
} | |||
static PyObject * | |||
Set_update(Bucket *self, PyObject *args) | |||
{ | |||
PyObject *seq = NULL; | |||
int n = 0; | |||
if (!PyArg_ParseTuple(args, "|O:update", &seq)) | |||
return NULL; | |||
if (seq) { | |||
n = _Set_update(self, seq); | |||
if (n < 0) | |||
return NULL; | |||
} | |||
return INT_FROM_LONG(n); | |||
} | |||
static PyObject * | |||
Set_remove(Bucket *self, PyObject *args) | |||
{ | |||
PyObject *key; | |||
UNLESS (PyArg_ParseTuple(args, "O", &key)) | |||
return NULL; | |||
if (_bucket_set(self, key, NULL, 0, 1, 0) < 0) | |||
return NULL; | |||
Py_INCREF(Py_None); | |||
return Py_None; | |||
} | |||
static int | |||
_set_setstate(Bucket *self, PyObject *args) | |||
{ | |||
PyObject *k, *items; | |||
Bucket *next=0; | |||
int i, l, copied=1; | |||
KEY_TYPE *keys; | |||
UNLESS (PyArg_ParseTuple(args, "O|O", &items, &next)) | |||
return -1; | |||
if (!PyTuple_Check(items)) { | |||
PyErr_SetString(PyExc_TypeError, | |||
"tuple required for first state element"); | |||
return -1; | |||
} | |||
if ((l=PyTuple_Size(items)) < 0) | |||
return -1; | |||
for (i=self->len; --i >= 0; ) | |||
{ | |||
DECREF_KEY(self->keys[i]); | |||
} | |||
self->len=0; | |||
if (self->next) | |||
{ | |||
Py_DECREF(self->next); | |||
self->next=0; | |||
} | |||
if (l > self->size) | |||
{ | |||
UNLESS (keys=BTree_Realloc(self->keys, sizeof(KEY_TYPE)*l)) | |||
return -1; | |||
self->keys=keys; | |||
self->size=l; | |||
} | |||
for (i=0; i<l; i++) | |||
{ | |||
k=PyTuple_GET_ITEM(items, i); | |||
COPY_KEY_FROM_ARG(self->keys[i], k, copied); | |||
UNLESS (copied) | |||
return -1; | |||
INCREF_KEY(self->keys[i]); | |||
} | |||
self->len=l; | |||
if (next) | |||
{ | |||
self->next=next; | |||
Py_INCREF(next); | |||
} | |||
return 0; | |||
} | |||
static PyObject * | |||
set_setstate(Bucket *self, PyObject *args) | |||
{ | |||
int r; | |||
UNLESS (PyArg_ParseTuple(args, "O", &args)) | |||
return NULL; | |||
PER_PREVENT_DEACTIVATION(self); | |||
r=_set_setstate(self, args); | |||
PER_UNUSE(self); | |||
if (r < 0) | |||
return NULL; | |||
Py_INCREF(Py_None); | |||
return Py_None; | |||
} | |||
static struct PyMethodDef Set_methods[] = { | |||
{"__getstate__", (PyCFunction) bucket_getstate, METH_VARARGS, | |||
"__getstate__() -- Return the picklable state of the object"}, | |||
{"__setstate__", (PyCFunction) set_setstate, METH_VARARGS, | |||
"__setstate__() -- Set the state of the object"}, | |||
{"keys", (PyCFunction) bucket_keys, METH_VARARGS | METH_KEYWORDS, | |||
"keys() -- Return the keys"}, | |||
{"has_key", (PyCFunction) bucket_has_key, METH_O, | |||
"has_key(key) -- Test whether the bucket contains the given key"}, | |||
{"clear", (PyCFunction) bucket_clear, METH_VARARGS, | |||
"clear() -- Remove all of the items from the bucket"}, | |||
{"maxKey", (PyCFunction) Bucket_maxKey, METH_VARARGS, | |||
"maxKey([key]) -- Find the maximum key\n\n" | |||
"If an argument is given, find the maximum <= the argument"}, | |||
{"minKey", (PyCFunction) Bucket_minKey, METH_VARARGS, | |||
"minKey([key]) -- Find the minimum key\n\n" | |||
"If an argument is given, find the minimum >= the argument"}, | |||
#ifdef PERSISTENT | |||
{"_p_resolveConflict", | |||
(PyCFunction) bucket__p_resolveConflict, METH_VARARGS, | |||
"_p_resolveConflict() -- Reinitialize from a newly created copy"}, | |||
{"_p_deactivate", | |||
(PyCFunction) bucket__p_deactivate, METH_VARARGS | METH_KEYWORDS, | |||
"_p_deactivate() -- Reinitialize from a newly created copy"}, | |||
#endif | |||
{"add", (PyCFunction)Set_insert, METH_VARARGS, | |||
"add(id) -- Add a key to the set"}, | |||
{"insert", (PyCFunction)Set_insert, METH_VARARGS, | |||
"insert(id) -- Add a key to the set"}, | |||
{"update", (PyCFunction)Set_update, METH_VARARGS, | |||
"update(seq) -- Add the items from the given sequence to the set"}, | |||
{"remove", (PyCFunction)Set_remove, METH_VARARGS, | |||
"remove(id) -- Remove an id from the set"}, | |||
{NULL, NULL} /* sentinel */ | |||
}; | |||
static int | |||
Set_init(PyObject *self, PyObject *args, PyObject *kwds) | |||
{ | |||
PyObject *v = NULL; | |||
if (!PyArg_ParseTuple(args, "|O:" MOD_NAME_PREFIX "Set", &v)) | |||
return -1; | |||
if (v) | |||
return _Set_update((Bucket *)self, v); | |||
else | |||
return 0; | |||
} | |||
static PyObject * | |||
set_repr(Bucket *self) | |||
{ | |||
static PyObject *format; | |||
PyObject *r, *t; | |||
if (!format) | |||
format = TEXT_FROM_STRING(MOD_NAME_PREFIX "Set(%s)"); | |||
UNLESS (t = PyTuple_New(1)) | |||
return NULL; | |||
UNLESS (r = bucket_keys(self, NULL, NULL)) | |||
goto err; | |||
PyTuple_SET_ITEM(t, 0, r); | |||
r = t; | |||
ASSIGN(r, TEXT_FORMAT(format, r)); | |||
return r; | |||
err: | |||
Py_DECREF(t); | |||
return NULL; | |||
} | |||
static Py_ssize_t | |||
set_length(Bucket *self) | |||
{ | |||
int r; | |||
PER_USE_OR_RETURN(self, -1); | |||
r = self->len; | |||
PER_UNUSE(self); | |||
return r; | |||
} | |||
static PyObject * | |||
set_item(Bucket *self, Py_ssize_t index) | |||
{ | |||
PyObject *r=0; | |||
PER_USE_OR_RETURN(self, NULL); | |||
if (index >= 0 && index < self->len) | |||
{ | |||
COPY_KEY_TO_OBJECT(r, self->keys[index]); | |||
} | |||
else | |||
IndexError(index); | |||
PER_UNUSE(self); | |||
return r; | |||
} | |||
static PySequenceMethods set_as_sequence = { | |||
(lenfunc)set_length, /* sq_length */ | |||
(binaryfunc)0, /* sq_concat */ | |||
(ssizeargfunc)0, /* sq_repeat */ | |||
(ssizeargfunc)set_item, /* sq_item */ | |||
(ssizessizeargfunc)0, /* sq_slice */ | |||
(ssizeobjargproc)0, /* sq_ass_item */ | |||
(ssizessizeobjargproc)0, /* sq_ass_slice */ | |||
(objobjproc)bucket_contains, /* sq_contains */ | |||
0, /* sq_inplace_concat */ | |||
0, /* sq_inplace_repeat */ | |||
}; | |||
static PyTypeObject SetType = { | |||
PyVarObject_HEAD_INIT(NULL, 0) /* PyPersist_Type */ | |||
MODULE_NAME MOD_NAME_PREFIX "Set", /* tp_name */ | |||
sizeof(Bucket), /* tp_basicsize */ | |||
0, /* tp_itemsize */ | |||
(destructor)bucket_dealloc, /* tp_dealloc */ | |||
0, /* tp_print */ | |||
0, /* tp_getattr */ | |||
0, /* tp_setattr */ | |||
0, /* tp_compare */ | |||
(reprfunc)set_repr, /* tp_repr */ | |||
0, /* tp_as_number */ | |||
&set_as_sequence, /* tp_as_sequence */ | |||
0, /* 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_HAVE_GC | | |||
Py_TPFLAGS_BASETYPE, /* tp_flags */ | |||
0, /* tp_doc */ | |||
(traverseproc)bucket_traverse, /* tp_traverse */ | |||
(inquiry)bucket_tp_clear, /* tp_clear */ | |||
0, /* tp_richcompare */ | |||
0, /* tp_weaklistoffset */ | |||
(getiterfunc)Bucket_getiter, /* tp_iter */ | |||
0, /* tp_iternext */ | |||
Set_methods, /* tp_methods */ | |||
Bucket_members, /* tp_members */ | |||
0, /* tp_getset */ | |||
0, /* tp_base */ | |||
0, /* tp_dict */ | |||
0, /* tp_descr_get */ | |||
0, /* tp_descr_set */ | |||
0, /* tp_dictoffset */ | |||
Set_init, /* tp_init */ | |||
0, /* tp_alloc */ | |||
0, /*PyType_GenericNew,*/ /* tp_new */ | |||
}; | |||
static int | |||
nextSet(SetIteration *i) | |||
{ | |||
if (i->position >= 0) | |||
{ | |||
UNLESS(PER_USE(BUCKET(i->set))) | |||
return -1; | |||
if (i->position) | |||
{ | |||
DECREF_KEY(i->key); | |||
} | |||
if (i->position < BUCKET(i->set)->len) | |||
{ | |||
COPY_KEY(i->key, BUCKET(i->set)->keys[i->position]); | |||
INCREF_KEY(i->key); | |||
i->position ++; | |||
} | |||
else | |||
{ | |||
i->position = -1; | |||
PER_ACCESSED(BUCKET(i->set)); | |||
} | |||
PER_ALLOW_DEACTIVATION(BUCKET(i->set)); | |||
} | |||
return 0; | |||
} |
@@ -0,0 +1,254 @@ | |||
/***************************************************************************** | |||
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 | |||
****************************************************************************/ | |||
#include "_compat.h" | |||
#define TREESETTEMPLATE_C "$Id$\n" | |||
static PyObject * | |||
TreeSet_insert(BTree *self, PyObject *args) | |||
{ | |||
PyObject *key; | |||
int i; | |||
if (!PyArg_ParseTuple(args, "O:insert", &key)) | |||
return NULL; | |||
i = _BTree_set(self, key, Py_None, 1, 1); | |||
if (i < 0) | |||
return NULL; | |||
return INT_FROM_LONG(i); | |||
} | |||
/* _Set_update and _TreeSet_update are identical except for the | |||
function they call to add the element to the set. | |||
*/ | |||
static int | |||
_TreeSet_update(BTree *self, PyObject *seq) | |||
{ | |||
int n=0, ind=0; | |||
PyObject *iter, *v; | |||
iter = PyObject_GetIter(seq); | |||
if (iter == NULL) | |||
return -1; | |||
while (1) | |||
{ | |||
v = PyIter_Next(iter); | |||
if (v == NULL) | |||
{ | |||
if (PyErr_Occurred()) | |||
goto err; | |||
else | |||
break; | |||
} | |||
ind = _BTree_set(self, v, Py_None, 1, 1); | |||
Py_DECREF(v); | |||
if (ind < 0) | |||
goto err; | |||
else | |||
n += ind; | |||
} | |||
err: | |||
Py_DECREF(iter); | |||
if (ind < 0) | |||
return -1; | |||
return n; | |||
} | |||
static PyObject * | |||
TreeSet_update(BTree *self, PyObject *args) | |||
{ | |||
PyObject *seq = NULL; | |||
int n = 0; | |||
if (!PyArg_ParseTuple(args, "|O:update", &seq)) | |||
return NULL; | |||
if (seq) | |||
{ | |||
n = _TreeSet_update(self, seq); | |||
if (n < 0) | |||
return NULL; | |||
} | |||
return INT_FROM_LONG(n); | |||
} | |||
static PyObject * | |||
TreeSet_remove(BTree *self, PyObject *args) | |||
{ | |||
PyObject *key; | |||
UNLESS (PyArg_ParseTuple(args, "O", &key)) | |||
return NULL; | |||
if (_BTree_set(self, key, NULL, 0, 1) < 0) | |||
return NULL; | |||
Py_INCREF(Py_None); | |||
return Py_None; | |||
} | |||
static PyObject * | |||
TreeSet_setstate(BTree *self, PyObject *args) | |||
{ | |||
int r; | |||
if (!PyArg_ParseTuple(args,"O",&args)) | |||
return NULL; | |||
PER_PREVENT_DEACTIVATION(self); | |||
r=_BTree_setstate(self, args, 1); | |||
PER_UNUSE(self); | |||
if (r < 0) | |||
return NULL; | |||
Py_INCREF(Py_None); | |||
return Py_None; | |||
} | |||
static struct PyMethodDef TreeSet_methods[] = | |||
{ | |||
{"__getstate__", (PyCFunction) BTree_getstate, METH_NOARGS, | |||
"__getstate__() -> state\n\n" | |||
"Return the picklable state of the TreeSet."}, | |||
{"__setstate__", (PyCFunction) TreeSet_setstate, METH_VARARGS, | |||
"__setstate__(state)\n\n" | |||
"Set the state of the TreeSet."}, | |||
{"has_key", (PyCFunction) BTree_has_key, METH_O, | |||
"has_key(key)\n\n" | |||
"Return true if the TreeSet contains the given key."}, | |||
{"keys", (PyCFunction) BTree_keys, METH_VARARGS | METH_KEYWORDS, | |||
"keys([min, max]) -> list of keys\n\n" | |||
"Returns the keys of the TreeSet. If min and max are supplied, only\n" | |||
"keys greater than min and less than max are returned."}, | |||
{"maxKey", (PyCFunction) BTree_maxKey, METH_VARARGS, | |||
"maxKey([max]) -> key\n\n" | |||
"Return the largest key in the BTree. If max is specified, return\n" | |||
"the largest key <= max."}, | |||
{"minKey", (PyCFunction) BTree_minKey, METH_VARARGS, | |||
"minKey([mi]) -> key\n\n" | |||
"Return the smallest key in the BTree. If min is specified, return\n" | |||
"the smallest key >= min."}, | |||
{"clear", (PyCFunction) BTree_clear, METH_NOARGS, | |||
"clear()\n\nRemove all of the items from the BTree."}, | |||
{"add", (PyCFunction)TreeSet_insert, METH_VARARGS, | |||
"add(id) -- Add an item to the set"}, | |||
{"insert", (PyCFunction)TreeSet_insert, METH_VARARGS, | |||
"insert(id) -- Add an item to the set"}, | |||
{"update", (PyCFunction)TreeSet_update, METH_VARARGS, | |||
"update(collection)\n\n Add the items from the given collection."}, | |||
{"remove", (PyCFunction)TreeSet_remove, METH_VARARGS, | |||
"remove(id) -- Remove a key from the set"}, | |||
{"_check", (PyCFunction) BTree_check, METH_NOARGS, | |||
"Perform sanity check on TreeSet, and raise exception if flawed."}, | |||
#ifdef PERSISTENT | |||
{"_p_resolveConflict", | |||
(PyCFunction) BTree__p_resolveConflict, METH_VARARGS, | |||
"_p_resolveConflict() -- Reinitialize from a newly created copy"}, | |||
{"_p_deactivate", | |||
(PyCFunction) BTree__p_deactivate, METH_VARARGS | METH_KEYWORDS, | |||
"_p_deactivate()\n\nReinitialize from a newly created copy."}, | |||
#endif | |||
{NULL, NULL} /* sentinel */ | |||
}; | |||
static PyMappingMethods TreeSet_as_mapping = { | |||
(lenfunc)BTree_length, /*mp_length*/ | |||
}; | |||
static PySequenceMethods TreeSet_as_sequence = { | |||
(lenfunc)0, /* sq_length */ | |||
(binaryfunc)0, /* sq_concat */ | |||
(ssizeargfunc)0, /* sq_repeat */ | |||
(ssizeargfunc)0, /* sq_item */ | |||
(ssizessizeargfunc)0, /* sq_slice */ | |||
(ssizeobjargproc)0, /* sq_ass_item */ | |||
(ssizessizeobjargproc)0, /* sq_ass_slice */ | |||
(objobjproc)BTree_contains, /* sq_contains */ | |||
0, /* sq_inplace_concat */ | |||
0, /* sq_inplace_repeat */ | |||
}; | |||
static int | |||
TreeSet_init(PyObject *self, PyObject *args, PyObject *kwds) | |||
{ | |||
PyObject *v = NULL; | |||
if (!PyArg_ParseTuple(args, "|O:" MOD_NAME_PREFIX "TreeSet", &v)) | |||
return -1; | |||
if (v) | |||
return _TreeSet_update((BTree *)self, v); | |||
else | |||
return 0; | |||
} | |||
static PyTypeObject TreeSetType = | |||
{ | |||
PyVarObject_HEAD_INIT(NULL, 0) | |||
MODULE_NAME MOD_NAME_PREFIX "TreeSet", /* tp_name */ | |||
sizeof(BTree), /* tp_basicsize */ | |||
0, /* tp_itemsize */ | |||
(destructor)BTree_dealloc, /* tp_dealloc */ | |||
0, /* tp_print */ | |||
0, /* tp_getattr */ | |||
0, /* tp_setattr */ | |||
0, /* tp_compare */ | |||
0, /* tp_repr */ | |||
&BTree_as_number_for_nonzero, /* tp_as_number */ | |||
&TreeSet_as_sequence, /* tp_as_sequence */ | |||
&TreeSet_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_HAVE_GC | | |||
Py_TPFLAGS_BASETYPE, /* tp_flags */ | |||
0, /* tp_doc */ | |||
(traverseproc)BTree_traverse, /* tp_traverse */ | |||
(inquiry)BTree_tp_clear, /* tp_clear */ | |||
0, /* tp_richcompare */ | |||
0, /* tp_weaklistoffset */ | |||
(getiterfunc)BTree_getiter, /* tp_iter */ | |||
0, /* tp_iternext */ | |||
TreeSet_methods, /* tp_methods */ | |||
BTree_members, /* tp_members */ | |||
0, /* tp_getset */ | |||
0, /* tp_base */ | |||
0, /* tp_dict */ | |||
0, /* tp_descr_get */ | |||
0, /* tp_descr_set */ | |||
0, /* tp_dictoffset */ | |||
TreeSet_init, /* tp_init */ | |||
0, /* tp_alloc */ | |||
0, /*PyType_GenericNew,*/ /* tp_new */ | |||
}; |
@@ -0,0 +1,41 @@ | |||
/*############################################################################ | |||
# | |||
# 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. | |||
# | |||
############################################################################*/ | |||
#define MASTER_ID "$Id$\n" | |||
/* IFBTree - int key, float value BTree | |||
Implements a collection using int type keys | |||
and float type values | |||
*/ | |||
/* Setup template macros */ | |||
#define PERSISTENT | |||
#define MOD_NAME_PREFIX "IF" | |||
#define DEFAULT_MAX_BUCKET_SIZE 120 | |||
#define DEFAULT_MAX_BTREE_SIZE 500 | |||
#include "_compat.h" | |||
#include "intkeymacros.h" | |||
#include "floatvaluemacros.h" | |||
#ifdef PY3K | |||
#define INITMODULE PyInit__IFBTree | |||
#else | |||
#define INITMODULE init_IFBTree | |||
#endif | |||
#include "BTreeModuleTemplate.c" |
@@ -0,0 +1,41 @@ | |||
/*############################################################################ | |||
# | |||
# 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. | |||
# | |||
############################################################################*/ | |||
#define MASTER_ID "$Id$\n" | |||
/* IIBTree - int key, int value BTree | |||
Implements a collection using int type keys | |||
and int type values | |||
*/ | |||
/* Setup template macros */ | |||
#define PERSISTENT | |||
#define MOD_NAME_PREFIX "II" | |||
#define DEFAULT_MAX_BUCKET_SIZE 120 | |||
#define DEFAULT_MAX_BTREE_SIZE 500 | |||
#include "_compat.h" | |||
#include "intkeymacros.h" | |||
#include "intvaluemacros.h" | |||
#ifdef PY3K | |||
#define INITMODULE PyInit__IIBTree | |||
#else | |||
#define INITMODULE init_IIBTree | |||
#endif | |||
#include "BTreeModuleTemplate.c" |
@@ -0,0 +1,39 @@ | |||
/*############################################################################ | |||
# | |||
# 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. | |||
# | |||
############################################################################*/ | |||
#define MASTER_ID "$Id$\n" | |||
/* IOBTree - int key, object value BTree | |||
Implements a collection using int type keys | |||
and object type values | |||
*/ | |||
#define PERSISTENT | |||
#define MOD_NAME_PREFIX "IO" | |||
#define DEFAULT_MAX_BUCKET_SIZE 60 | |||
#define DEFAULT_MAX_BTREE_SIZE 500 | |||
#include "_compat.h" | |||
#include "intkeymacros.h" | |||
#include "objectvaluemacros.h" | |||
#ifdef PY3K | |||
#define INITMODULE PyInit__IOBTree | |||
#else | |||
#define INITMODULE init_IOBTree | |||
#endif | |||
#include "BTreeModuleTemplate.c" |
@@ -0,0 +1,43 @@ | |||
/*############################################################################ | |||
# | |||
# 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. | |||
# | |||
############################################################################*/ | |||
#define MASTER_ID "$Id: _IFBTree.c 67074 2006-04-17 19:13:39Z fdrake $\n" | |||
/* IFBTree - int key, float value BTree | |||
Implements a collection using int type keys | |||
and float type values | |||
*/ | |||
/* Setup template macros */ | |||
#define PERSISTENT | |||
#define MOD_NAME_PREFIX "LF" | |||
#define DEFAULT_MAX_BUCKET_SIZE 120 | |||
#define DEFAULT_MAX_BTREE_SIZE 500 | |||
#define ZODB_64BIT_INTS | |||
#include "_compat.h" | |||
#include "intkeymacros.h" | |||
#include "floatvaluemacros.h" | |||
#ifdef PY3K | |||
#define INITMODULE PyInit__LFBTree | |||
#else | |||
#define INITMODULE init_LFBTree | |||
#endif | |||
#include "BTreeModuleTemplate.c" |
@@ -0,0 +1,43 @@ | |||
/*############################################################################ | |||
# | |||
# 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. | |||
# | |||
############################################################################*/ | |||
#define MASTER_ID "$Id: _IIBTree.c 25186 2004-06-02 15:07:33Z jim $\n" | |||
/* IIBTree - int key, int value BTree | |||
Implements a collection using int type keys | |||
and int type values | |||
*/ | |||
/* Setup template macros */ | |||
#define PERSISTENT | |||
#define MOD_NAME_PREFIX "LL" | |||
#define DEFAULT_MAX_BUCKET_SIZE 120 | |||
#define DEFAULT_MAX_BTREE_SIZE 500 | |||
#define ZODB_64BIT_INTS | |||
#include "_compat.h" | |||
#include "intkeymacros.h" | |||
#include "intvaluemacros.h" | |||
#ifdef PY3K | |||
#define INITMODULE PyInit__LLBTree | |||
#else | |||
#define INITMODULE init_LLBTree | |||
#endif | |||
#include "BTreeModuleTemplate.c" |
@@ -0,0 +1,41 @@ | |||
/*############################################################################ | |||
# | |||
# 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. | |||
# | |||
############################################################################*/ | |||
#define MASTER_ID "$Id: _IOBTree.c 25186 2004-06-02 15:07:33Z jim $\n" | |||
/* IOBTree - int key, object value BTree | |||
Implements a collection using int type keys | |||
and object type values | |||
*/ | |||
#define PERSISTENT | |||
#define MOD_NAME_PREFIX "LO" | |||
#define DEFAULT_MAX_BUCKET_SIZE 60 | |||
#define DEFAULT_MAX_BTREE_SIZE 500 | |||
#define ZODB_64BIT_INTS | |||
#include "_compat.h" | |||
#include "intkeymacros.h" | |||
#include "objectvaluemacros.h" | |||
#ifdef PY3K | |||
#define INITMODULE PyInit__LOBTree | |||
#else | |||
#define INITMODULE init_LOBTree | |||
#endif | |||
#include "BTreeModuleTemplate.c" |
@@ -0,0 +1,39 @@ | |||
/*############################################################################ | |||
# | |||
# 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. | |||
# | |||
############################################################################*/ | |||
#define MASTER_ID "$Id$\n" | |||
/* OIBTree - object key, int value BTree | |||
Implements a collection using object type keys | |||
and int type values | |||
*/ | |||
#define PERSISTENT | |||
#define MOD_NAME_PREFIX "OI" | |||
#define DEFAULT_MAX_BUCKET_SIZE 60 | |||
#define DEFAULT_MAX_BTREE_SIZE 250 | |||
#include "_compat.h" | |||
#include "objectkeymacros.h" | |||
#include "intvaluemacros.h" | |||
#ifdef PY3K | |||
#define INITMODULE PyInit__OIBTree | |||
#else | |||
#define INITMODULE init_OIBTree | |||
#endif | |||
#include "BTreeModuleTemplate.c" |
@@ -0,0 +1,41 @@ | |||
/*############################################################################ | |||
# | |||
# 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. | |||
# | |||
############################################################################*/ | |||
#define MASTER_ID "$Id: _OIBTree.c 25186 2004-06-02 15:07:33Z jim $\n" | |||
/* OIBTree - object key, int value BTree | |||
Implements a collection using object type keys | |||
and int type values | |||
*/ | |||
#define PERSISTENT | |||
#define MOD_NAME_PREFIX "OL" | |||
#define DEFAULT_MAX_BUCKET_SIZE 60 | |||
#define DEFAULT_MAX_BTREE_SIZE 250 | |||
#define ZODB_64BIT_INTS | |||
#include "_compat.h" | |||
#include "objectkeymacros.h" | |||
#include "intvaluemacros.h" | |||
#ifdef PY3K | |||
#define INITMODULE PyInit__OLBTree | |||
#else | |||
#define INITMODULE init_OLBTree | |||
#endif | |||
#include "BTreeModuleTemplate.c" |
@@ -0,0 +1,39 @@ | |||
/*############################################################################ | |||
# | |||
# 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. | |||
# | |||
############################################################################*/ | |||
#define MASTER_ID "$Id$\n" | |||
/* OOBTree - object key, object value BTree | |||
Implements a collection using object type keys | |||
and object type values | |||
*/ | |||
#define PERSISTENT | |||
#define MOD_NAME_PREFIX "OO" | |||
#define DEFAULT_MAX_BUCKET_SIZE 30 | |||
#define DEFAULT_MAX_BTREE_SIZE 250 | |||
#include "_compat.h" | |||
#include "objectkeymacros.h" | |||
#include "objectvaluemacros.h" | |||
#ifdef PY3K | |||
#define INITMODULE PyInit__OOBTree | |||
#else | |||
#define INITMODULE init_OOBTree | |||
#endif | |||
#include "BTreeModuleTemplate.c" |
@@ -0,0 +1,69 @@ | |||
############################################################################# | |||
# | |||
# Copyright (c) 2007 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE. | |||
# | |||
############################################################################# | |||
import zope.interface | |||
import BTrees.Interfaces | |||
@zope.interface.implementer(BTrees.Interfaces.IBTreeFamily) | |||
class _Family(object): | |||
from BTrees import OOBTree as OO | |||
class _Family32(_Family): | |||
from BTrees import OIBTree as OI | |||
from BTrees import IIBTree as II | |||
from BTrees import IOBTree as IO | |||
from BTrees import IFBTree as IF | |||
maxint = int(2**31-1) | |||
minint = -maxint - 1 | |||
def __reduce__(self): | |||
return _family32, () | |||
class _Family64(_Family): | |||
from BTrees import OLBTree as OI | |||
from BTrees import LLBTree as II | |||
from BTrees import LOBTree as IO | |||
from BTrees import LFBTree as IF | |||
maxint = 2**63-1 | |||
minint = -maxint - 1 | |||
def __reduce__(self): | |||
return _family64, () | |||
def _family32(): | |||
return family32 | |||
_family32.__safe_for_unpickling__ = True | |||
def _family64(): | |||
return family64 | |||
_family64.__safe_for_unpickling__ = True | |||
family32 = _Family32() | |||
family64 = _Family64() | |||
BTrees.family64.IO.family = family64 | |||
BTrees.family64.OI.family = family64 | |||
BTrees.family64.IF.family = family64 | |||
BTrees.family64.II.family = family64 | |||
BTrees.family32.IO.family = family32 | |||
BTrees.family32.OI.family = family32 | |||
BTrees.family32.IF.family = family32 | |||
BTrees.family32.II.family = family32 |
@@ -0,0 +1,55 @@ | |||
/* Straddle Python 2 / 3 */ | |||
#ifndef BTREES__COMPAT_H | |||
#define BTREES__COMPAT_H | |||
#include "Python.h" | |||
#ifdef INTERN | |||
#undef INTERN | |||
#endif | |||
#ifdef INT_FROM_LONG | |||
#undef INT_FROM_LONG | |||
#endif | |||
#ifdef INT_CHECK | |||
#undef INT_CHECK | |||
#endif | |||
#if PY_MAJOR_VERSION >= 3 | |||
#define PY3K | |||
#define INTERN PyUnicode_InternFromString | |||
#define INT_FROM_LONG(x) PyLong_FromLong(x) | |||
#define INT_CHECK(x) PyLong_Check(x) | |||
#define INT_AS_LONG(x) PyLong_AS_LONG(x) | |||
#define TEXT_FROM_STRING PyUnicode_FromString | |||
#define TEXT_FORMAT PyUnicode_Format | |||
/* Note that the second comparison is skipped if the first comparison returns: | |||
1 -> There was no error and the answer is -1 | |||
-1 -> There was an error, which the caller will detect with PyError_Occurred. | |||
*/ | |||
#define COMPARE(lhs, rhs) \ | |||
(lhs == Py_None ? (rhs == Py_None ? 0 : -1) : (rhs == Py_None ? 1 : \ | |||
(PyObject_RichCompareBool((lhs), (rhs), Py_LT) != 0 ? -1 : \ | |||
(PyObject_RichCompareBool((lhs), (rhs), Py_EQ) > 0 ? 0 : 1)))) | |||
#else | |||
#define INTERN PyString_InternFromString | |||
#define INT_FROM_LONG(x) PyInt_FromLong(x) | |||
#define INT_CHECK(x) PyInt_Check(x) | |||
#define INT_AS_LONG(x) PyInt_AS_LONG(x) | |||
#define TEXT_FROM_STRING PyString_FromString | |||
#define TEXT_FORMAT PyString_Format | |||
#define COMPARE(lhs, rhs) \ | |||
(lhs == Py_None ? (rhs == Py_None ? 0 : -1) : (rhs == Py_None ? 1 : \ | |||
PyObject_Compare((lhs), (rhs)))) | |||
#endif | |||
#endif /* BTREES__COMPAT_H */ |
@@ -0,0 +1,100 @@ | |||
############################################################################## | |||
# | |||
# Copyright (c) 2001-2012 Zope Foundation and Contributors. | |||
# All Rights Reserved. | |||
# | |||
# This software is subject to the provisions of the Zope Public License, | |||
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. | |||
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED | |||
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS | |||
# FOR A PARTICULAR PURPOSE | |||
# | |||
############################################################################## | |||
import os | |||
import sys | |||
PYPY = hasattr(sys, 'pypy_version_info') | |||
# We can and do build the C extensions on PyPy, but | |||
# as of Persistent 4.2.5 the persistent C extension is not | |||
# built on PyPy, so importing our C extension will fail anyway. | |||
PURE_PYTHON = os.environ.get('PURE_PYTHON', PYPY) | |||
if sys.version_info[0] < 3: #pragma NO COVER Python2 | |||
PY2 = True | |||
PY3 = False | |||
int_types = int, long | |||
xrange = xrange | |||
def compare(x, y): | |||
if x is None: | |||
if y is None: | |||
return 0 | |||
else: | |||
return -1 | |||
elif y is None: | |||
return 1 | |||
else: | |||
return cmp(x, y) | |||
_bytes = str | |||
def _ascii(x): | |||
return bytes(x) | |||
else: #pragma NO COVER Python3 | |||
PY2 = False | |||
PY3 = True | |||
int_types = int, | |||
xrange = range | |||
def compare(x, y): | |||
if x is None: | |||
if y is None: | |||
return 0 | |||
else: | |||
return -1 | |||
elif y is None: | |||
return 1 | |||
else: | |||
return (x > y) - (y > x) | |||
_bytes = bytes | |||
def _ascii(x): | |||
return bytes(x, 'ascii') | |||
def import_c_extension(mod_globals): | |||
import importlib | |||
c_module = None | |||
module_name = mod_globals['__name__'] | |||
assert module_name.startswith('BTrees.') | |||
module_name = module_name.split('.')[1] | |||
if not PURE_PYTHON: | |||
try: | |||
c_module = importlib.import_module('BTrees._' + module_name) | |||
except ImportError: | |||
pass | |||
if c_module is not None: | |||
new_values = dict(c_module.__dict__) | |||
new_values.pop("__name__", None) | |||
new_values.pop('__file__', None) | |||
new_values.pop('__doc__', None) | |||
mod_globals.update(new_values) | |||
else: | |||
# No C extension, make the Py versions available without that | |||
# extension. The list comprehension both filters and prevents | |||
# concurrent modification errors. | |||
for py in [k for k in mod_globals if k.endswith('Py')]: | |||
mod_globals[py[:-2]] = mod_globals[py] | |||
# Assign the global aliases | |||
prefix = module_name[:2] | |||
for name in ('Bucket', 'Set', 'BTree', 'TreeSet'): | |||
mod_globals[name] = mod_globals[prefix + name] | |||
# Cleanup | |||
del mod_globals['import_c_extension'] |
@@ -0,0 +1,164 @@ | |||
/*############################################################################ | |||
# | |||
# 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. | |||
# | |||
############################################################################*/ | |||
#define MASTER_ID "$Id$\n" | |||
/* fsBTree - FileStorage index BTree | |||
This BTree implements a mapping from 2-character strings | |||
to six-character strings. This allows us to efficiently store | |||
a FileStorage index as a nested mapping of 6-character oid prefix | |||
to mapping of 2-character oid suffix to 6-character (byte) file | |||
positions. | |||
*/ | |||
typedef unsigned char char2[2]; | |||
typedef unsigned char char6[6]; | |||
/* Setup template macros */ | |||
#define PERSISTENT | |||
#define MOD_NAME_PREFIX "fs" | |||
#define DEFAULT_MAX_BUCKET_SIZE 500 | |||
#define DEFAULT_MAX_BTREE_SIZE 500 | |||
#include "_compat.h" | |||
/*#include "intkeymacros.h"*/ | |||
#define KEYMACROS_H "$Id$\n" | |||
#define KEY_TYPE char2 | |||
#undef KEY_TYPE_IS_PYOBJECT | |||
#define KEY_CHECK(K) (PyBytes_Check(K) && PyBytes_GET_SIZE(K)==2) | |||
#define TEST_KEY_SET_OR(V, K, T) if ( ( (V) = ((*(K) < *(T) || (*(K) == *(T) && (K)[1] < (T)[1])) ? -1 : ((*(K) == *(T) && (K)[1] == (T)[1]) ? 0 : 1)) ), 0 ) | |||
#define DECREF_KEY(KEY) | |||
#define INCREF_KEY(k) | |||
#define COPY_KEY(KEY, E) (*(KEY)=*(E), (KEY)[1]=(E)[1]) | |||
#define COPY_KEY_TO_OBJECT(O, K) O=PyBytes_FromStringAndSize((const char*)K,2) | |||
#define COPY_KEY_FROM_ARG(TARGET, ARG, STATUS) \ | |||
if (KEY_CHECK(ARG)) memcpy(TARGET, PyBytes_AS_STRING(ARG), 2); else { \ | |||
PyErr_SetString(PyExc_TypeError, "expected two-character string key"); \ | |||
(STATUS)=0; } | |||
/*#include "intvaluemacros.h"*/ | |||
#define VALUEMACROS_H "$Id$\n" | |||
#define VALUE_TYPE char6 | |||
#undef VALUE_TYPE_IS_PYOBJECT | |||
#define TEST_VALUE(K, T) memcmp(K,T,6) | |||
#define DECREF_VALUE(k) | |||
#define INCREF_VALUE(k) | |||
#define COPY_VALUE(V, E) (memcpy(V, E, 6)) | |||
#define COPY_VALUE_TO_OBJECT(O, K) O=PyBytes_FromStringAndSize((const char*)K,6) | |||
#define COPY_VALUE_FROM_ARG(TARGET, ARG, STATUS) \ | |||
if ((PyBytes_Check(ARG) && PyBytes_GET_SIZE(ARG)==6)) \ | |||
memcpy(TARGET, PyBytes_AS_STRING(ARG), 6); else { \ | |||
PyErr_SetString(PyExc_TypeError, "expected six-character string key"); \ | |||
(STATUS)=0; } | |||
#define NORMALIZE_VALUE(V, MIN) | |||
#include "Python.h" | |||
static PyObject *bucket_toBytes(PyObject *self); | |||
static PyObject *bucket_fromBytes(PyObject *self, PyObject *state); | |||
#define EXTRA_BUCKET_METHODS \ | |||
{"toBytes", (PyCFunction) bucket_toBytes, METH_NOARGS, \ | |||
"toBytes() -- Return the state as a bytes array"}, \ | |||
{"fromBytes", (PyCFunction) bucket_fromBytes, METH_O, \ | |||
"fromSBytes(s) -- Set the state of the object from a bytes array"}, \ | |||
{"toString", (PyCFunction) bucket_toBytes, METH_NOARGS, \ | |||
"toString() -- Deprecated alias for 'toBytes'"}, \ | |||
{"fromString", (PyCFunction) bucket_fromBytes, METH_O, \ | |||
"fromString(s) -- Deprecated alias for 'fromBytes'"}, \ | |||
#ifdef PY3K | |||
#define INITMODULE PyInit__fsBTree | |||
#else | |||
#define INITMODULE init_fsBTree | |||
#endif | |||
#include "BTreeModuleTemplate.c" | |||
static PyObject * | |||
bucket_toBytes(PyObject *oself) | |||
{ | |||
Bucket *self = (Bucket *)oself; | |||
PyObject *items = NULL; | |||
int len; | |||
PER_USE_OR_RETURN(self, NULL); | |||
len = self->len; | |||
items = PyBytes_FromStringAndSize(NULL, len*8); | |||
if (items == NULL) | |||
goto err; | |||
memcpy(PyBytes_AS_STRING(items), self->keys, len*2); | |||
memcpy(PyBytes_AS_STRING(items)+len*2, self->values, len*6); | |||
PER_UNUSE(self); | |||
return items; | |||
err: | |||
PER_UNUSE(self); | |||
Py_XDECREF(items); | |||
return NULL; | |||
} | |||
static PyObject * | |||
bucket_fromBytes(PyObject *oself, PyObject *state) | |||
{ | |||
Bucket *self = (Bucket *)oself; | |||
int len; | |||
KEY_TYPE *keys; | |||
VALUE_TYPE *values; | |||
len = PyBytes_Size(state); | |||
if (len < 0) | |||
return NULL; | |||
if (len%8) | |||
{ | |||
PyErr_SetString(PyExc_ValueError, "state string of wrong size"); | |||
return NULL; | |||
} | |||
len /= 8; | |||
if (self->next) { | |||
Py_DECREF(self->next); | |||
self->next = NULL; | |||
} | |||
if (len > self->size) { | |||
keys = BTree_Realloc(self->keys, sizeof(KEY_TYPE)*len); | |||
if (keys == NULL) | |||
return NULL; | |||
values = BTree_Realloc(self->values, sizeof(VALUE_TYPE)*len); | |||
if (values == NULL) | |||
return NULL; | |||
self->keys = keys; | |||
self->values = values; | |||
self->size = len; | |||
} | |||
memcpy(self->keys, PyBytes_AS_STRING(state), len*2); | |||
memcpy(self->values, PyBytes_AS_STRING(state)+len*2, len*6); | |||
self->len = len; | |||
Py_INCREF(self); | |||
return (PyObject *)self; | |||
} |
@@ -0,0 +1,428 @@ | |||
############################################################################## | |||
# | |||
# 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 | |||
# | |||
############################################################################## | |||
""" | |||
Utilities for working with BTrees (TreeSets, Buckets, and Sets) at a low | |||
level. | |||
The primary function is check(btree), which performs value-based consistency | |||
checks of a kind btree._check() does not perform. See the function docstring | |||
for details. | |||
display(btree) displays the internal structure of a BTree (TreeSet, etc) to | |||
stdout. | |||
CAUTION: When a BTree node has only a single bucket child, it can be | |||
impossible to get at the bucket from Python code (__getstate__() may squash | |||
the bucket object out of existence, as a pickling storage optimization). In | |||
such a case, the code here synthesizes a temporary bucket with the same keys | |||
(and values, if the bucket is of a mapping type). This has no first-order | |||
consequences, but can mislead if you pay close attention to reported object | |||
addresses and/or object identity (the synthesized bucket has an address | |||
that doesn't exist in the actual BTree). | |||
""" | |||
from BTrees.IFBTree import IFBTree, IFBucket, IFSet, IFTreeSet | |||
from BTrees.IFBTree import IFBTreePy, IFBucketPy, IFSetPy, IFTreeSetPy | |||
from BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet | |||
from BTrees.IIBTree import IIBTreePy, IIBucketPy, IISetPy, IITreeSetPy | |||
from BTrees.IOBTree import IOBTree, IOBucket, IOSet, IOTreeSet | |||
from BTrees.IOBTree import IOBTreePy, IOBucketPy, IOSetPy, IOTreeSetPy | |||
from BTrees.LFBTree import LFBTree, LFBucket, LFSet, LFTreeSet | |||
from BTrees.LFBTree import LFBTreePy, LFBucketPy, LFSetPy, LFTreeSetPy | |||
from BTrees.LLBTree import LLBTree, LLBucket, LLSet, LLTreeSet | |||
from BTrees.LLBTree import LLBTreePy, LLBucketPy, LLSetPy, LLTreeSetPy | |||
from BTrees.LOBTree import LOBTree, LOBucket, LOSet, LOTreeSet | |||
from BTrees.LOBTree import LOBTreePy, LOBucketPy, LOSetPy, LOTreeSetPy | |||
from BTrees.OIBTree import OIBTree, OIBucket, OISet, OITreeSet | |||
from BTrees.OIBTree import OIBTreePy, OIBucketPy, OISetPy, OITreeSetPy | |||
from BTrees.OLBTree import OLBTree, OLBucket, OLSet, OLTreeSet | |||
from BTrees.OLBTree import OLBTreePy, OLBucketPy, OLSetPy, OLTreeSetPy | |||
from BTrees.OOBTree import OOBTree, OOBucket, OOSet, OOTreeSet | |||
from BTrees.OOBTree import OOBTreePy, OOBucketPy, OOSetPy, OOTreeSetPy | |||
from BTrees.utils import positive_id | |||
from BTrees.utils import oid_repr | |||
TYPE_UNKNOWN, TYPE_BTREE, TYPE_BUCKET = range(3) | |||
from ._compat import compare | |||
_type2kind = {} | |||
for kv in ('OO', | |||
'II', 'IO', 'OI', 'IF', | |||
'LL', 'LO', 'OL', 'LF', | |||
): | |||
for name, kind in ( | |||
('BTree', (TYPE_BTREE, True)), | |||
('Bucket', (TYPE_BUCKET, True)), | |||
('TreeSet', (TYPE_BTREE, False)), | |||
('Set', (TYPE_BUCKET, False)), | |||
): | |||
_type2kind[globals()[kv+name]] = kind | |||
py = kv + name + 'Py' | |||
_type2kind[globals()[py]] = kind | |||
# Return pair | |||
# | |||
# TYPE_BTREE or TYPE_BUCKET, is_mapping | |||
def classify(obj): | |||
return _type2kind[type(obj)] | |||
BTREE_EMPTY, BTREE_ONE, BTREE_NORMAL = range(3) | |||
# If the BTree is empty, returns | |||
# | |||
# BTREE_EMPTY, [], [] | |||
# | |||
# If the BTree has only one bucket, sometimes returns | |||
# | |||
# BTREE_ONE, bucket_state, None | |||
# | |||
# Else returns | |||
# | |||
# BTREE_NORMAL, list of keys, list of kids | |||
# | |||
# and the list of kids has one more entry than the list of keys. | |||
# | |||
# BTree.__getstate__() docs: | |||
# | |||
# For an empty BTree (self->len == 0), None. | |||
# | |||
# For a BTree with one child (self->len == 1), and that child is a bucket, | |||
# and that bucket has a NULL oid, a one-tuple containing a one-tuple | |||
# containing the bucket's state: | |||
# | |||
# ( | |||
# ( | |||
# child[0].__getstate__(), | |||
# ), | |||
# ) | |||
# | |||
# Else a two-tuple. The first element is a tuple interleaving the BTree's | |||
# keys and direct children, of size 2*self->len - 1 (key[0] is unused and | |||
# is not saved). The second element is the firstbucket: | |||
# | |||
# ( | |||
# (child[0], key[1], child[1], key[2], child[2], ..., | |||
# key[len-1], child[len-1]), | |||
# self->firstbucket | |||
# ) | |||
_btree2bucket = {} | |||
for kv in ('OO', | |||
'II', 'IO', 'OI', 'IF', | |||
'LL', 'LO', 'OL', 'LF', | |||
): | |||
_btree2bucket[globals()[kv+'BTree']] = globals()[kv+'Bucket'] | |||
py = kv + 'BTreePy' | |||
_btree2bucket[globals()[py]] = globals()[kv+'BucketPy'] | |||
_btree2bucket[globals()[kv+'TreeSet']] = globals()[kv+'Set'] | |||
py = kv + 'TreeSetPy' | |||
_btree2bucket[globals()[kv+'TreeSetPy']] = globals()[kv+'SetPy'] | |||
def crack_btree(t, is_mapping): | |||
state = t.__getstate__() | |||
if state is None: | |||
return BTREE_EMPTY, [], [] | |||
assert isinstance(state, tuple) | |||
if len(state) == 1: | |||
state = state[0] | |||
assert isinstance(state, tuple) and len(state) == 1 | |||
state = state[0] | |||
return BTREE_ONE, state, None | |||
assert len(state) == 2 | |||
data, firstbucket = state | |||
n = len(data) | |||
assert n & 1 | |||
kids = [] | |||
keys = [] | |||
i = 0 | |||
for x in data: | |||
if i & 1: | |||
keys.append(x) | |||
else: | |||
kids.append(x) | |||
i += 1 | |||
return BTREE_NORMAL, keys, kids | |||
# Returns | |||
# | |||
# keys, values # for a mapping; len(keys) == len(values) in this case | |||
# or | |||
# keys, [] # for a set | |||
# | |||
# bucket.__getstate__() docs: | |||
# | |||
# For a set bucket (self->values is NULL), a one-tuple or two-tuple. The | |||
# first element is a tuple of keys, of length self->len. The second element | |||
# is the next bucket, present if and only if next is non-NULL: | |||
# | |||
# ( | |||
# (keys[0], keys[1], ..., keys[len-1]), | |||
# <self->next iff non-NULL> | |||
# ) | |||
# | |||
# For a mapping bucket (self->values is not NULL), a one-tuple or two-tuple. | |||
# The first element is a tuple interleaving keys and values, of length | |||
# 2 * self->len. The second element is the next bucket, present iff next is | |||
# non-NULL: | |||
# | |||
# ( | |||
# (keys[0], values[0], keys[1], values[1], ..., | |||
# keys[len-1], values[len-1]), | |||
# <self->next iff non-NULL> | |||
# ) | |||
def crack_bucket(b, is_mapping): | |||
state = b.__getstate__() | |||
assert isinstance(state, tuple) | |||
assert 1 <= len(state) <= 2 | |||
data = state[0] | |||
if not is_mapping: | |||
return data, [] | |||
keys = [] | |||
values = [] | |||
n = len(data) | |||
assert n & 1 == 0 | |||
i = 0 | |||
for x in data: | |||
if i & 1: | |||
values.append(x) | |||
else: | |||
keys.append(x) | |||
i += 1 | |||
return keys, values | |||
def type_and_adr(obj): | |||
if hasattr(obj, '_p_oid'): | |||
oid = oid_repr(obj._p_oid) | |||
else: | |||
oid = 'None' | |||
return "%s (0x%x oid=%s)" % (type(obj).__name__, positive_id(obj), oid) | |||
# Walker implements a depth-first search of a BTree (or TreeSet or Set or | |||
# Bucket). Subclasses must implement the visit_btree() and visit_bucket() | |||
# methods, and arrange to call the walk() method. walk() calls the | |||
# visit_XYZ() methods once for each node in the tree, in depth-first | |||
# left-to-right order. | |||
class Walker: | |||
def __init__(self, obj): | |||
self.obj = obj | |||
# obj is the BTree (BTree or TreeSet). | |||
# path is a list of indices, from the root. For example, if a BTree node | |||
# is child[5] of child[3] of the root BTree, [3, 5]. | |||
# parent is the parent BTree object, or None if this is the root BTree. | |||
# is_mapping is True for a BTree and False for a TreeSet. | |||
# keys is a list of the BTree's internal keys. | |||
# kids is a list of the BTree's children. | |||
# If the BTree is an empty root node, keys == kids == []. | |||
# Else len(kids) == len(keys) + 1. | |||
# lo and hi are slice bounds on the values the elements of keys *should* | |||
# lie in (lo inclusive, hi exclusive). lo is None if there is no lower | |||
# bound known, and hi is None if no upper bound is known. | |||
def visit_btree(self, obj, path, parent, is_mapping, | |||
keys, kids, lo, hi): | |||
raise NotImplementedError | |||
# obj is the bucket (Bucket or Set). | |||
# path is a list of indices, from the root. For example, if a bucket | |||
# node is child[5] of child[3] of the root BTree, [3, 5]. | |||
# parent is the parent BTree object. | |||
# is_mapping is True for a Bucket and False for a Set. | |||
# keys is a list of the bucket's keys. | |||
# values is a list of the bucket's values. | |||
# If is_mapping is false, values == []. Else len(keys) == len(values). | |||
# lo and hi are slice bounds on the values the elements of keys *should* | |||
# lie in (lo inclusive, hi exclusive). lo is None if there is no lower | |||
# bound known, and hi is None if no upper bound is known. | |||
def visit_bucket(self, obj, path, parent, is_mapping, | |||
keys, values, lo, hi): | |||
raise NotImplementedError | |||
def walk(self): | |||
obj = self.obj | |||
path = [] | |||
stack = [(obj, path, None, None, None)] | |||
while stack: | |||
obj, path, parent, lo, hi = stack.pop() | |||
kind, is_mapping = classify(obj) | |||
if kind is TYPE_BTREE: | |||
bkind, keys, kids = crack_btree(obj, is_mapping) | |||
if bkind is BTREE_NORMAL: | |||
# push the kids, in reverse order (so they're popped off | |||
# the stack in forward order) | |||
n = len(kids) | |||
for i in range(len(kids)-1, -1, -1): | |||
newlo, newhi = lo, hi | |||
if i < n-1: | |||
newhi = keys[i] | |||
if i > 0: | |||
newlo = keys[i-1] | |||
stack.append((kids[i], | |||
path + [i], | |||
obj, | |||
newlo, | |||
newhi)) | |||
elif bkind is BTREE_EMPTY: | |||
pass | |||
else: | |||
assert bkind is BTREE_ONE | |||
# Yuck. There isn't a bucket object to pass on, as | |||
# the bucket state is embedded directly in the BTree | |||
# state. Synthesize a bucket. | |||
assert kids is None # and "keys" is really the bucket | |||
# state | |||
bucket = _btree2bucket[type(obj)]() | |||
bucket.__setstate__(keys) | |||
stack.append((bucket, | |||
path + [0], | |||
obj, | |||
lo, | |||
hi)) | |||
keys = [] | |||
kids = [bucket] | |||
self.visit_btree(obj, | |||
path, | |||
parent, | |||
is_mapping, | |||
keys, | |||
kids, | |||
lo, | |||
hi) | |||
else: | |||
assert kind is TYPE_BUCKET | |||
keys, values = crack_bucket(obj, is_mapping) | |||
self.visit_bucket(obj, | |||
path, | |||
parent, | |||
is_mapping, | |||
keys, | |||
values, | |||
lo, | |||
hi) | |||
class Checker(Walker): | |||
def __init__(self, obj): | |||
Walker.__init__(self, obj) | |||
self.errors = [] | |||
def check(self): | |||
self.walk() | |||
if self.errors: | |||
s = "Errors found in %s:" % type_and_adr(self.obj) | |||
self.errors.insert(0, s) | |||
s = "\n".join(self.errors) | |||
raise AssertionError(s) | |||
def visit_btree(self, obj, path, parent, is_mapping, | |||
keys, kids, lo, hi): | |||
self.check_sorted(obj, path, keys, lo, hi) | |||
def visit_bucket(self, obj, path, parent, is_mapping, | |||
keys, values, lo, hi): | |||
self.check_sorted(obj, path, keys, lo, hi) | |||
def check_sorted(self, obj, path, keys, lo, hi): | |||
i, n = 0, len(keys) | |||
for x in keys: | |||
# lo or hi are ommitted by supplying None. Thus the not | |||
# None checkes below. | |||
if lo is not None and not compare(lo, x) <= 0: | |||
s = "key %r < lower bound %r at index %d" % (x, lo, i) | |||
self.complain(s, obj, path) | |||
if hi is not None and not compare(x, hi) < 0: | |||
s = "key %r >= upper bound %r at index %d" % (x, hi, i) | |||
self.complain(s, obj, path) | |||
if i < n-1 and not compare(x, keys[i+1]) < 0: | |||
s = "key %r at index %d >= key %r at index %d" % ( | |||
x, i, keys[i+1], i+1) | |||
self.complain(s, obj, path) | |||
i += 1 | |||
def complain(self, msg, obj, path): | |||
s = "%s, in %s, path from root %s" % ( | |||
msg, | |||
type_and_adr(obj), | |||
".".join(map(str, path))) | |||
self.errors.append(s) | |||
class Printer(Walker): #pragma NO COVER | |||
def __init__(self, obj): | |||
Walker.__init__(self, obj) | |||
def display(self): | |||
self.walk() | |||
def visit_btree(self, obj, path, parent, is_mapping, | |||
keys, kids, lo, hi): | |||
indent = " " * len(path) | |||
print("%s%s %s with %d children" % ( | |||
indent, | |||
".".join(map(str, path)), | |||
type_and_adr(obj), | |||
len(kids))) | |||
indent += " " | |||
n = len(keys) | |||
for i in range(n): | |||
print("%skey %d: %r" % (indent, i, keys[i])) | |||
def visit_bucket(self, obj, path, parent, is_mapping, | |||
keys, values, lo, hi): | |||
indent = " " * len(path) | |||
print("%s%s %s with %d keys" % ( | |||
indent, | |||
".".join(map(str, path)), | |||
type_and_adr(obj), | |||
len(keys))) | |||
indent += " " | |||
n = len(keys) | |||
for i in range(n): | |||
print("%skey %d: %r" % (indent, i, keys[i]),) | |||
if is_mapping: | |||
print("value %r" % (values[i],)) | |||
def check(btree): | |||
"""Check internal value-based invariants in a BTree or TreeSet. | |||
The btree._check() method checks internal C-level pointer consistency. | |||
The check() function here checks value-based invariants: whether the | |||
keys in leaf bucket and internal nodes are in strictly increasing order, | |||
and whether they all lie in their expected range. The latter is a subtle | |||
invariant that can't be checked locally -- it requires propagating | |||
range info down from the root of the tree, and modifying it at each | |||
level for each child. | |||
Raises AssertionError if anything is wrong, with a string detail | |||
explaining the problems. The entire tree is checked before | |||
AssertionError is raised, and the string detail may be large (depending | |||
on how much went wrong). | |||
""" | |||
Checker(btree).check() | |||
def display(btree): #pragma NO COVER | |||
"Display the internal structure of a BTree, Bucket, TreeSet or Set." | |||
Printer(btree).display() |