Commit 5438ceaf authored by Jason Madden's avatar Jason Madden

Add support for PyPy and Python 3.4 using a pure-python port of the C extension code.

All tests continue to pass under 2.6 and 2.7.

The pure-python code currently raises AttributeError instead of RuntimeError in the
recursive-parent cases, though I'm not sure why that is. There may also be some issues
with the proxying of some methods, but those aren't covered by current test cases.
parent 48c7341c
...@@ -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,6 +24,21 @@ with open('README.rst') as f: ...@@ -22,6 +24,21 @@ 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.1',
...@@ -41,16 +58,15 @@ setup( ...@@ -41,16 +58,15 @@ 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.1a1',
'zope.interface', 'zope.interface',
......
from __future__ import absolute_import, print_function
# pylint:disable=W0212,R0911,R0912
import os
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 from ._proxy import PyProxyBase
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."""
# Note specifically this is a type check, not duck typing, or
# we get into cycles
return isinstance(obj, ExtensionClass.Base)
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
# Trivial port of the C function
# 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__'):
return object.__getattribute__(wrapper, orig_name)
# If we're doing a containment search, replace the wrapper with aq_inner
if containment:
while wrapper._obj is not None and 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(PyProxyBase):
__slots__ = ('_container',)
_IS_IMPLICIT = None
def __new__(cls, obj, container):
inst = PyProxyBase.__new__(cls, obj)
inst._container = container
return inst
def __init__(self, obj, container):
PyProxyBase.__init__(self, obj)
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 ()
# Methods looked up by the type of self._obj
# NOTE: This is probably incomplete
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)
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)
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):
# Workaround ExtensionClass bug #3
try:
if not isinstance(self, _Wrapper) \
and self is object.__getattribute__(context, '__parent__'):
return self
except AttributeError:
pass
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__
# XXX have to implement the mixed checking
return getattr(obj, '__parent__', None)
def aq_chain(obj, containment=False):
# This is a straight port of the C code,
# but it doesn't work for the containment branch...
# not that I really understand what that branch is supposed to do
def isWrapper(self):
return isinstance(self, _Wrapper)
result = []
while True:
if isWrapper(obj):
if obj._obj is not None:
if containment:
while obj._obj is not None and isWrapper(obj._obj):
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.aq_self
while isinstance(result, _Wrapper):
obj = result
result = result.aq_self
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)
......
##############################################################################
#
# Copyright (c) 2003 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Taken from zope.proxy to support acquisition wrappers.
"""
import operator
import pickle
import sys
import ExtensionClass
_MARKER = object()
class PyProxyBase(ExtensionClass.Base):
"""Reference implementation.
"""
__slots__ = ('_obj', )
def __new__(cls, value):
inst = super(PyProxyBase, cls).__new__(cls)
inst._obj = value
return inst
def __init__(self, obj):
self._obj = obj
def __call__(self, *args, **kw):
return self._obj(*args, **kw)
def __repr__(self):
return repr(self._obj)
def __str__(self):
return str(self._obj)
def __unicode__(self):
return unicode(self._obj)
def __reduce__(self): #pragma NO COVER (__reduce_ex__ prevents normal)
raise pickle.PicklingError
def __reduce_ex__(self, proto):
raise pickle.PicklingError
# Rich comparison protocol
def __lt__(self, other):
return self._obj < other
def __le__(self, other):
return self._obj <= other
def __eq__(self, other):
return self._obj == other
def __ne__(self, other):
return self._obj != other
def __gt__(self, other):
return self._obj > other
def __ge__(self, other):
return self._obj >= other
def __nonzero__(self):
return bool(self._obj)
__bool__ = __nonzero__ # Python3 compat
def __hash__(self):
return hash(self._obj)
# Attribute protocol
# Left for the _Wrapper subclass
# Container protocols
def __len__(self):
return len(self._obj)
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, 'setslice'): # PY2
return operator.getslice(self._obj, start, stop)
return self._obj[start:stop]
return self._obj[key]
def __setitem__(self, key, value):
self._obj[key] = value
def __delitem__(self, key):
del self._obj[key]
def __iter__(self):
# This handles a custom __iter__ and generator support at the same time.
return iter(self._obj)
def next(self):
# Called when we wrap an iterator itself.
return self._obj.next()
def __next__(self): #pragma NO COVER Python3
return self._obj.__next__()
# Python 2.7 won't let the C wrapper support __reversed__ :(
#def __reversed__(self):
# return reversed(self._obj)
def __contains__(self, item):
return item in self._obj
# Numeric protocol: unary operators
def __neg__(self):
return -self._obj
def __pos__(self):
return +self._obj
def __abs__(self):
return abs(self._obj)
def __invert__(self):
return ~self._obj
# Numeric protocol: unary conversions
def __complex__(self):
return complex(self._obj)
def __int__(self):
return int(self._obj)
def __long__(self):
return long(self._obj)
def __float__(self):
return float(self._obj)
def __oct__(self):
return oct(self._obj)
def __hex__(self):
return hex(self._obj)
def __index__(self):
return operator.index(self._obj)
# Numeric protocol: binary coercion
def __coerce__(self, other):
left, right = coerce(self._obj, other)
if left == self._obj and type(left) is type(self._obj):
left = self
return left, right
# Numeric protocol: binary arithmetic operators
def __add__(self, other):
return self._obj + other
def __sub__(self, other):
return self._obj - other
def __mul__(self, other):
return self._obj * other
def __floordiv__(self, other):
return self._obj // other
def __truediv__(self, other): #pragma NO COVER
# Only one of __truediv__ and __div__ is meaningful at any one time.
return self._obj / other
def __div__(self, other): #pragma NO COVER
# Only one of __truediv__ and __div__ is meaningful at any one time.
return self._obj / other
def __mod__(self, other):
return self._obj % other
def __divmod__(self, other):
return divmod(self._obj, other)
def __pow__(self, other, modulus=None):
if modulus is None:
return pow(self._obj, other)
return pow(self._obj, other, modulus)
def __radd__(self, other):
return other + self._obj
def __rsub__(self, other):
return other - self._obj
def __rmul__(self, other):
return other * self._obj
def __rfloordiv__(self, other):
return other // self._obj
def __rtruediv__(self, other): #pragma NO COVER
# Only one of __rtruediv__ and __rdiv__ is meaningful at any one time.
return other / self._obj
def __rdiv__(self, other): #pragma NO COVER
# Only one of __rtruediv__ and __rdiv__ is meaningful at any one time.
return other / self._obj
def __rmod__(self, other):
return other % self._obj
def __rdivmod__(self, other):
return divmod(other, self._obj)
def __rpow__(self, other, modulus=None):
if modulus is None:
return pow(other, self._obj)
# We can't actually get here, because we can't lie about our type()
return pow(other, self._obj, modulus) #pragma NO COVER
# Numeric protocol: binary bitwise operators
def __lshift__(self, other):
return self._obj << other
def __rshift__(self, other):
return self._obj >> other
def __and__(self, other):
return self._obj & other
def __xor__(self, other):
return self._obj ^ other
def __or__(self, other):
return self._obj | other
def __rlshift__(self, other):
return other << self._obj
def __rrshift__(self, other):
return other >> self._obj
def __rand__(self, other):
return other & self._obj
def __rxor__(self, other):
return other ^ self._obj
def __ror__(self, other):
return other | self._obj
# Numeric protocol: binary in-place operators
def __iadd__(self, other):
self._obj += other
return self
def __isub__(self, other):
self._obj -= other
return self
def __imul__(self, other):
self._obj *= other
return self
def __idiv__(self, other): #pragma NO COVER
# Only one of __itruediv__ and __idiv__ is meaningful at any one time.
self._obj /= other
return self
def __itruediv__(self, other): #pragma NO COVER
# Only one of __itruediv__ and __idiv__ is meaningful at any one time.
self._obj /= other
return self
def __ifloordiv__(self, other):
self._obj //= other
return self
def __imod__(self, other):
self._obj %= other
return self
def __ilshift__(self, other):
self._obj <<= other
return self
def __irshift__(self, other):
self._obj >>= other
return self
def __iand__(self, other):
self._obj &= other
return self
def __ixor__(self, other):
self._obj ^= other
return self
def __ior__(self, other):
self._obj |= other
return self
def __ipow__(self, other, modulus=None):
if modulus is None:
self._obj **= other
else: #pragma NO COVER
# There is no syntax which triggers in-place pow w/ modulus
self._obj = pow(self._obj, other, modulus)
return self
...@@ -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,26 @@ ...@@ -328,6 +328,26 @@
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
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)
else:
PY2 = True
PY3 = False
import ExtensionClass import ExtensionClass
import Acquisition import Acquisition
...@@ -558,7 +578,6 @@ def test_simple(): ...@@ -558,7 +578,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 +585,10 @@ def test__of__exception(): ...@@ -566,13 +585,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 +598,7 @@ def test__of__exception(): ...@@ -582,7 +598,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 +1252,8 @@ def test_aq_inContextOf(): ...@@ -1236,7 +1252,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 +1261,17 @@ def test_aq_inContextOf(): ...@@ -1244,15 +1261,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 +1368,7 @@ def test_AqAlg(): ...@@ -1349,7 +1368,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 +1376,7 @@ def test_AqAlg(): ...@@ -1357,7 +1376,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 +1385,7 @@ def test_AqAlg(): ...@@ -1366,7 +1385,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 +1394,7 @@ def test_AqAlg(): ...@@ -1375,7 +1394,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 +1403,7 @@ def test_AqAlg(): ...@@ -1384,7 +1403,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 +1426,22 @@ def test_explicit_acquisition(): ...@@ -1407,19 +1426,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 +1464,7 @@ def test_creating_wrappers_directly(): ...@@ -1442,7 +1464,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 +1486,17 @@ def test_creating_wrappers_directly(): ...@@ -1464,17 +1486,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 +1601,10 @@ def test_cant_pickle_acquisition_wrappers_newstyle(): ...@@ -1579,7 +1601,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,7 +1629,7 @@ def test_cant_persist_acquisition_wrappers_classic(): ...@@ -1604,7 +1629,7 @@ def test_cant_persist_acquisition_wrappers_classic():
Check custom pickler: Check custom pickler:
>>> from cStringIO import StringIO >>> from io import BytesIO as StringIO
>>> file = StringIO() >>> file = StringIO()
>>> pickler = cPickle.Pickler(file, 1) >>> pickler = cPickle.Pickler(file, 1)
...@@ -1620,25 +1645,31 @@ def test_cant_persist_acquisition_wrappers_classic(): ...@@ -1620,25 +1645,31 @@ def test_cant_persist_acquisition_wrappers_classic():
>>> 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,7 +1694,7 @@ def test_cant_persist_acquisition_wrappers_newstyle(): ...@@ -1663,7 +1694,7 @@ def test_cant_persist_acquisition_wrappers_newstyle():
Check custom pickler: Check custom pickler:
>>> from cStringIO import StringIO >>> from io import BytesIO as StringIO
>>> file = StringIO() >>> file = StringIO()
>>> pickler = cPickle.Pickler(file, 1) >>> pickler = cPickle.Pickler(file, 1)
...@@ -1679,18 +1710,22 @@ def test_cant_persist_acquisition_wrappers_newstyle(): ...@@ -1679,18 +1710,22 @@ def test_cant_persist_acquisition_wrappers_newstyle():
>>> 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
""" """
...@@ -1723,7 +1758,7 @@ def test_interfaces(): ...@@ -1723,7 +1758,7 @@ def test_interfaces():
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,71 +1789,70 @@ def showaq(m_self, indent=''): ...@@ -1754,71 +1789,70 @@ def showaq(m_self, indent=''):
rval = rval + indent + id + "\n" rval = rval + indent + id + "\n"
return rval return rval
if hasattr(gc, 'get_threshold'):
#CPython implementation detail
def test_Basic_gc():
"""Test to make sure that EC instances participate in GC
def test_Basic_gc(): >>> from ExtensionClass import Base
"""Test to make sure that EC instances participate in GC >>> import gc
>>> thresholds = gc.get_threshold()
>>> from ExtensionClass import Base >>> gc.set_threshold(0)
>>> import gc
>>> thresholds = gc.get_threshold()
>>> gc.set_threshold(0)
>>> for B in I, E: >>> for B in I, E:
... class C1(B): ... class C1(B):
... pass ... pass
... ...
... 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')
... a.b.a = a ... a.b.a = a
... a.b.c = C2() ... a.b.c = C2()
... ignore = gc.collect() ... ignore = gc.collect()
... del a ... del a
... removed = gc.collect() ... removed = gc.collect()
... print removed > 0 ... print(removed > 0)
removed removed
True True
removed removed
True True
>>> gc.set_threshold(*thresholds) >>> gc.set_threshold(*thresholds)
"""
def test_Wrapper_gc():
"""Test to make sure that EC instances participate in GC
>>> import gc
>>> thresholds = gc.get_threshold()
>>> gc.set_threshold(0)
>>> for B in I, E:
... class C:
... def __del__(self):
... print('removed')
...
... a=B('a')
... a.b = B('b')
... a.a_b = a.b # circ ref through wrapper
... a.b.c = C()
... ignored = gc.collect()
... del a
... removed = gc.collect()
... removed > 0
removed
True
removed
True
>>> gc.set_threshold(*thresholds)
""" """
def test_Wrapper_gc():
"""Test to make sure that EC instances participate in GC
>>> import gc
>>> thresholds = gc.get_threshold()
>>> gc.set_threshold(0)
>>> for B in I, E:
... class C:
... def __del__(self):
... print 'removed'
...
... a=B('a')
... a.b = B('b')
... a.a_b = a.b # circ ref through wrapper
... a.b.c = C()
... ignored = gc.collect()
... del a
... removed = gc.collect()
... removed > 0
removed
True
removed
True
>>> gc.set_threshold(*thresholds)
"""
def test_proxying(): def test_proxying():
"""Make sure that recent python slots are proxied. """Make sure that recent python slots are proxied.
...@@ -1829,18 +1863,21 @@ def test_proxying(): ...@@ -1829,18 +1863,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 +1895,7 @@ def test_proxying(): ...@@ -1858,7 +1895,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 +1918,7 @@ def test_proxying(): ...@@ -1881,7 +1918,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 +1930,21 @@ def test_proxying(): ...@@ -1893,18 +1930,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 +1962,7 @@ def test_proxying(): ...@@ -1922,7 +1962,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 +1985,7 @@ def test_proxying(): ...@@ -1945,7 +1985,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 +1999,14 @@ def test_proxying(): ...@@ -1959,14 +1999,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 +2015,7 @@ def test_proxying(): ...@@ -1975,7 +2015,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 +2047,10 @@ def test_proxying(): ...@@ -2007,10 +2047,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,10 +2058,10 @@ def test_proxying(): ...@@ -2018,10 +2058,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: iter() returned non-iterator of type 'list' TypeError: iter() returned non-iterator...
""" """
...@@ -2463,8 +2503,6 @@ def test__iter__after_AttributeError(): ...@@ -2463,8 +2503,6 @@ def test__iter__after_AttributeError():
... raise ... raise
""" """
import unittest
from doctest import DocTestSuite, DocFileSuite
class TestParent(unittest.TestCase): class TestParent(unittest.TestCase):
......
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