Commit 8a77d12c authored by Tres Seaver's avatar Tres Seaver

Merge branch 'pypy-py3k' of git://github.com/NextThought/Acquisition into NextThought-pypy-py3k

parents 2667b34d ba35cf6f
...@@ -3,6 +3,8 @@ sudo: false ...@@ -3,6 +3,8 @@ sudo: false
python: python:
- 2.6 - 2.6
- 2.7 - 2.7
- 3.4
- pypy
install: install:
- python bootstrap.py - python bootstrap.py
- bin/buildout - bin/buildout
......
Changelog Changelog
========= =========
4.2 (unreleased)
----------------
- Add support for PyPy and Python 3.
4.1 (2014-12-18) 4.1 (2014-12-18)
---------------- ----------------
...@@ -62,7 +67,7 @@ Changelog ...@@ -62,7 +67,7 @@ Changelog
- Add ``aq_explicit`` to ``IAcquisitionWrapper``. - Add ``aq_explicit`` to ``IAcquisitionWrapper``.
- Fixed bug: ``unicode(wrapped)`` was not calling a ``__unicode__`` - Fixed bug: ``unicode(wrapped)`` was not calling a ``__unicode__``
method on wrapped objects. method on wrapped objects.
2.13.5 (2010-09-29) 2.13.5 (2010-09-29)
...@@ -133,7 +138,7 @@ Changelog ...@@ -133,7 +138,7 @@ Changelog
2.12.2 (2009-08-02) 2.12.2 (2009-08-02)
------------------- -------------------
- Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See - Fixed 64-bit compatibility issues for Python 2.5.x / 2.6.x. See
http://www.python.org/dev/peps/pep-0353/ for details. http://www.python.org/dev/peps/pep-0353/ for details.
2.12.1 (2009-04-15) 2.12.1 (2009-04-15)
......
...@@ -34,7 +34,7 @@ class. For example:: ...@@ -34,7 +34,7 @@ class. For example::
>>> class A(Acquisition.Implicit): >>> class A(Acquisition.Implicit):
... def report(self): ... def report(self):
... print self.color ... print(self.color)
... ...
>>> a = A() >>> a = A()
>>> c = C() >>> c = C()
...@@ -107,7 +107,7 @@ When explicit acquisition is used, attributes are not automatically ...@@ -107,7 +107,7 @@ When explicit acquisition is used, attributes are not automatically
obtained from the environment. Instead, the method aq_acquire must be obtained from the environment. Instead, the method aq_acquire must be
used. For example:: used. For example::
>>> print c.a.aq_acquire('color') >>> print(c.a.aq_acquire('color'))
red red
To support explicit acquisition, your class should inherit from the To support explicit acquisition, your class should inherit from the
...@@ -178,7 +178,7 @@ Here's an example:: ...@@ -178,7 +178,7 @@ Here's an example::
>>> class E(Explicit, HandyForTesting): pass >>> class E(Explicit, HandyForTesting): pass
... ...
>>> class Nice(HandyForTesting): >>> class Nice(HandyForTesting):
... isNice = 1 ... isNice = 1
... def __str__(self): ... def __str__(self):
... return HandyForTesting.__str__(self)+' and I am nice!' ... return HandyForTesting.__str__(self)+' and I am nice!'
... __repr__ = __str__ ... __repr__ = __str__
...@@ -192,7 +192,7 @@ Here's an example:: ...@@ -192,7 +192,7 @@ Here's an example::
>>> def find_nice(self, ancestor, name, object, extra): >>> def find_nice(self, ancestor, name, object, extra):
... return hasattr(object,'isNice') and object.isNice ... 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! spam(Nice) and I am nice!
The filtered acquisition in the last line skips over the first The filtered acquisition in the last line skips over the first
...@@ -221,7 +221,7 @@ method. For example:: ...@@ -221,7 +221,7 @@ method. For example::
>>> a = C() >>> a = C()
>>> b = C() >>> b = C()
>>> a.color = "red" >>> a.color = "red"
>>> print b.__of__(a).color >>> print(b.__of__(a).color)
red red
In this case, ``a`` does not contain ``b``, but it is put in ``b``'s In this case, ``a`` does not contain ``b``, but it is put in ``b``'s
...@@ -241,7 +241,7 @@ acquisition context that includes non-container objects:: ...@@ -241,7 +241,7 @@ acquisition context that includes non-container objects::
>>> a.b.color = "red" >>> a.b.color = "red"
>>> a.x = C("x") >>> a.x = C("x")
>>> print a.b.x.color >>> print(a.b.x.color)
red red
Even though ``b`` does not contain ``x``, ``x`` can acquire the color Even though ``b`` does not contain ``x``, ``x`` can acquire the color
...@@ -262,7 +262,7 @@ If in the example above suppose both a and b have an color attribute:: ...@@ -262,7 +262,7 @@ If in the example above suppose both a and b have an color attribute::
>>> a.b.color = "red" >>> a.b.color = "red"
>>> a.x = C("x") >>> a.x = C("x")
>>> print a.b.x.color >>> print(a.b.x.color)
green green
Why does ``a.b.x.color`` acquire color from ``a`` and not from ``b``? Why does ``a.b.x.color`` acquire color from ``a`` and not from ``b``?
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
"""Setup for the Acquisition distribution """Setup for the Acquisition distribution
""" """
import os import os
import platform
import sys
from setuptools import setup, find_packages, Extension from setuptools import setup, find_packages, Extension
with open('README.rst') as f: with open('README.rst') as f:
...@@ -22,9 +24,24 @@ with open('README.rst') as f: ...@@ -22,9 +24,24 @@ with open('README.rst') as f:
with open('CHANGES.rst') as f: with open('CHANGES.rst') as f:
CHANGES = f.read() 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( setup(
name='Acquisition', name='Acquisition',
version='4.1', version='4.2.dev0',
url='https://github.com/zopefoundation/Acquisition', url='https://github.com/zopefoundation/Acquisition',
license='ZPL 2.1', license='ZPL 2.1',
description="Acquisition is a mechanism that allows objects to obtain " description="Acquisition is a mechanism that allows objects to obtain "
...@@ -41,18 +58,17 @@ setup( ...@@ -41,18 +58,17 @@ setup(
"License :: OSI Approved :: Zope Public License", "License :: OSI Approved :: Zope Public License",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2 :: Only", "Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7", "Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
], ],
ext_modules=[Extension("Acquisition._Acquisition", ext_modules=ext_modules,
[os.path.join('src', 'Acquisition',
'_Acquisition.c')],
include_dirs=['include', 'src']),
],
install_requires=[ install_requires=[
'ExtensionClass >= 4.1a1', 'ExtensionClass >= 4.1.1',
'zope.interface', 'zope.interface',
], ],
include_package_data=True, include_package_data=True,
......
from __future__ import absolute_import, print_function
# pylint:disable=W0212,R0911,R0912
import os
import operator
import sys
import types
import ExtensionClass
from zope.interface import classImplements from zope.interface import classImplements
from _Acquisition import * from .interfaces import IAcquirer
from interfaces import IAcquirer from .interfaces import IAcquisitionWrapper
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,):
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
else:
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
###
# 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):
if _has__of__(result):
result = result.__of__(wrapper)
return result
else:
raise AttributeError(name)
else:
if _has__of__(result):
result = result.__of__(wrapper)
return result
raise AttributeError(name)
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:
return result if _apply_filter(predicate, wrapper, orig_name, result, predicate_extra, orig_object) else None
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
class _Wrapper(ExtensionClass.Base):
__slots__ = ('_obj','_container',)
_IS_IMPLICIT = None
def __new__(cls, obj, container):
inst = super(_Wrapper,cls).__new__(cls)
inst._obj = obj
inst._container = container
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 object.__getattribute__(self, name)
if self._obj is not None or self._container is not None:
return _Wrapper_findattr(self, name, None, None, None,
True, type(self)._IS_IMPLICIT, False, False)
return object.__getattribute__(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
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(type(aq_self), 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
return type(aq_self).__repr__(aq_self)
def __str__(self):
aq_self = self._obj
return type(aq_self).__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
_rebound_method(getattr(type(aq_self), '__setitem__'), self)(key, value)
def __getitem__(self, key):
if isinstance(key, slice):
if isinstance(self._obj, (list, tuple)):
return self._obj[key]
start, stop = key.start, key.stop
if start is None:
start = 0
if start < 0:
start += len(self._obj)
if stop is None:
stop = getattr(sys, 'maxint', None) # PY2
elif stop < 0:
stop += len(self._obj)
if hasattr(operator, 'getslice'): # PY2
return operator.getslice(self._obj, start, stop)
return self._obj[start:stop]
return self._obj[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:
# 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 ExtensionClass.Base.__getattribute__(self, name)
except AttributeError:
# the doctests have very specific error message
# requirements
raise AttributeError(name)
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.aq_self
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:
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(Explicit, IAcquirer)
classImplements(ExplicitAcquisitionWrapper, IAcquisitionWrapper) classImplements(ExplicitAcquisitionWrapper, IAcquisitionWrapper)
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
>>> class A(Acquisition.Implicit): >>> class A(Acquisition.Implicit):
... def report(self): ... def report(self):
... print self.color ... print(self.color)
>>> a = A() >>> a = A()
>>> c = C() >>> c = C()
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
automatically obtained from the environment. Instead, the automatically obtained from the environment. Instead, the
method 'aq_aquire' must be used, as in:: 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 To support explicit acquisition, an object should inherit
from the mix-in class 'Acquisition.Explicit'. from the mix-in class 'Acquisition.Explicit'.
...@@ -170,7 +170,7 @@ ...@@ -170,7 +170,7 @@
... __roles__ = Acquisition.Acquired ... __roles__ = Acquisition.Acquired
>>> c.x = C() >>> c.x = C()
>>> c.x.__roles__ >>> c.x.__roles__ # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last): Traceback (most recent call last):
... ...
AttributeError: __roles__ AttributeError: __roles__
...@@ -231,7 +231,7 @@ ...@@ -231,7 +231,7 @@
>>> def find_nice(self, ancestor, name, object, extra): >>> def find_nice(self, ancestor, name, object, extra):
... return hasattr(object,'isNice') and object.isNice ... 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! spam(Nice) and I am nice!
The filtered acquisition in the last line skips over the first The filtered acquisition in the last line skips over the first
...@@ -328,6 +328,36 @@ ...@@ -328,6 +328,36 @@
http://www.bell-labs.com/people/cope/oopsla/Oopsla96TechnicalProgramAbstracts.html#GilLorenz, http://www.bell-labs.com/people/cope/oopsla/Oopsla96TechnicalProgramAbstracts.html#GilLorenz,
OOPSLA '96 Proceedings, ACM SIG-PLAN, October, 1996 OOPSLA '96 Proceedings, ACM SIG-PLAN, October, 1996
""" """
from __future__ import print_function
import gc
import unittest
import sys
import platform
import operator
from doctest import DocTestSuite, DocFileSuite
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
import ExtensionClass import ExtensionClass
import Acquisition import Acquisition
...@@ -558,7 +588,6 @@ def test_simple(): ...@@ -558,7 +588,6 @@ def test_simple():
True True
""" """
def test__of__exception(): def test__of__exception():
""" """
Wrapper_findattr did't check for an exception in a user defined Wrapper_findattr did't check for an exception in a user defined
...@@ -566,13 +595,10 @@ def test__of__exception(): ...@@ -566,13 +595,10 @@ def test__of__exception():
case the 'value' argument of the filter was NULL, which caused case the 'value' argument of the filter was NULL, which caused
a segfault when being accessed. a segfault when being accessed.
>>> class UserError(Exception):
... pass
...
>>> class X(Acquisition.Implicit): >>> class X(Acquisition.Implicit):
... def __of__(self, parent): ... def __of__(self, parent):
... if Acquisition.aq_base(parent) is not parent: ... if Acquisition.aq_base(parent) is not parent:
... raise UserError, 'ack' ... raise NotImplementedError('ack')
... return X.inheritedAttribute('__of__')(self, parent) ... return X.inheritedAttribute('__of__')(self, parent)
... ...
>>> a = I('a') >>> a = I('a')
...@@ -582,7 +608,7 @@ def test__of__exception(): ...@@ -582,7 +608,7 @@ def test__of__exception():
... lambda self, object, name, value, extra: repr(value)) ... lambda self, object, name, value, extra: repr(value))
Traceback (most recent call last): Traceback (most recent call last):
... ...
UserError: ack NotImplementedError: ack
""" """
...@@ -1236,7 +1262,8 @@ def test_aq_inContextOf(): ...@@ -1236,7 +1262,8 @@ def test_aq_inContextOf():
>>> class A(Acquisition.Implicit): >>> class A(Acquisition.Implicit):
... def hi(self): ... def hi(self):
... print "%s()" % self.__class__.__name__, self.color ... print(self.__class__.__name__)
... print(self.color)
>>> class Location(object): >>> class Location(object):
... __parent__ = None ... __parent__ = None
...@@ -1244,15 +1271,17 @@ def test_aq_inContextOf(): ...@@ -1244,15 +1271,17 @@ def test_aq_inContextOf():
>>> b=B() >>> b=B()
>>> b.a=A() >>> b.a=A()
>>> b.a.hi() >>> b.a.hi()
A() red A
red
>>> b.a.color='green' >>> b.a.color='green'
>>> b.a.hi() >>> b.a.hi()
A() green A
green
>>> try: >>> try:
... A().hi() ... A().hi()
... raise 'Program error', 'spam' ... raise RuntimeError( 'Program error', 'spam')
... except AttributeError: pass ... except AttributeError: pass
A() A
New test for wrapper comparisons. New test for wrapper comparisons.
...@@ -1349,7 +1378,7 @@ def test_AqAlg(): ...@@ -1349,7 +1378,7 @@ def test_AqAlg():
[A] [A]
>>> Acquisition.aq_chain(A, 1) >>> Acquisition.aq_chain(A, 1)
[A] [A]
>>> map(Acquisition.aq_base, Acquisition.aq_chain(A, 1)) >>> list(map(Acquisition.aq_base, Acquisition.aq_chain(A, 1)))
[A] [A]
>>> A.C >>> A.C
C C
...@@ -1357,7 +1386,7 @@ def test_AqAlg(): ...@@ -1357,7 +1386,7 @@ def test_AqAlg():
[C, A] [C, A]
>>> Acquisition.aq_chain(A.C, 1) >>> Acquisition.aq_chain(A.C, 1)
[C, A] [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] [C, A]
>>> A.C.D >>> A.C.D
...@@ -1366,7 +1395,7 @@ def test_AqAlg(): ...@@ -1366,7 +1395,7 @@ def test_AqAlg():
[D, C, A] [D, C, A]
>>> Acquisition.aq_chain(A.C.D, 1) >>> Acquisition.aq_chain(A.C.D, 1)
[D, C, A] [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] [D, C, A]
>>> A.B.C >>> A.B.C
...@@ -1375,7 +1404,7 @@ def test_AqAlg(): ...@@ -1375,7 +1404,7 @@ def test_AqAlg():
[C, B, A] [C, B, A]
>>> Acquisition.aq_chain(A.B.C, 1) >>> Acquisition.aq_chain(A.B.C, 1)
[C, A] [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] [C, A]
>>> A.B.C.D >>> A.B.C.D
...@@ -1384,7 +1413,7 @@ def test_AqAlg(): ...@@ -1384,7 +1413,7 @@ def test_AqAlg():
[D, C, B, A] [D, C, B, A]
>>> Acquisition.aq_chain(A.B.C.D, 1) >>> Acquisition.aq_chain(A.B.C.D, 1)
[D, C, A] [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] [D, C, A]
...@@ -1407,19 +1436,22 @@ def test_explicit_acquisition(): ...@@ -1407,19 +1436,22 @@ def test_explicit_acquisition():
>>> class A(Acquisition.Explicit): >>> class A(Acquisition.Explicit):
... def hi(self): ... def hi(self):
... print self.__class__.__name__, self.acquire('color') ... print(self.__class__.__name__)
... print(self.acquire('color'))
>>> b=B() >>> b=B()
>>> b.a=A() >>> b.a=A()
>>> b.a.hi() >>> b.a.hi()
A red A
red
>>> b.a.color='green' >>> b.a.color='green'
>>> b.a.hi() >>> b.a.hi()
A green A
green
>>> try: >>> try:
... A().hi() ... A().hi()
... raise 'Program error', 'spam' ... raise RuntimeError('Program error', 'spam')
... except AttributeError: pass ... except AttributeError: pass
A A
...@@ -1442,7 +1474,7 @@ def test_creating_wrappers_directly(): ...@@ -1442,7 +1474,7 @@ def test_creating_wrappers_directly():
>>> w.color >>> w.color
'red' 'red'
>>> w = ImplicitAcquisitionWrapper(a.b) >>> w = ImplicitAcquisitionWrapper(a.b) # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: __init__() takes exactly 2 arguments (1 given) TypeError: __init__() takes exactly 2 arguments (1 given)
...@@ -1464,17 +1496,17 @@ def test_creating_wrappers_directly(): ...@@ -1464,17 +1496,17 @@ def test_creating_wrappers_directly():
Note that messing with the wrapper won't in any way affect the Note that messing with the wrapper won't in any way affect the
wrapped object: wrapped object:
>>> Acquisition.aq_base(w).__parent__ >>> Acquisition.aq_base(w).__parent__ # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last): Traceback (most recent call last):
... ...
AttributeError: __parent__ AttributeError: __parent__
>>> w = ImplicitAcquisitionWrapper() >>> w = ImplicitAcquisitionWrapper() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: __init__() takes exactly 2 arguments (0 given) 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): Traceback (most recent call last):
... ...
TypeError: kwyword arguments not allowed TypeError: kwyword arguments not allowed
...@@ -1579,7 +1611,10 @@ def test_cant_pickle_acquisition_wrappers_newstyle(): ...@@ -1579,7 +1611,10 @@ def test_cant_pickle_acquisition_wrappers_newstyle():
def test_cant_persist_acquisition_wrappers_classic(): def test_cant_persist_acquisition_wrappers_classic():
""" """
>>> import cPickle >>> try:
... import cPickle
... except ImportError:
... import pickle as cPickle
>>> class X: >>> class X:
... _p_oid = '1234' ... _p_oid = '1234'
...@@ -1604,8 +1639,8 @@ def test_cant_persist_acquisition_wrappers_classic(): ...@@ -1604,8 +1639,8 @@ def test_cant_persist_acquisition_wrappers_classic():
Check custom pickler: Check custom pickler:
>>> from cStringIO import StringIO >>> from io import BytesIO
>>> file = StringIO() >>> file = BytesIO()
>>> pickler = cPickle.Pickler(file, 1) >>> pickler = cPickle.Pickler(file, 1)
>>> pickler.dump(w) >>> pickler.dump(w)
...@@ -1616,29 +1651,35 @@ def test_cant_persist_acquisition_wrappers_classic(): ...@@ -1616,29 +1651,35 @@ def test_cant_persist_acquisition_wrappers_classic():
Check custom pickler with a persistent_id method matching the semantics Check custom pickler with a persistent_id method matching the semantics
in ZODB.serialize.ObjectWriter.persistent_id: in ZODB.serialize.ObjectWriter.persistent_id:
>>> file = StringIO() >>> file = BytesIO()
>>> pickler = cPickle.Pickler(file, 1) >>> pickler = cPickle.Pickler(file, 1)
>>> def persistent_id(obj): >>> def persistent_id(obj):
... if not hasattr(obj, '_p_oid'): return None
... klass = type(obj) ... klass = type(obj)
... oid = obj._p_oid ... oid = obj._p_oid
... if hasattr(klass, '__getnewargs__'): ... if hasattr(klass, '__getnewargs__'):
... return oid ... return oid
... return 'class_and_oid', klass ... 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) >>> _ = pickler.dump(w)
>>> state = file.getvalue() >>> state = file.getvalue()
>>> '1234' in state >>> b'1234' in state
True True
>>> 'class_and_oid' in state >>> b'class_and_oid' in state
False False
""" """
def test_cant_persist_acquisition_wrappers_newstyle(): def test_cant_persist_acquisition_wrappers_newstyle():
""" """
>>> import cPickle >>> try:
... import cPickle
... except ImportError:
... import pickle as cPickle
>>> class X(object): >>> class X(object):
... _p_oid = '1234' ... _p_oid = '1234'
...@@ -1663,8 +1704,8 @@ def test_cant_persist_acquisition_wrappers_newstyle(): ...@@ -1663,8 +1704,8 @@ def test_cant_persist_acquisition_wrappers_newstyle():
Check custom pickler: Check custom pickler:
>>> from cStringIO import StringIO >>> from io import BytesIO
>>> file = StringIO() >>> file = BytesIO()
>>> pickler = cPickle.Pickler(file, 1) >>> pickler = cPickle.Pickler(file, 1)
>>> pickler.dump(w) >>> pickler.dump(w)
...@@ -1675,22 +1716,26 @@ def test_cant_persist_acquisition_wrappers_newstyle(): ...@@ -1675,22 +1716,26 @@ def test_cant_persist_acquisition_wrappers_newstyle():
Check custom pickler with a persistent_id method matching the semantics Check custom pickler with a persistent_id method matching the semantics
in ZODB.serialize.ObjectWriter.persistent_id: in ZODB.serialize.ObjectWriter.persistent_id:
>>> file = StringIO() >>> file = BytesIO()
>>> pickler = cPickle.Pickler(file, 1) >>> pickler = cPickle.Pickler(file, 1)
>>> def persistent_id(obj): >>> def persistent_id(obj):
... if not hasattr(obj, '_p_oid'): return None
... klass = type(obj) ... klass = type(obj)
... oid = obj._p_oid ... oid = obj._p_oid
... if hasattr(klass, '__getnewargs__'): ... if hasattr(klass, '__getnewargs__'):
... return oid ... return oid
... return 'class_and_oid', klass ... 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) >>> _ = pickler.dump(w)
>>> state = file.getvalue() >>> state = file.getvalue()
>>> '1234' in state >>> b'1234' in state
True True
>>> 'class_and_oid' in state >>> b'class_and_oid' in state
False False
""" """
...@@ -1721,9 +1766,94 @@ def test_interfaces(): ...@@ -1721,9 +1766,94 @@ def test_interfaces():
True True
""" """
if PY2:
# Assigning to __bases__ is difficult under Python 3.
# 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): def show(x):
print showaq(x).strip() print(showaq(x).strip())
def showaq(m_self, indent=''): def showaq(m_self, indent=''):
...@@ -1754,9 +1884,10 @@ def showaq(m_self, indent=''): ...@@ -1754,9 +1884,10 @@ def showaq(m_self, indent=''):
rval = rval + indent + id + "\n" rval = rval + indent + id + "\n"
return rval return rval
def test_Basic_gc(): 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 >>> from ExtensionClass import Base
>>> import gc >>> import gc
...@@ -1769,7 +1900,7 @@ def test_Basic_gc(): ...@@ -1769,7 +1900,7 @@ def test_Basic_gc():
... ...
... class C2(Base): ... class C2(Base):
... def __del__(self): ... def __del__(self):
... print 'removed' ... print('removed')
... ...
... a=C1('a') ... a=C1('a')
... a.b = C1('a.b') ... a.b = C1('a.b')
...@@ -1778,7 +1909,7 @@ def test_Basic_gc(): ...@@ -1778,7 +1909,7 @@ def test_Basic_gc():
... ignore = gc.collect() ... ignore = gc.collect()
... del a ... del a
... removed = gc.collect() ... removed = gc.collect()
... print removed > 0 ... print(removed > 0 or PYPY)
removed removed
True True
removed removed
...@@ -1787,10 +1918,10 @@ def test_Basic_gc(): ...@@ -1787,10 +1918,10 @@ def test_Basic_gc():
>>> gc.set_threshold(*thresholds) >>> gc.set_threshold(*thresholds)
""" """
def test_Wrapper_gc(): 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 >>> import gc
>>> thresholds = gc.get_threshold() >>> thresholds = gc.get_threshold()
...@@ -1799,7 +1930,7 @@ def test_Wrapper_gc(): ...@@ -1799,7 +1930,7 @@ def test_Wrapper_gc():
>>> for B in I, E: >>> for B in I, E:
... class C: ... class C:
... def __del__(self): ... def __del__(self):
... print 'removed' ... print('removed')
... ...
... a=B('a') ... a=B('a')
... a.b = B('b') ... a.b = B('b')
...@@ -1808,7 +1939,7 @@ def test_Wrapper_gc(): ...@@ -1808,7 +1939,7 @@ def test_Wrapper_gc():
... ignored = gc.collect() ... ignored = gc.collect()
... del a ... del a
... removed = gc.collect() ... removed = gc.collect()
... removed > 0 ... removed > 0 or PYPY
removed removed
True True
removed removed
...@@ -1816,11 +1947,11 @@ def test_Wrapper_gc(): ...@@ -1816,11 +1947,11 @@ def test_Wrapper_gc():
>>> gc.set_threshold(*thresholds) >>> gc.set_threshold(*thresholds)
""" """
def test_proxying(): def test_container_proxying():
"""Make sure that recent python slots are proxied. """Make sure that recent python container-related slots are proxied.
>>> import sys >>> import sys
>>> import Acquisition >>> import Acquisition
...@@ -1829,18 +1960,21 @@ def test_proxying(): ...@@ -1829,18 +1960,21 @@ def test_proxying():
>>> class C(Acquisition.Implicit): >>> class C(Acquisition.Implicit):
... def __getitem__(self, key): ... def __getitem__(self, key):
... print 'getitem', key ... if isinstance(key, slice):
... print('slicing...')
... return (key.start,key.stop)
... print('getitem', key)
... if key == 4: ... if key == 4:
... raise IndexError ... raise IndexError
... return key ... return key
... def __contains__(self, key): ... def __contains__(self, key):
... print 'contains', repr(key) ... print('contains', repr(key))
... return key == 5 ... return key == 5
... def __iter__(self): ... def __iter__(self):
... print 'iterating...' ... print('iterating...')
... return iter((42,)) ... return iter((42,))
... def __getslice__(self, start, end): ... def __getslice__(self, start, end):
... print 'slicing...' ... print('slicing...')
... return (start, end) ... return (start, end)
The naked class behaves like this: The naked class behaves like this:
...@@ -1858,7 +1992,7 @@ def test_proxying(): ...@@ -1858,7 +1992,7 @@ def test_proxying():
>>> c[5:10] >>> c[5:10]
slicing... slicing...
(5, 10) (5, 10)
>>> c[5:] == (5, sys.maxsize) >>> c[5:] == (5, sys.maxsize if PY2 else None)
slicing... slicing...
True True
...@@ -1881,7 +2015,7 @@ def test_proxying(): ...@@ -1881,7 +2015,7 @@ def test_proxying():
>>> i.c[5:10] >>> i.c[5:10]
slicing... slicing...
(5, 10) (5, 10)
>>> i.c[5:] == (5, sys.maxsize) >>> i.c[5:] == (5, sys.maxsize if PY2 else None)
slicing... slicing...
True True
...@@ -1893,18 +2027,21 @@ def test_proxying(): ...@@ -1893,18 +2027,21 @@ def test_proxying():
>>> class C(Acquisition.Explicit): >>> class C(Acquisition.Explicit):
... def __getitem__(self, key): ... def __getitem__(self, key):
... print 'getitem', key ... if isinstance(key, slice):
... print('slicing...')
... return (key.start,key.stop)
... print('getitem', key)
... if key == 4: ... if key == 4:
... raise IndexError ... raise IndexError
... return key ... return key
... def __contains__(self, key): ... def __contains__(self, key):
... print 'contains', repr(key) ... print('contains', repr(key))
... return key == 5 ... return key == 5
... def __iter__(self): ... def __iter__(self):
... print 'iterating...' ... print('iterating...')
... return iter((42,)) ... return iter((42,))
... def __getslice__(self, start, end): ... def __getslice__(self, start, end):
... print 'slicing...' ... print('slicing...')
... return (start, end) ... return (start, end)
The naked class behaves like this: The naked class behaves like this:
...@@ -1922,7 +2059,7 @@ def test_proxying(): ...@@ -1922,7 +2059,7 @@ def test_proxying():
>>> c[5:10] >>> c[5:10]
slicing... slicing...
(5, 10) (5, 10)
>>> c[5:] == (5, sys.maxsize) >>> c[5:] == (5, sys.maxsize if PY2 else None)
slicing... slicing...
True True
...@@ -1945,7 +2082,7 @@ def test_proxying(): ...@@ -1945,7 +2082,7 @@ def test_proxying():
>>> i.c[5:10] >>> i.c[5:10]
slicing... slicing...
(5, 10) (5, 10)
>>> i.c[5:] == (5, sys.maxsize) >>> i.c[5:] == (5, sys.maxsize if PY2 else None)
slicing... slicing...
True True
...@@ -1959,14 +2096,14 @@ def test_proxying(): ...@@ -1959,14 +2096,14 @@ def test_proxying():
... return self.l[i] ... return self.l[i]
>>> c1 = C() >>> c1 = C()
>>> type(iter(c1)) >>> type(iter(c1)) #doctest: +ELLIPSIS
<type 'iterator'> <... '...iterator'>
>>> list(c1) >>> list(c1)
[1, 2, 3] [1, 2, 3]
>>> c2 = C().__of__(c1) >>> c2 = C().__of__(c1)
>>> type(iter(c2)) >>> type(iter(c2)) #doctest: +ELLIPSIS
<type 'iterator'> <... '...iterator'>
>>> list(c2) >>> list(c2)
[1, 2, 3] [1, 2, 3]
...@@ -1975,7 +2112,7 @@ def test_proxying(): ...@@ -1975,7 +2112,7 @@ def test_proxying():
>>> class C(Acquisition.Implicit): >>> class C(Acquisition.Implicit):
... def __iter__(self): ... def __iter__(self):
... print 'iterating...' ... print('iterating...')
... for i in range(5): ... for i in range(5):
... yield i, self.aq_parent.name ... yield i, self.aq_parent.name
>>> c = C() >>> c = C()
...@@ -2007,10 +2144,10 @@ def test_proxying(): ...@@ -2007,10 +2144,10 @@ def test_proxying():
>>> c = C() >>> c = C()
>>> i = Impl() >>> i = Impl()
>>> i.c = c >>> i.c = c
>>> list(i.c) >>> list(i.c) #doctest: +ELLIPSIS
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: iteration over non-sequence TypeError: ...iter...
>>> class C(Acquisition.Implicit): >>> class C(Acquisition.Implicit):
... def __iter__(self): ... def __iter__(self):
...@@ -2018,14 +2155,13 @@ def test_proxying(): ...@@ -2018,14 +2155,13 @@ def test_proxying():
>>> c = C() >>> c = C()
>>> i = Impl() >>> i = Impl()
>>> i.c = c >>> i.c = c
>>> list(i.c) >>> list(i.c) #doctest: +ELLIPSIS
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: iter() returned non-iterator of type 'list' TypeError: iter() returned non-iterator...
""" """
class Location(object): class Location(object):
__parent__ = None __parent__ = None
...@@ -2388,7 +2524,8 @@ def test___parent__aq_parent_circles(): ...@@ -2388,7 +2524,8 @@ def test___parent__aq_parent_circles():
>>> x.__parent__.aq_base is y.aq_base >>> x.__parent__.aq_base is y.aq_base
True True
>>> Acquisition.aq_parent(x) is y
True
>>> x.__parent__.__parent__ is x >>> x.__parent__.__parent__ is x
True True
...@@ -2463,8 +2600,6 @@ def test__iter__after_AttributeError(): ...@@ -2463,8 +2600,6 @@ def test__iter__after_AttributeError():
... raise ... raise
""" """
import unittest
from doctest import DocTestSuite, DocFileSuite
class TestParent(unittest.TestCase): class TestParent(unittest.TestCase):
...@@ -2635,6 +2770,355 @@ class TestUnicode(unittest.TestCase): ...@@ -2635,6 +2770,355 @@ class TestUnicode(unittest.TestCase):
inner = A().__of__(outer) inner = A().__of__(outer)
self.assertEqual(u'True', unicode(inner)) self.assertEqual(u'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(): def test_suite():
import os.path import os.path
...@@ -2647,6 +3131,7 @@ def test_suite(): ...@@ -2647,6 +3131,7 @@ def test_suite():
unittest.makeSuite(TestParent), unittest.makeSuite(TestParent),
unittest.makeSuite(TestAcquire), unittest.makeSuite(TestAcquire),
unittest.makeSuite(TestUnicode), unittest.makeSuite(TestUnicode),
unittest.makeSuite(TestProxying),
] ]
# This file is only available in a source checkout, skip it # This file is only available in a source checkout, skip it
......
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