Commit eefcf53e authored by Boxiang Sun's avatar Boxiang Sun

apply 4.2.2 changes from Acquistion upstream.

The revision is 3f7211c67253983a051b4add25139c0520849de7
parent 09792502
File added
Changelog
=========
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.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.
include *.txt
include *.rst
recursive-include include *
recursive-include src *
global-exclude *.dll
global-exclude *.pyc
global-exclude *.pyo
global-exclude *.so
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
>>> a.report() # raises an attribute error
Traceback (most recent call last):
...
AttributeError: color
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:
... 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)
1
.. 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.
......@@ -208,10 +208,8 @@ static PyExtensionClass NAME ## Type = { PyObject_HEAD_INIT(NULL) 0, # NAME, \
PyExtensionClassCAPI->PyECMethod_New_((CALLABLE),(INST))
/* Return the instance that is bound by an extension class method. */
// Pyston change:
#define PyECMethod_Self(M) \
(PyMethod_Check((M)) ? PyMethod_GET_SELF(M) : NULL)
// (PyMethod_Check((M)) ? ((PyMethodObject*)(M))->im_self : NULL)
(PyMethod_Check((M)) ? ((PyMethodObject*)(M))->im_self : NULL)
/* Check whether an object has an __of__ method for returning itself
in the context of it's container. */
......
......@@ -14,31 +14,65 @@
"""Setup for the Acquisition distribution
"""
import os
import platform
import sys
from setuptools import setup, find_packages, Extension
setup(name='Acquisition',
version = '2.13.9',
url='http://pypi.python.org/pypi/Acquisition',
with open('README.rst') as f:
README = f.read()
with open('CHANGES.rst') as f:
CHANGES = f.read()
# PyPy won't build the extension.
py_impl = getattr(platform, 'python_implementation', lambda: None)
is_pypy = py_impl() == 'PyPy'
is_pure = 'PURE_PYTHON' in os.environ
py3k = sys.version_info >= (3, )
if is_pypy or is_pure or py3k:
ext_modules = []
else:
ext_modules=[Extension("Acquisition._Acquisition",
[os.path.join('src', 'Acquisition',
'_Acquisition.c')],
include_dirs=['include', 'src']),
]
setup(
name='Acquisition',
version='4.2.2',
url='https://github.com/zopefoundation/Acquisition',
license='ZPL 2.1',
description="Acquisition is a mechanism that allows objects to obtain "
"attributes from the containment hierarchy they're in.",
author='Zope Foundation and Contributors',
author_email='zope-dev@zope.org',
long_description=open(
os.path.join('src', 'Acquisition', 'README.txt')).read() + '\n' +
open('CHANGES.txt').read(),
long_description='\n\n'.join([README, CHANGES]),
packages=find_packages('src'),
package_dir={'': 'src'},
ext_modules=[Extension("Acquisition._Acquisition",
[os.path.join('src', 'Acquisition',
'_Acquisition.c')],
include_dirs=['include', 'src']),
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.2",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
],
ext_modules=ext_modules,
install_requires=[
'ExtensionClass<4.0.dev',
'zope.interface'],
'ExtensionClass >= 4.1.1',
'zope.interface',
],
include_package_data=True,
zip_safe=False,
)
)
......@@ -452,6 +452,11 @@ Wrapper_acquire(Wrapper *self, PyObject *oname,
PyObject *filter, PyObject *extra, PyObject *orig,
int explicit, int containment);
static PyObject *
Wrapper_findattr_name(Wrapper *self, char* name, PyObject *oname,
PyObject *filter, PyObject *extra, PyObject *orig,
int sob, int sco, int explicit, int containment);
static PyObject *
Wrapper_findattr(Wrapper *self, PyObject *oname,
PyObject *filter, PyObject *extra, PyObject *orig,
......@@ -474,16 +479,48 @@ Wrapper_findattr(Wrapper *self, PyObject *oname,
attribute.
*/
{
PyObject *r, *v, *tb, *tmp;
PyObject *tmp=NULL;
PyObject *result;
char *name="";
if (PyString_Check(oname)) name=PyString_AS_STRING(oname);
if (PyUnicode_Check(oname)) {
else if (PyUnicode_Check(oname)) {
tmp=PyUnicode_AsASCIIString(oname);
if (tmp==NULL) return NULL;
name=PyString_AS_STRING(tmp);
Py_DECREF(tmp);
}
result = Wrapper_findattr_name(self, name, oname, filter, extra, orig,
sob, sco, explicit, containment);
Py_XDECREF(tmp);
return result;
}
static PyObject *
Wrapper_findattr_name(Wrapper *self, char* name, PyObject *oname,
PyObject *filter, PyObject *extra, PyObject *orig,
int sob, int sco, int explicit, int containment)
/*
Exactly the same as Wrapper_findattr, except that the incoming
Python name string/unicode object has already been decoded
into a C string. This helper function lets us more easily manage
the lifetime of any temporary allocations.
This function uses Wrapper_acquire, which only takes the original
oname value, not the decoded value. That function can call back into
this one (via Wrapper_findattr). Although that may lead to a few
temporary allocations as we walk through the containment hierarchy,
it is correct: This function may modify its internal view of the
`name` value, and if that were propagated up the hierarchy
the incorrect name may be looked up.
*/
{
PyObject *r, *v, *tb;
if ((*name=='a' && name[1]=='q' && name[2]=='_') ||
(strcmp(name, "__parent__")==0))
{
......@@ -551,6 +588,14 @@ Wrapper_findattr(Wrapper *self, PyObject *oname,
Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb);
r=NULL;
}
/* Deal with mixed __parent__ / aq_parent circles */
else if (self->container && isWrapper(self->container) &&
WRAPPER(self->container)->container &&
self == WRAPPER(WRAPPER(self->container)->container)) {
PyErr_SetString(PyExc_RuntimeError,
"Recursion detected in acquisition wrapper");
return NULL;
}
/* normal attribute lookup */
else if ((r=PyObject_GetAttr(self->obj,oname)))
{
......@@ -642,14 +687,16 @@ Wrapper_acquire(Wrapper *self, PyObject *oname,
acquisition wrapper in the first place (see above). */
else if ((r = PyObject_GetAttr(self->container, py__parent__)))
{
ASSIGN(self->container, newWrapper(self->container, r,
(PyTypeObject*)&Wrappertype));
/* Don't search the container when the parent of the parent
is the same object as 'self' */
if (WRAPPER(r)->obj == WRAPPER(self)->obj)
if (r == WRAPPER(self)->obj)
sco=0;
else if (WRAPPER(r)->obj == WRAPPER(self)->obj)
sco=0;
ASSIGN(self->container, newWrapper(self->container, r,
(PyTypeObject*)&Wrappertype));
Py_DECREF(r); /* don't need __parent__ anymore */
r=Wrapper_findattr((Wrapper*)self->container,
......@@ -722,62 +769,73 @@ Wrapper_getattro(Wrapper *self, PyObject *oname)
static PyObject *
Xaq_getattro(Wrapper *self, PyObject *oname)
{
PyObject *tmp;
PyObject *tmp=NULL;
PyObject *result;
char *name="";
/* Special case backward-compatible acquire method. */
if (PyString_Check(oname)) name=PyString_AS_STRING(oname);
if (PyUnicode_Check(oname)) {
else if (PyUnicode_Check(oname)) {
tmp=PyUnicode_AsASCIIString(oname);
if (tmp==NULL) return NULL;
name=PyString_AS_STRING(tmp);
Py_DECREF(tmp);
}
if (*name=='a' && name[1]=='c' && strcmp(name+2,"quire")==0)
return Py_FindAttr(OBJECT(self),oname);
result = Py_FindAttr(OBJECT(self),oname);
if (self->obj || self->container)
return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 0, 0, 0);
else if (self->obj || self->container)
result = Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 0, 0, 0);
/* Maybe we are getting initialized? */
return Py_FindAttr(OBJECT(self),oname);
else result = Py_FindAttr(OBJECT(self),oname);
Py_XDECREF(tmp);
return result;
}
static int
Wrapper_setattro(Wrapper *self, PyObject *oname, PyObject *v)
{
PyObject *tmp;
PyObject *tmp=NULL;
char *name="";
int result;
/* Allow assignment to parent, to change context. */
if (PyString_Check(oname)) name=PyString_AS_STRING(oname);
if (PyUnicode_Check(oname)) {
else if (PyUnicode_Check(oname)) {
tmp=PyUnicode_AsASCIIString(oname);
if (tmp==NULL) return -1;
name=PyString_AS_STRING(tmp);
Py_DECREF(tmp);
}
if ((*name=='a' && name[1]=='q' && name[2]=='_'
&& strcmp(name+3,"parent")==0) || (strcmp(name, "__parent__")==0))
{
Py_XINCREF(v);
ASSIGN(self->container, v);
return 0;
result = 0;
}
if (self->obj)
else if (self->obj)
{
/* Unwrap passed in wrappers! */
while (v && isWrapper(v))
v=WRAPPER(v)->obj;
if (v) return PyObject_SetAttr(self->obj, oname, v);
else return PyObject_DelAttr(self->obj, oname);
if (v) result = PyObject_SetAttr(self->obj, oname, v);
else result = PyObject_DelAttr(self->obj, oname);
}
else
{
PyErr_SetString(PyExc_AttributeError,
"Attempt to set attribute on empty acquisition wrapper");
return -1;
result = -1;
}
Py_XDECREF(tmp);
return result;
}
static int
......@@ -1280,13 +1338,6 @@ Wrapper_acquire_method(Wrapper *self, PyObject *args, PyObject *kw)
if (filter==Py_None) filter=0;
/* DM 2005-08-25: argument "default" ignored -- fix it! */
# if 0
return Wrapper_findattr(self,name,filter,extra,OBJECT(self),1,
explicit ||
self->ob_type==(PyTypeObject*)&Wrappertype,
explicit, containment);
# else
result = Wrapper_findattr(self,name,filter,extra,OBJECT(self),1,
explicit ||
self->ob_type==(PyTypeObject*)&Wrappertype,
......@@ -1304,7 +1355,6 @@ Wrapper_acquire_method(Wrapper *self, PyObject *args, PyObject *kw)
}
}
return result;
# endif
}
/* forward declaration so that we can use it in Wrapper_inContextOf */
......
from __future__ import absolute_import, print_function
# pylint:disable=W0212,R0911,R0912
import os
import operator
import sys
import types
import weakref
import ExtensionClass
from zope.interface import classImplements
from _Acquisition import *
from interfaces import IAcquirer
from interfaces import IAcquisitionWrapper
from .interfaces import IAcquirer
from .interfaces import IAcquisitionWrapper
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
raise AttributeError(name) # pragma: no cover (this line cannot be reached)
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),
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)
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,
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 (Hits under Py3)
return str(aq_self)
__binary_special_methods__ = [
# general numeric
'__add__',
'__sub__',
'__mul__',
'__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__',
'__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 1
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 0
if 'PURE_PYTHON' not in os.environ: # pragma: no cover
try:
from ._Acquisition import *
except ImportError:
pass
classImplements(Explicit, IAcquirer)
classImplements(ExplicitAcquisitionWrapper, IAcquisitionWrapper)
......
......@@ -31,7 +31,7 @@
>>> class A(Acquisition.Implicit):
... def report(self):
... print self.color
... print(self.color)
>>> a = A()
>>> c = C()
......@@ -117,7 +117,7 @@
automatically obtained from the environment. Instead, the
method 'aq_aquire' must be used, as in::
print c.a.aq_acquire('color')
print(c.a.aq_acquire('color'))
To support explicit acquisition, an object should inherit
from the mix-in class 'Acquisition.Explicit'.
......@@ -170,7 +170,7 @@
... __roles__ = Acquisition.Acquired
>>> c.x = C()
>>> c.x.__roles__
>>> c.x.__roles__ # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
AttributeError: __roles__
......@@ -231,7 +231,7 @@
>>> def find_nice(self, ancestor, name, object, extra):
... return hasattr(object,'isNice') and object.isNice
>>> print a.b.c.aq_acquire('p', find_nice)
>>> 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
......@@ -327,25 +327,64 @@
"Environmental Acquisition--A New Inheritance-Like Abstraction Mechanism",
http://www.bell-labs.com/people/cope/oopsla/Oopsla96TechnicalProgramAbstracts.html#GilLorenz,
OOPSLA '96 Proceedings, ACM SIG-PLAN, October, 1996
$Id$
"""
from __future__ import print_function
import gc
import unittest
import sys
import platform
import operator
from doctest import DocTestSuite, DocFileSuite
import ExtensionClass
import Acquisition
if sys.version_info >= (3,):
PY3 = True
PY2 = False
def unicode(self):
# For test purposes, redirect the unicode
# to the type of the object, just like Py2 did
try:
return type(self).__unicode__(self)
except AttributeError as e:
return type(self).__str__(self)
long = int
else:
PY2 = True
PY3 = False
py_impl = getattr(platform, 'python_implementation', lambda: None)
PYPY = py_impl() == 'PyPy'
if not hasattr(gc, 'get_threshold'):
# PyPy
gc.get_threshold = lambda: ()
gc.set_threshold = lambda *x: None
AQ_PARENT = unicode('aq_parent')
UNICODE_WAS_CALLED = unicode('unicode was called')
STR_WAS_CALLED = unicode('str was called')
TRUE = unicode('True')
class I(Acquisition.Implicit):
def __init__(self, id):
self.id = id
def __repr__(self):
return self.id
class E(Acquisition.Explicit):
def __init__(self, id):
self.id = id
def __repr__(self):
return self.id
def test_unwrapped():
"""
>>> c = I('unwrapped')
......@@ -409,10 +448,9 @@ def test_unwrapped():
>>> Acquisition.aq_self(c) is c
1
"""
def test_simple():
"""
>>> a = I('a')
......@@ -561,13 +599,10 @@ def test__of__exception():
case the 'value' argument of the filter was NULL, which caused
a segfault when being accessed.
>>> class UserError(Exception):
... pass
...
>>> class X(Acquisition.Implicit):
... def __of__(self, parent):
... if Acquisition.aq_base(parent) is not parent:
... raise UserError, 'ack'
... raise NotImplementedError('ack')
... return X.inheritedAttribute('__of__')(self, parent)
...
>>> a = I('a')
......@@ -577,10 +612,11 @@ def test__of__exception():
... lambda self, object, name, value, extra: repr(value))
Traceback (most recent call last):
...
UserError: ack
NotImplementedError: ack
"""
def test_muliple():
r"""
>>> a = I('a')
......@@ -830,6 +866,7 @@ def test_muliple():
"""
def test_pinball():
r"""
>>> a = I('a')
......@@ -946,6 +983,7 @@ def test_pinball():
"""
def test_explicit():
"""
>>> a = E('a')
......@@ -1079,6 +1117,7 @@ def test_explicit():
"""
def test_mixed_explicit_and_explicit():
"""
>>> a = I('a')
......@@ -1227,7 +1266,8 @@ def test_aq_inContextOf():
>>> class A(Acquisition.Implicit):
... def hi(self):
... print "%s()" % self.__class__.__name__, self.color
... print(self.__class__.__name__)
... print(self.color)
>>> class Location(object):
... __parent__ = None
......@@ -1235,15 +1275,17 @@ def test_aq_inContextOf():
>>> b=B()
>>> b.a=A()
>>> b.a.hi()
A() red
A
red
>>> b.a.color='green'
>>> b.a.hi()
A() green
A
green
>>> try:
... A().hi()
... raise 'Program error', 'spam'
... raise RuntimeError( 'Program error', 'spam')
... except AttributeError: pass
A()
A
New test for wrapper comparisons.
......@@ -1325,6 +1367,7 @@ def test_aq_inContextOf():
0
"""
def test_AqAlg():
"""
>>> A=I('A')
......@@ -1339,7 +1382,7 @@ def test_AqAlg():
[A]
>>> Acquisition.aq_chain(A, 1)
[A]
>>> map(Acquisition.aq_base, Acquisition.aq_chain(A, 1))
>>> list(map(Acquisition.aq_base, Acquisition.aq_chain(A, 1)))
[A]
>>> A.C
C
......@@ -1347,7 +1390,7 @@ def test_AqAlg():
[C, A]
>>> Acquisition.aq_chain(A.C, 1)
[C, A]
>>> map(Acquisition.aq_base, Acquisition.aq_chain(A.C, 1))
>>> list(map(Acquisition.aq_base, Acquisition.aq_chain(A.C, 1)))
[C, A]
>>> A.C.D
......@@ -1356,7 +1399,7 @@ def test_AqAlg():
[D, C, A]
>>> Acquisition.aq_chain(A.C.D, 1)
[D, C, A]
>>> map(Acquisition.aq_base, Acquisition.aq_chain(A.C.D, 1))
>>> list(map(Acquisition.aq_base, Acquisition.aq_chain(A.C.D, 1)))
[D, C, A]
>>> A.B.C
......@@ -1365,7 +1408,7 @@ def test_AqAlg():
[C, B, A]
>>> Acquisition.aq_chain(A.B.C, 1)
[C, A]
>>> map(Acquisition.aq_base, Acquisition.aq_chain(A.B.C, 1))
>>> list(map(Acquisition.aq_base, Acquisition.aq_chain(A.B.C, 1)))
[C, A]
>>> A.B.C.D
......@@ -1374,7 +1417,7 @@ def test_AqAlg():
[D, C, B, A]
>>> Acquisition.aq_chain(A.B.C.D, 1)
[D, C, A]
>>> map(Acquisition.aq_base, Acquisition.aq_chain(A.B.C.D, 1))
>>> list(map(Acquisition.aq_base, Acquisition.aq_chain(A.B.C.D, 1)))
[D, C, A]
......@@ -1386,6 +1429,7 @@ def test_AqAlg():
"""
def test_explicit_acquisition():
"""
>>> from ExtensionClass import Base
......@@ -1396,24 +1440,28 @@ def test_explicit_acquisition():
>>> class A(Acquisition.Explicit):
... def hi(self):
... print self.__class__.__name__, self.acquire('color')
... print(self.__class__.__name__)
... print(self.acquire('color'))
>>> b=B()
>>> b.a=A()
>>> b.a.hi()
A red
A
red
>>> b.a.color='green'
>>> b.a.hi()
A green
A
green
>>> try:
... A().hi()
... raise 'Program error', 'spam'
... raise RuntimeError('Program error', 'spam')
... except AttributeError: pass
A
"""
def test_creating_wrappers_directly():
"""
>>> from ExtensionClass import Base
......@@ -1430,7 +1478,7 @@ def test_creating_wrappers_directly():
>>> w.color
'red'
>>> w = ImplicitAcquisitionWrapper(a.b)
>>> w = ImplicitAcquisitionWrapper(a.b) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
TypeError: __init__() takes exactly 2 arguments (1 given)
......@@ -1452,22 +1500,23 @@ def test_creating_wrappers_directly():
Note that messing with the wrapper won't in any way affect the
wrapped object:
>>> Acquisition.aq_base(w).__parent__
>>> Acquisition.aq_base(w).__parent__ # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
AttributeError: __parent__
>>> w = ImplicitAcquisitionWrapper()
>>> w = ImplicitAcquisitionWrapper() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
TypeError: __init__() takes exactly 2 arguments (0 given)
>>> w = ImplicitAcquisitionWrapper(obj=1)
>>> w = ImplicitAcquisitionWrapper(obj=1) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
...
TypeError: kwyword arguments not allowed
"""
def test_cant_pickle_acquisition_wrappers_classic():
"""
>>> import pickle
......@@ -1515,6 +1564,7 @@ def test_cant_pickle_acquisition_wrappers_classic():
TypeError: Can't pickle objects in acquisition wrappers.
"""
def test_cant_pickle_acquisition_wrappers_newstyle():
"""
>>> import pickle
......@@ -1562,9 +1612,13 @@ def test_cant_pickle_acquisition_wrappers_newstyle():
TypeError: Can't pickle objects in acquisition wrappers.
"""
def test_cant_persist_acquisition_wrappers_classic():
"""
>>> import cPickle
>>> try:
... import cPickle
... except ImportError:
... import pickle as cPickle
>>> class X:
... _p_oid = '1234'
......@@ -1589,8 +1643,8 @@ def test_cant_persist_acquisition_wrappers_classic():
Check custom pickler:
>>> from cStringIO import StringIO
>>> file = StringIO()
>>> from io import BytesIO
>>> file = BytesIO()
>>> pickler = cPickle.Pickler(file, 1)
>>> pickler.dump(w)
......@@ -1601,29 +1655,36 @@ def test_cant_persist_acquisition_wrappers_classic():
Check custom pickler with a persistent_id method matching the semantics
in ZODB.serialize.ObjectWriter.persistent_id:
>>> file = StringIO()
>>> file = BytesIO()
>>> pickler = cPickle.Pickler(file, 1)
>>> def persistent_id(obj):
... if not hasattr(obj, '_p_oid'): return None
... klass = type(obj)
... oid = obj._p_oid
... if hasattr(klass, '__getnewargs__'):
... assert klass.__getnewargs__(obj) == () # Coverage, make sure it can be called
... return oid
... return 'class_and_oid', klass
>>> pickler.inst_persistent_id = persistent_id
>>> try: pickler.inst_persistent_id = persistent_id
... except AttributeError: pass
>>> pickler.persistent_id = persistent_id #PyPy and Py3k
>>> _ = pickler.dump(w)
>>> state = file.getvalue()
>>> '1234' in state
>>> b'1234' in state
True
>>> 'class_and_oid' in state
>>> b'class_and_oid' in state
False
"""
def test_cant_persist_acquisition_wrappers_newstyle():
"""
>>> import cPickle
>>> try:
... import cPickle
... except ImportError:
... import pickle as cPickle
>>> class X(object):
... _p_oid = '1234'
......@@ -1648,8 +1709,8 @@ def test_cant_persist_acquisition_wrappers_newstyle():
Check custom pickler:
>>> from cStringIO import StringIO
>>> file = StringIO()
>>> from io import BytesIO
>>> file = BytesIO()
>>> pickler = cPickle.Pickler(file, 1)
>>> pickler.dump(w)
......@@ -1660,22 +1721,26 @@ def test_cant_persist_acquisition_wrappers_newstyle():
Check custom pickler with a persistent_id method matching the semantics
in ZODB.serialize.ObjectWriter.persistent_id:
>>> file = StringIO()
>>> file = BytesIO()
>>> pickler = cPickle.Pickler(file, 1)
>>> def persistent_id(obj):
... if not hasattr(obj, '_p_oid'): return None
... klass = type(obj)
... oid = obj._p_oid
... if hasattr(klass, '__getnewargs__'):
... return oid
... return 'class_and_oid', klass
>>> pickler.inst_persistent_id = persistent_id
>>> try: pickler.inst_persistent_id = persistent_id
... except AttributeError: pass
>>> pickler.persistent_id = persistent_id #PyPy and Py3k
>>> _ = pickler.dump(w)
>>> state = file.getvalue()
>>> '1234' in state
>>> b'1234' in state
True
>>> 'class_and_oid' in state
>>> b'class_and_oid' in state
False
"""
......@@ -1706,17 +1771,116 @@ def test_interfaces():
True
"""
try:
class Plain(object):
pass
Plain.__bases__ = (ExtensionClass.Base,)
except TypeError:
# Not supported
pass
else:
# Assigning to __bases__ is difficult under some versions of python.
# PyPy usually lets it, but CPython (3 esp) may not.
# In this example, you get:
# "TypeError: __bases__ assignment: 'Base' deallocator differs from 'object'"
# I don't know what the workaround is; the old one of using a dummy
# superclass no longer works. See http://bugs.python.org/issue672115
def test_mixin_post_class_definition():
"""
Mixing in Base after class definition doesn't break anything,
but also doesn't result in any wrappers.
>>> from ExtensionClass import Base
>>> class Plain(object):
... pass
>>> Plain.__bases__ == (object,)
True
>>> Plain.__bases__ = (Base,)
>>> isinstance(Plain(), Base)
True
Even after mixing in that base, when we request such an object
from an implicit acquiring base, it doesn't come out wrapped:
>>> from Acquisition import Implicit
>>> class I(Implicit):
... pass
>>> root = I()
>>> root.a = I()
>>> root.a.b = Plain()
>>> type(root.a.b) is Plain
True
This is because after the mixin, even though Plain is-a Base,
it's still not an Explicit/Implicit acquirer and provides
neither the `__of__` nor `__get__` methods necessary
(`__get__` is added as a consequence of `__of__` at class creation time):
>>> hasattr(Plain, '__get__')
False
>>> hasattr(Plain, '__of__')
False
"""
def test_mixin_base():
"""
We can mix-in Base as part of multiple inheritance.
>>> from ExtensionClass import Base
>>> class MyBase(object):
... pass
>>> class MixedIn(Base,MyBase):
... pass
>>> MixedIn.__bases__ == (Base,MyBase)
True
>>> isinstance(MixedIn(), Base)
True
Because it's not an acquiring object and doesn't provide `__of__`
or `__get__`, when accessed from implicit contexts it doesn't come
out wrapped:
>>> from Acquisition import Implicit
>>> class I(Implicit):
... pass
>>> root = I()
>>> root.a = I()
>>> root.a.b = MixedIn()
>>> type(root.a.b) is MixedIn
True
This is because after the mixin, even though Plain is-a Base,
it doesn't provide the `__of__` method used for wrapping, and so
the class definition code that would add the `__get__` method also
doesn't run:
>>> hasattr(MixedIn, '__of__')
False
>>> hasattr(MixedIn, '__get__')
False
"""
def show(x):
print showaq(x).strip()
print(showaq(x).strip())
def showaq(m_self, indent=''):
rval = ''
obj = m_self
base = getattr(obj, 'aq_base', obj)
try: id = base.id
except: id = str(base)
try: id = id()
except: pass
try:
id = base.id
except Exception:
id = str(base)
try:
id = id()
except Exception:
pass
if hasattr(obj, 'aq_self'):
if hasattr(obj.aq_self, 'aq_self'):
......@@ -1733,10 +1897,10 @@ def showaq(m_self, indent=''):
rval = rval + indent + id + "\n"
return rval
def test_Basic_gc():
"""Test to make sure that EC instances participate in GC
"""Test to make sure that EC instances participate in GC.
Note that PyPy always reports 0 collected objects even
though we can see its finalizers run.
>>> from ExtensionClass import Base
>>> import gc
......@@ -1749,7 +1913,7 @@ def test_Basic_gc():
...
... class C2(Base):
... def __del__(self):
... print 'removed'
... print('removed')
...
... a=C1('a')
... a.b = C1('a.b')
......@@ -1758,7 +1922,7 @@ def test_Basic_gc():
... ignore = gc.collect()
... del a
... removed = gc.collect()
... print removed > 0
... print(removed > 0 or PYPY)
removed
True
removed
......@@ -1767,9 +1931,10 @@ def test_Basic_gc():
>>> gc.set_threshold(*thresholds)
"""
def test_Wrapper_gc():
"""Test to make sure that EC instances participate in GC
"""Test to make sure that EC instances participate in GC.
Note that PyPy always reports 0 collected objects even
though we can see its finalizers run.
>>> import gc
>>> thresholds = gc.get_threshold()
......@@ -1778,7 +1943,7 @@ def test_Wrapper_gc():
>>> for B in I, E:
... class C:
... def __del__(self):
... print 'removed'
... print('removed')
...
... a=B('a')
... a.b = B('b')
......@@ -1787,7 +1952,7 @@ def test_Wrapper_gc():
... ignored = gc.collect()
... del a
... removed = gc.collect()
... removed > 0
... removed > 0 or PYPY
removed
True
removed
......@@ -1795,11 +1960,11 @@ def test_Wrapper_gc():
>>> gc.set_threshold(*thresholds)
"""
"""
def test_proxying():
"""Make sure that recent python slots are proxied.
def test_container_proxying():
"""Make sure that recent python container-related slots are proxied.
>>> import sys
>>> import Acquisition
......@@ -1808,18 +1973,21 @@ def test_proxying():
>>> class C(Acquisition.Implicit):
... def __getitem__(self, key):
... print 'getitem', key
... if isinstance(key, slice):
... print('slicing...')
... return (key.start,key.stop)
... print('getitem', key)
... if key == 4:
... raise IndexError
... return key
... def __contains__(self, key):
... print 'contains', repr(key)
... print('contains', repr(key))
... return key == 5
... def __iter__(self):
... print 'iterating...'
... print('iterating...')
... return iter((42,))
... def __getslice__(self, start, end):
... print 'slicing...'
... print('slicing...')
... return (start, end)
The naked class behaves like this:
......@@ -1837,7 +2005,7 @@ def test_proxying():
>>> c[5:10]
slicing...
(5, 10)
>>> c[5:] == (5, sys.maxsize)
>>> c[5:] == (5, sys.maxsize if PY2 else None)
slicing...
True
......@@ -1860,7 +2028,7 @@ def test_proxying():
>>> i.c[5:10]
slicing...
(5, 10)
>>> i.c[5:] == (5, sys.maxsize)
>>> i.c[5:] == (5, sys.maxsize if PY2 else None)
slicing...
True
......@@ -1872,18 +2040,21 @@ def test_proxying():
>>> class C(Acquisition.Explicit):
... def __getitem__(self, key):
... print 'getitem', key
... if isinstance(key, slice):
... print('slicing...')
... return (key.start,key.stop)
... print('getitem', key)
... if key == 4:
... raise IndexError
... return key
... def __contains__(self, key):
... print 'contains', repr(key)
... print('contains', repr(key))
... return key == 5
... def __iter__(self):
... print 'iterating...'
... print('iterating...')
... return iter((42,))
... def __getslice__(self, start, end):
... print 'slicing...'
... print('slicing...')
... return (start, end)
The naked class behaves like this:
......@@ -1901,7 +2072,7 @@ def test_proxying():
>>> c[5:10]
slicing...
(5, 10)
>>> c[5:] == (5, sys.maxsize)
>>> c[5:] == (5, sys.maxsize if PY2 else None)
slicing...
True
......@@ -1924,7 +2095,7 @@ def test_proxying():
>>> i.c[5:10]
slicing...
(5, 10)
>>> i.c[5:] == (5, sys.maxsize)
>>> i.c[5:] == (5, sys.maxsize if PY2 else None)
slicing...
True
......@@ -1938,14 +2109,14 @@ def test_proxying():
... return self.l[i]
>>> c1 = C()
>>> type(iter(c1))
<type 'iterator'>
>>> type(iter(c1)) #doctest: +ELLIPSIS
<... '...iterator'>
>>> list(c1)
[1, 2, 3]
>>> c2 = C().__of__(c1)
>>> type(iter(c2))
<type 'iterator'>
>>> type(iter(c2)) #doctest: +ELLIPSIS
<... '...iterator'>
>>> list(c2)
[1, 2, 3]
......@@ -1954,7 +2125,7 @@ def test_proxying():
>>> class C(Acquisition.Implicit):
... def __iter__(self):
... print 'iterating...'
... print('iterating...')
... for i in range(5):
... yield i, self.aq_parent.name
>>> c = C()
......@@ -1986,10 +2157,10 @@ def test_proxying():
>>> c = C()
>>> i = Impl()
>>> i.c = c
>>> list(i.c)
>>> list(i.c) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: iteration over non-sequence
TypeError: ...iter...
>>> class C(Acquisition.Implicit):
... def __iter__(self):
......@@ -1997,19 +2168,21 @@ def test_proxying():
>>> c = C()
>>> i = Impl()
>>> i.c = c
>>> list(i.c)
>>> list(i.c) #doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: iter() returned non-iterator of type 'list'
TypeError: iter() returned non-iterator...
"""
class Location(object):
__parent__ = None
class ECLocation(ExtensionClass.Base):
__parent__ = None
def test___parent__no_wrappers():
"""
Acquisition also works with objects that aren't wrappers, as long
......@@ -2061,6 +2234,7 @@ def test___parent__no_wrappers():
True
"""
def test_implicit_wrapper_as___parent__():
"""
Let's do the same test again, only now not all objects are of the
......@@ -2142,6 +2316,7 @@ def test_implicit_wrapper_as___parent__():
AttributeError: __parent__
"""
def test_explicit_wrapper_as___parent__():
"""
Let's do this test yet another time, with an explicit wrapper:
......@@ -2221,6 +2396,7 @@ def test_explicit_wrapper_as___parent__():
AttributeError: __parent__
"""
def test_implicit_wrapper_has_nonwrapper_as_aq_parent():
"""Let's do this the other way around: The root and the
intermediate parent is an object that doesn't support acquisition,
......@@ -2285,6 +2461,7 @@ def test_implicit_wrapper_has_nonwrapper_as_aq_parent():
3.145
"""
def test_explicit_wrapper_has_nonwrapper_as_aq_parent():
"""Let's do this the other way around: The root and the
intermediate parent is an object that doesn't support acquisition,
......@@ -2339,6 +2516,7 @@ def test_explicit_wrapper_has_nonwrapper_as_aq_parent():
True
"""
def test___parent__aq_parent_circles():
"""
As a general safety belt, Acquisition won't follow a mixture of
......@@ -2359,7 +2537,8 @@ def test___parent__aq_parent_circles():
>>> x.__parent__.aq_base is y.aq_base
True
>>> Acquisition.aq_parent(x) is y
True
>>> x.__parent__.__parent__ is x
True
......@@ -2383,7 +2562,7 @@ def test___parent__aq_parent_circles():
>>> Acquisition.aq_acquire(y, 'non_existant_attr')
Traceback (most recent call last):
...
AttributeError: non_existant_attr
RuntimeError: Recursion detected in acquisition wrapper
>>> x.non_existant_attr
Traceback (most recent call last):
......@@ -2393,7 +2572,56 @@ def test___parent__aq_parent_circles():
>>> y.non_existant_attr
Traceback (most recent call last):
...
AttributeError: non_existant_attr
RuntimeError: Recursion detected in acquisition wrapper
"""
if hasattr(Acquisition.ImplicitAcquisitionWrapper, '_obj'):
def test_python_impl_cycle():
"""
An extra safety belt, specific to the Python implementation
because it's not clear how one could arrive in this situation
naturally.
>>> class Impl(Acquisition.Implicit):
... pass
>>> root = Impl()
>>> root.child = Impl()
>>> child_wrapper = root.child
Now set up the python specific boo-boo:
>>> child_wrapper._obj = child_wrapper
Now nothing works:
>>> child_wrapper.non_existant_attr
Traceback (most recent call last):
...
RuntimeError: Recursion detected in acquisition wrapper
>>> Acquisition.aq_acquire(child_wrapper, 'non_existant_attr')
Traceback (most recent call last):
...
RuntimeError: Recursion detected in acquisition wrapper
"""
def test_unwrapped_implicit_acquirer_unwraps__parent__():
"""
Set up an implicit acquirer with a parent:
>>> class Impl(Acquisition.Implicit):
... pass
>>> y = Impl()
>>> x = Impl()
>>> x.__parent__ = y
Now if we retrieve the parent from the (unwrapped) instance,
the parent should not be wrapped in the instance's acquisition chain.
>>> x.__parent__ is y
True
"""
......@@ -2415,15 +2643,377 @@ def test__iter__after_AttributeError():
... raise
"""
import unittest
from doctest import DocTestSuite, DocFileSuite
def test_special_names():
"""
This test captures some aq_special names that are not otherwise
tested for.
>>> class Impl(Acquisition.Implicit):
... pass
>>> root = Impl()
>>> root.child = Impl()
First, the 'aq_explicit' name returns an explicit wrapper
instead of an explicit wrapper:
>>> ex_wrapper = root.child.aq_explicit
>>> type(ex_wrapper) #doctest: +ELLIPSIS
<... 'Acquisition.ExplicitAcquisitionWrapper'>
If we ask an explicit wrapper to be explicit, we get back
the same object:
>>> ex_wrapper.aq_explicit is ex_wrapper.aq_explicit
True
These special names can also be filtered:
>>> Acquisition.aq_acquire(root.child, 'aq_explicit',
... lambda searched, parent, name, ob, extra: None,
... default=None) is None
True
>>> Acquisition.aq_acquire(root.child, 'aq_explicit',
... lambda searched, parent, name, ob, extra: True,
... default=None) is None
False
Last, a value that can be used for testing that you have a wrapper:
>>> root.child.aq_uncle
'Bob'
"""
def test_deleting_parent_attrs():
"""
We can detach a wrapper object from its chain by deleting its
parent.
>>> class Impl(Acquisition.Implicit):
... pass
>>> root = Impl()
>>> root.a = 42
>>> root.child = Impl()
Initially, a wrapped object has the parent we expect:
>>> child_wrapper = root.child
>>> child_wrapper.aq_parent is child_wrapper.__parent__ is root
True
Even though we acquired the 'a' attribute, we can't delete it:
>>> child_wrapper.a
42
>>> del child_wrapper.a #doctest: +ELLIPSIS
Traceback (most recent call last):
...
AttributeError: ...
Now if we delete it (as many times as we want)
we lose access to the parent and acquired attributes:
>>> del child_wrapper.__parent__
>>> del child_wrapper.aq_parent
>>> child_wrapper.aq_parent is child_wrapper.__parent__ is None
True
>>> hasattr(child_wrapper, 'a')
False
"""
def test__cmp__is_called_on_wrapped_object():
"""
If we define an object that implements `__cmp__`:
>>> class Impl(Acquisition.Implicit):
... def __cmp__(self,other):
... return self.a
Then it gets called when a wrapper is compared (we call it
directly to avoid any Python2/3 issues):
>>> root = Impl()
>>> root.a = 42
>>> root.child = Impl()
>>> root.child.a
42
>>> root.child.__cmp__(None)
42
"""
def test_wrapped_methods_have_correct_self():
"""
Getting a method from a wrapper returns an object that uses the
wrapper as its `__self__`, no matter how many layers deep we go;
this makes acquisition work in that code.
>>> class Impl(Acquisition.Implicit):
... def method(self):
... return self.a
>>> root = Impl()
>>> root.a = 42
>>> root.child = Impl()
>>> root.child.child = Impl()
We explicitly construct a wrapper to bypass some of the optimizations
that remove redundant wrappers and thus get more full code coverage:
>>> child_wrapper = Acquisition.ImplicitAcquisitionWrapper(root.child.child, root.child)
>>> method = child_wrapper.method
>>> method.__self__ is child_wrapper
True
>>> method()
42
"""
def test_cannot_set_attributes_on_empty_wrappers():
"""
If a wrapper is around None, no attributes can be set on it:
>>> wrapper = Acquisition.ImplicitAcquisitionWrapper(None,None)
>>> wrapper.a = 42 #doctest: +ELLIPSIS
Traceback (most recent call last):
...
AttributeError: ...
Likewise, we can't really get any attributes on such an empty wrapper
>>> wrapper.a #doctest: +ELLIPSIS
Traceback (most recent call last):
...
AttributeError: ...
"""
def test_getitem_setitem_not_implemented():
"""
If a wrapper wraps something that doesn't implement get/setitem,
those failures propagate up.
>>> class Impl(Acquisition.Implicit):
... pass
>>> root = Impl()
>>> root.child = Impl()
We can't set anything:
>>> root.child['key'] = 42
Traceback (most recent call last):
...
AttributeError: __setitem__
We can't get anything:
>>> root.child['key']
Traceback (most recent call last):
...
AttributeError: __getitem__
"""
def test_getitem_setitem_implemented():
"""
The wrapper delegates to get/set item.
>>> class Root(Acquisition.Implicit):
... pass
>>> class Impl(Acquisition.Implicit):
... def __getitem__(self, i):
... return self.a
... def __setitem__(self, key, value):
... self.a[key] = value
>>> root = Root()
>>> root.a = dict()
>>> root.child = Impl()
>>> root.child[1]
{}
>>> root.child['a'] = 'b'
>>> root.child[1]
{'a': 'b'}
"""
def test_wrapped_objects_are_unwrapped_on_set():
"""
A wrapper is not passed to the base object during `setattr`.
>>> class Impl(Acquisition.Implicit):
... pass
Given two different wrappers:
>>> root = Impl()
>>> child = Impl()
>>> child2 = Impl()
>>> root.child = child
>>> root.child2 = child
If we pass one to the other as an attribute:
>>> root.child.child2 = root.child2
By the time it gets there, it's not wrapped:
>>> type(child.__dict__['child2']) is Impl
True
"""
def test_wrapper_calls_of_on_non_wrapper():
"""
The ExtensionClass protocol is respected even for non-Acquisition
objects.
>>> class MyBase(ExtensionClass.Base):
... def __of__(self, other):
... print("Of called")
... return 42
>>> class Impl(Acquisition.Implicit):
... pass
If we have a wrapper around an object that is an extension class,
but not an Acquisition wrapper:
>>> root = Impl()
>>> wrapper = Acquisition.ImplicitAcquisitionWrapper(MyBase(), root)
And access that object itself through a wrapper:
>>> root.child = Impl()
>>> root.child.wrapper = wrapper
The `__of__` protocol is respected implicitly:
>>> root.child.wrapper
Of called
42
Here it is explicitly:
>>> wrapper.__of__(root.child)
Of called
42
"""
def test_aq_inContextOf_odd_cases():
"""
The aq_inContextOf function still works in some
artificial cases.
>>> from Acquisition import aq_inContextOf, aq_inner
>>> root = object()
>>> wrapper_around_none = Acquisition.ImplicitAcquisitionWrapper(None,None)
>>> aq_inContextOf(wrapper_around_none, root)
0
If we don't ask for inner objects, the same thing happens in this case:
>>> aq_inContextOf(wrapper_around_none, root, False)
0
Somewhat surprisingly, the `aq_inner` of this wrapper is itself a wrapper:
>>> aq_inner(wrapper_around_none) is None
False
If we manipulate the Python implementation to make this no longer true,
nothing breaks:
>>> setattr(wrapper_around_none, '_obj', None) if hasattr(wrapper_around_none, '_obj') else None
>>> aq_inContextOf(wrapper_around_none, root)
0
>>> wrapper_around_none
None
Following parent pointers in weird circumstances works too:
>>> class WithParent(object):
... __parent__ = None
>>> aq_inContextOf(WithParent(), root)
0
"""
def test_search_repeated_objects():
"""
If an acquisition wrapper object is wrapping another wrapper, and
also has another wrapper as its parent, and both of *those*
wrappers have the same object (one as its direct object, one as
its parent), then acquisition proceeds as normal: we don't get
into any cycles or fail to acquire expected attributes. In fact,
we actually can optimize out a level of the search in that case.
This is a bit of a convoluted scenario to set up when the code is
written out all in one place, but it may occur organically when
spread across a project.
We begin with some simple setup, importing the objects we'll use
and setting up the object that we'll repeat. This particular test
is specific to the Python implementation, so we're using low-level
functions from that module:
>>> from Acquisition import _Wrapper as Wrapper
>>> from Acquisition import _Wrapper_acquire
>>> from Acquisition import aq_acquire
>>> class Repeated(object):
... hello = "world"
... def __repr__(self):
... return 'repeated'
>>> repeated = Repeated()
Now the tricky part, creating the repeating pattern. To rephrase
the opening sentence, we need a wrapper whose object and parent
(container) are themselves both wrappers, and the object's parent is
the same object as the wrapper's parent's object. That might be a
bit more clear in code:
>>> wrappers_object = Wrapper('a', repeated)
>>> wrappers_parent = Wrapper(repeated, 'b')
>>> wrapper = Wrapper(wrappers_object, wrappers_parent)
>>> wrapper._obj._container is wrapper._container._obj
True
Using the low-level function on the wrapper fails to find the
desired attribute. This is because of the optimization that cuts
out a level of the search (it is assumed that the higher level
`_Wrapper_findattr` function is driving the search and will take
the appropriate steps):
>>> _Wrapper_acquire(wrapper, 'hello') #doctest: +ELLIPSIS
Traceback (most recent call last):
...
AttributeError: ...
In fact, if we go through the public interface of the high-level
functions, we do find the attribute as expected:
>>> aq_acquire(wrapper, 'hello')
'world'
"""
class TestParent(unittest.TestCase):
def test_parent_parent_circles(self):
class Impl(Acquisition.Implicit):
hello = 'world'
class Impl2(Acquisition.Implicit):
hello = 'world2'
only = 'here'
......@@ -2443,10 +3033,13 @@ class TestParent(unittest.TestCase):
y, 'non_existant_attr')
def test_parent_parent_parent_circles(self):
class Impl(Acquisition.Implicit):
hello = 'world'
class Impl2(Acquisition.Implicit):
hello = 'world'
class Impl3(Acquisition.Implicit):
hello = 'world2'
only = 'here'
......@@ -2458,37 +3051,35 @@ class TestParent(unittest.TestCase):
b.__parent__ = c
c.__parent__ = a
# This is not quite what you'd expect, an AQ circle with an
# intermediate object gives strange results
self.assertTrue(a.__parent__.__parent__ is a)
self.assertTrue(a.__parent__.__parent__.__parent__.aq_base is b)
self.assertTrue(b.__parent__.__parent__ is b)
self.assertTrue(c.__parent__.__parent__ is c)
self.assertTrue(a.__parent__.__parent__ is c)
self.assertTrue(
Acquisition.aq_base(a.__parent__.__parent__.__parent__) is a)
self.assertTrue(b.__parent__.__parent__ is a)
self.assertTrue(c.__parent__.__parent__ is b)
self.assertEqual(Acquisition.aq_acquire(a, 'hello'), 'world')
self.assertEqual(Acquisition.aq_acquire(b, 'hello'), 'world')
self.assertEqual(Acquisition.aq_acquire(c, 'hello'), 'world2')
self.assertRaises(AttributeError, Acquisition.aq_acquire,
a, 'only')
self.assertEqual(Acquisition.aq_acquire(a, 'only'), 'here')
self.assertEqual(Acquisition.aq_acquire(b, 'only'), 'here')
self.assertEqual(Acquisition.aq_acquire(c, 'only'), 'here')
self.assertRaises(AttributeError, Acquisition.aq_acquire,
a, 'non_existant_attr')
self.assertRaises(AttributeError, Acquisition.aq_acquire,
b, 'non_existant_attr')
self.assertRaises(AttributeError, Acquisition.aq_acquire,
c, 'non_existant_attr')
self.assertRaises(AttributeError, getattr, a, 'non_existant_attr')
self.assertRaises(AttributeError, getattr, b, 'non_existant_attr')
self.assertRaises(AttributeError, getattr, c, 'non_existant_attr')
class TestAcquire(unittest.TestCase):
def setUp(self):
class Impl(Acquisition.Implicit):
pass
class Expl(Acquisition.Explicit):
pass
a = Impl('a')
a.y = 42
a.b = Expl('b')
......@@ -2522,7 +3113,7 @@ class TestAcquire(unittest.TestCase):
class NotWrapped(object):
pass
child = NotWrapped()
parent = child.__parent__ = NotWrapped()
child.__parent__ = NotWrapped()
self.assertEqual(self.acquire(child, 'nonesuch', default=4), 4)
def test_unwrapped_falls_back_to_default(self):
......@@ -2530,26 +3121,145 @@ class TestAcquire(unittest.TestCase):
def test_w_unicode_attr_name(self):
# See https://bugs.launchpad.net/acquisition/+bug/143358
found = self.acquire(self.a.b.c, u'aq_parent')
found = self.acquire(self.a.b.c, AQ_PARENT)
self.assertTrue(found.aq_self is self.a.b.aq_self)
class TestCooperativeBase(unittest.TestCase):
def _make_acquirer(self, kind):
from ExtensionClass import Base
class ExtendsBase(Base):
def __getattribute__(self, name):
if name == 'magic':
return 42
return super(ExtendsBase,self).__getattribute__(name)
class Acquirer(kind, ExtendsBase):
pass
return Acquirer()
def _check___getattribute___is_cooperative(self, acquirer):
self.assertEqual(getattr(acquirer, 'magic'), 42)
def test_implicit___getattribute__is_cooperative(self):
self._check___getattribute___is_cooperative(self._make_acquirer(Acquisition.Implicit))
def test_explicit___getattribute__is_cooperative(self):
self._check___getattribute___is_cooperative(self._make_acquirer(Acquisition.Explicit))
if 'Acquisition._Acquisition' not in sys.modules:
# Implicitly wrapping an object that uses object.__getattribute__
# in its implementation of __getattribute__ doesn't break.
# This can arise with the `persistent` library or other
# "base" classes.
# The C implementation doesn't directly support this; however,
# it is used heavily in the Python implementation of Persistent.
class TestImplicitWrappingGetattribute(unittest.TestCase):
def test_object_getattribute_in_rebound_method_with_slots(self):
class Persistent(object):
__slots__ = ('__flags',)
def __init__(self):
self.__flags = 42
def get_flags(self):
return object.__getattribute__(self, '_Persistent__flags')
wrapped = Persistent()
wrapper = Acquisition.ImplicitAcquisitionWrapper(wrapped, None)
self.assertEqual(wrapped.get_flags(), wrapper.get_flags())
# Changing it is not reflected in the wrapper's dict (this is an
# implementation detail)
wrapper._Persistent__flags = -1
self.assertEqual(wrapped.get_flags(), -1)
self.assertEqual(wrapped.get_flags(), wrapper.get_flags())
wrapper_dict = object.__getattribute__(wrapper, '__dict__')
self.assertFalse('_Persistent__flags' in wrapper_dict)
def test_type_with_slots_reused(self):
class Persistent(object):
__slots__ = ('__flags',)
def __init__(self):
self.__flags = 42
def get_flags(self):
return object.__getattribute__(self, '_Persistent__flags')
wrapped = Persistent()
wrapper = Acquisition.ImplicitAcquisitionWrapper(wrapped, None)
wrapper2 = Acquisition.ImplicitAcquisitionWrapper(wrapped, None)
self.assertTrue( type(wrapper) is type(wrapper2))
def test_object_getattribute_in_rebound_method_with_dict(self):
class Persistent(object):
def __init__(self):
self.__flags = 42
def get_flags(self):
return object.__getattribute__(self, '_Persistent__flags')
wrapped = Persistent()
wrapper = Acquisition.ImplicitAcquisitionWrapper(wrapped, None)
self.assertEqual(wrapped.get_flags(), wrapper.get_flags())
# Changing it is also reflected in both dicts (this is an
# implementation detail)
wrapper._Persistent__flags = -1
self.assertEqual(wrapped.get_flags(), -1)
self.assertEqual(wrapped.get_flags(), wrapper.get_flags())
wrapper_dict = object.__getattribute__(wrapper, '__dict__')
self.assertTrue('_Persistent__flags' in wrapper_dict)
def test_object_getattribute_in_rebound_method_with_slots_and_dict(self):
class Persistent(object):
__slots__ = ('__flags', '__dict__')
def __init__(self):
self.__flags = 42
self.__oid = 'oid'
def get_flags(self):
return object.__getattribute__(self, '_Persistent__flags')
def get_oid(self):
return object.__getattribute__(self, '_Persistent__oid')
wrapped = Persistent()
wrapper = Acquisition.ImplicitAcquisitionWrapper(wrapped, None)
self.assertEqual(wrapped.get_flags(), wrapper.get_flags())
self.assertEqual(wrapped.get_oid(), wrapper.get_oid())
class TestUnicode(unittest.TestCase):
def test_implicit_aq_unicode_should_be_called(self):
class A(Acquisition.Implicit):
def __unicode__(self):
return u'unicode was called'
return UNICODE_WAS_CALLED
wrapped = A().__of__(A())
self.assertEqual(u'unicode was called', unicode(wrapped))
self.assertEqual(UNICODE_WAS_CALLED, unicode(wrapped))
self.assertEqual(str(wrapped), repr(wrapped))
def test_explicit_aq_unicode_should_be_called(self):
class A(Acquisition.Explicit):
def __unicode__(self):
return u'unicode was called'
return UNICODE_WAS_CALLED
wrapped = A().__of__(A())
self.assertEqual(u'unicode was called', unicode(wrapped))
self.assertEqual(UNICODE_WAS_CALLED, unicode(wrapped))
self.assertEqual(str(wrapped), repr(wrapped))
def test_implicit_should_fall_back_to_str(self):
......@@ -2557,7 +3267,7 @@ class TestUnicode(unittest.TestCase):
def __str__(self):
return 'str was called'
wrapped = A().__of__(A())
self.assertEqual(u'str was called', unicode(wrapped))
self.assertEqual(STR_WAS_CALLED, unicode(wrapped))
self.assertEqual('str was called', str(wrapped))
def test_explicit_should_fall_back_to_str(self):
......@@ -2565,7 +3275,7 @@ class TestUnicode(unittest.TestCase):
def __str__(self):
return 'str was called'
wrapped = A().__of__(A())
self.assertEqual(u'str was called', unicode(wrapped))
self.assertEqual(STR_WAS_CALLED, unicode(wrapped))
self.assertEqual('str was called', str(wrapped))
def test_str_fallback_should_be_called_with_wrapped_self(self):
......@@ -2574,7 +3284,7 @@ class TestUnicode(unittest.TestCase):
return str(self.aq_parent == outer)
outer = A()
inner = A().__of__(outer)
self.assertEqual(u'True', unicode(inner))
self.assertEqual(TRUE, unicode(inner))
def test_unicode_should_be_called_with_wrapped_self(self):
class A(Acquisition.Implicit):
......@@ -2582,14 +3292,376 @@ class TestUnicode(unittest.TestCase):
return str(self.aq_parent == outer)
outer = A()
inner = A().__of__(outer)
self.assertEqual(u'True', unicode(inner))
self.assertEqual(TRUE, unicode(inner))
class TestProxying(unittest.TestCase):
__binary_numeric_methods__ = [
'__add__',
'__sub__',
'__mul__',
# '__floordiv__', # not implemented in C
'__mod__',
'__divmod__',
'__pow__',
'__lshift__',
'__rshift__',
'__and__',
'__xor__',
'__or__',
# division
'__truediv__',
'__div__',
# reflected
'__radd__',
'__rsub__',
'__rmul__',
'__rdiv__',
'__rtruediv__',
'__rfloordiv__',
'__rmod__',
'__rdivmod__',
'__rpow__',
'__rlshift__',
'__rrshift__',
'__rand__',
'__rxor__',
'__ror__',
# in place
'__iadd__',
'__isub__',
'__imul__',
'__idiv__',
'__itruediv__',
'__ifloordiv__',
'__imod__',
'__idivmod__',
'__ipow__',
'__ilshift__',
'__irshift__',
'__iand__',
'__ixor__',
'__ior__',
# conversion
# implementing it messes up all the arithmetic tests
#'__coerce__',
]
__unary_special_methods__ = [
# arithmetic
'__neg__',
'__pos__',
'__abs__',
'__invert__',
]
__unary_conversion_methods__ = {
# conversion
'__complex__': complex,
'__int__': int,
'__long__': long,
'__float__': float,
'__oct__': oct,
'__hex__': hex,
'__len__': lambda o: o if isinstance(o,int) else len(o),
#'__index__': operator.index, # not implemented in C
}
def _check_special_methods(self,base_class=Acquisition.Implicit):
"Check that special methods are proxied when called implicitly by the interpreter"
def binary_acquired_func(self, other):
return self.value
def unary_acquired_func(self):
return self.value
acquire_meths = {}
for k in self.__binary_numeric_methods__:
acquire_meths[k] = binary_acquired_func
for k in self.__unary_special_methods__:
acquire_meths[k] = unary_acquired_func
def make_converter(f):
def converter(self,*args):
return f(self.value)
return converter
for k, convert in self.__unary_conversion_methods__.items():
acquire_meths[k] = make_converter(convert)
acquire_meths['__len__'] = lambda self: self.value
if PY3:
# Under Python 3, oct() and hex() call __index__ directly
acquire_meths['__index__'] = acquire_meths['__int__']
if base_class == Acquisition.Explicit:
acquire_meths['value'] = Acquisition.Acquired
AcquireValue = type('AcquireValue', (base_class,), acquire_meths)
class B(Acquisition.Implicit):
pass
base = B()
base.value = 42
base.derived = AcquireValue()
# one syntax check for the heck of it
self.assertEqual(base.value, base.derived + 1)
# divmod is not in the operator module
self.assertEqual(base.value, divmod(base.derived, 1))
_found_at_least_one_div = False
for meth in self.__binary_numeric_methods__:
# called on the instance
self.assertEqual(base.value,
getattr(base.derived, meth)(-1))
# called on the type, as the interpreter does
# Note that the C version can only implement either __truediv__
# or __div__, not both
op = getattr(operator, meth, None)
if op is not None:
try:
self.assertEqual(base.value,
op(base.derived, 1))
if meth in ('__div__', '__truediv__'):
_found_at_least_one_div = True
except TypeError:
if meth in ('__div__', '__truediv__'):
pass
self.assertTrue(_found_at_least_one_div, "Must implement at least one of __div__ and __truediv__")
# Unary methods
for meth in self.__unary_special_methods__:
self.assertEqual(base.value,
getattr(base.derived, meth)())
op = getattr(operator, meth)
self.assertEqual(base.value,
op(base.derived) )
# Conversion functions
for meth, converter in self.__unary_conversion_methods__.items():
if not converter:
continue
self.assertEqual(converter(base.value),
getattr(base.derived, meth)())
self.assertEqual(converter(base.value),
converter(base.derived))
def test_implicit_proxy_special_meths(self):
self._check_special_methods()
def test_explicit_proxy_special_meths(self):
self._check_special_methods(base_class=Acquisition.Explicit)
def _check_contains(self, base_class=Acquisition.Implicit):
# Contains has lots of fallback behaviour
class B(Acquisition.Implicit):
pass
base = B()
base.value = 42
# The simple case is if the object implements contains itself
class ReallyContains(base_class):
if base_class is Acquisition.Explicit:
value = Acquisition.Acquired
def __contains__(self, item):
return self.value == item
base.derived = ReallyContains()
self.assertTrue(42 in base.derived)
self.assertFalse(24 in base.derived)
# Iterable objects are NOT iterated
# XXX: Is this a bug in the C code? Shouldn't it do
# what the interpreter does and fallback to iteration?
class IterContains(base_class):
if base_class is Acquisition.Explicit:
value = Acquisition.Acquired
def __iter__(self):
return iter((42,))
base.derived = IterContains()
self.assertRaises(AttributeError, operator.contains, base.derived, 42)
def test_implicit_proxy_contains(self):
self._check_contains()
def test_explicit_proxy_contains(self):
self._check_contains(base_class=Acquisition.Explicit)
def _check_call(self, base_class=Acquisition.Implicit):
class B(Acquisition.Implicit):
pass
base = B()
base.value = 42
class Callable(base_class):
if base_class is Acquisition.Explicit:
value = Acquisition.Acquired
def __call__(self, arg, k=None):
return self.value, arg, k
base.derived = Callable()
self.assertEqual( base.derived(1, k=2),
(42, 1, 2))
if not PYPY:
# XXX: This test causes certain versions
# of PyPy to segfault (at least 2.6.0-alpha1)
class NotCallable(base_class):
pass
base.derived = NotCallable()
try:
base.derived()
self.fail("Not callable")
except (TypeError,AttributeError):
pass
def test_implicit_proxy_call(self):
self._check_call()
def test_explicit_proxy_call(self):
self._check_call(base_class=Acquisition.Explicit)
def _check_hash(self,base_class=Acquisition.Implicit):
class B(Acquisition.Implicit):
pass
base = B()
base.value = B()
base.value.hash = 42
class NoAcquired(base_class):
def __hash__(self):
return 1
hashable = NoAcquired()
base.derived = hashable
self.assertEqual(1, hash(hashable))
self.assertEqual(1, hash(base.derived))
# cannot access acquired attributes during
# __hash__
class CannotAccessAcquiredAttributesAtHash(base_class):
if base_class is Acquisition.Explicit:
value = Acquisition.Acquired
def __hash__(self):
return self.value.hash
hashable = CannotAccessAcquiredAttributesAtHash()
base.derived = hashable
self.assertRaises(AttributeError, hash, hashable)
self.assertRaises(AttributeError, hash, base.derived)
def test_implicit_proxy_hash(self):
self._check_hash()
def test_explicit_proxy_hash(self):
self._check_hash(base_class=Acquisition.Explicit)
def _check_comparison(self,base_class=Acquisition.Implicit):
# Comparison behaviour is complex; see notes in _Wrapper
class B(Acquisition.Implicit):
pass
base = B()
base.value = 42
rich_cmp_methods = ['__lt__', '__gt__', '__eq__',
'__ne__', '__ge__', '__le__']
def _never_called(self, other):
raise RuntimeError("This should never be called")
class RichCmpNeverCalled(base_class):
for _name in rich_cmp_methods:
locals()[_name] = _never_called
base.derived = RichCmpNeverCalled()
base.derived2 = RichCmpNeverCalled()
# We can access all of the operators, but only because
# they are masked
for name in rich_cmp_methods:
getattr(operator, name)(base.derived, base.derived2)
self.assertFalse(base.derived2 == base.derived)
self.assertEquals(base.derived, base.derived)
def test_implicit_proxy_comporison(self):
self._check_comparison()
def test_explicit_proxy_comporison(self):
self._check_comparison(base_class=Acquisition.Explicit)
def _check_bool(self, base_class=Acquisition.Implicit):
class B(Acquisition.Implicit):
pass
base = B()
base.value = 42
class WithBool(base_class):
if base_class is Acquisition.Explicit:
value = Acquisition.Acquired
def __nonzero__(self):
return bool(self.value)
__bool__ = __nonzero__
class WithLen(base_class):
if base_class is Acquisition.Explicit:
value = Acquisition.Acquired
def __len__(self):
return self.value
class WithNothing(base_class):
pass
base.wbool = WithBool()
base.wlen = WithLen()
base.wnothing = WithNothing()
self.assertEqual(bool(base.wbool), True)
self.assertEqual(bool(base.wlen), True)
self.assertEqual(bool(base.wnothing), True)
base.value = 0
self.assertFalse(base.wbool)
self.assertFalse(base.wlen)
def test_implicit_proxy_bool(self):
self._check_bool()
def test_explicit_proxy_bool(self):
self._check_bool(base_class=Acquisition.Explicit)
def test_suite():
return unittest.TestSuite((
import os.path
here = os.path.dirname(__file__)
root = os.path.join(here, os.pardir, os.pardir)
readme = os.path.relpath(os.path.join(root, 'README.rst'))
suites = [
DocTestSuite(),
DocFileSuite('README.txt', package='Acquisition'),
unittest.makeSuite(TestParent),
unittest.makeSuite(TestAcquire),
unittest.makeSuite(TestUnicode),
))
unittest.makeSuite(TestProxying),
unittest.makeSuite(TestCooperativeBase),
]
# This file is only available in a source checkout, skip it
# when tests are run for an installed version.
if os.path.isfile(readme):
suites.append(DocFileSuite(readme))
return unittest.TestSuite(suites)
[tox]
envlist =
py26,py27,py27-pure,py32,py33,py34,pypy,pypy3,coverage
[testenv]
commands =
nosetests --with-doctest --doctest-tests --where={envsitepackagesdir}/Acquisition
deps =
nose
[testenv:py27-pure]
basepython =
python2.7
setenv =
PURE_PYTHON = 1
[testenv:coverage]
basepython =
python2.7
commands =
nosetests --with-xunit --with-xcoverage --with-doctest --doctest-tests --where={envsitepackagesdir}/Acquisition --cover-package=Acquisition
deps =
nose
coverage
nosexcover
setenv =
PURE_PYTHON = 1
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment