From 118664f9c9e2e6e8fb0a8af4d015d905fe3d6422 Mon Sep 17 00:00:00 2001 From: Andreas Zeidler Date: Thu, 29 Oct 2009 09:45:59 +0000 Subject: [PATCH] backport fix for LP #360761 (r105349:105351) to Zope 2.11 --- _Acquisition.c | 27 ++++++++++++++++++++++- tests.py | 58 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/_Acquisition.c b/_Acquisition.c index 529f269..079495c 100644 --- a/_Acquisition.c +++ b/_Acquisition.c @@ -819,10 +819,35 @@ Wrapper_contains(Wrapper *self, PyObject *v) return c; } +/* Support for iteration cannot rely on the internal implementation of + `PyObject_GetIter`, since the `self` passed into `__iter__` and + `__getitem__` should be acquisition-wrapped (also see LP 360761): The + wrapper obviously supports the iterator protocol so simply calling + `PyObject_GetIter(OBJECT(self))` results in an infinite recursion. + Instead the base object needs to be checked and the wrapper must only + be used when actually calling `__getitem__` or setting up a sequence + iterator. */ static PyObject * Wrapper_iter(Wrapper *self) { - return PyObject_GetIter(self->obj); + PyObject *obj = self->obj; + PyObject *res; + if ((res=PyObject_GetAttr(OBJECT(self),py__iter__))) { + ASSIGN(res,PyObject_CallFunction(res,NULL,NULL)); + if (res != NULL && !PyIter_Check(res)) { + PyErr_Format(PyExc_TypeError, + "iter() returned non-iterator " + "of type '%.100s'", + res->ob_type->tp_name); + Py_DECREF(res); + res = NULL; + } + } else if (PySequence_Check(obj)) { + ASSIGN(res,PySeqIter_New(OBJECT(self))); + } else { + res = PyErr_Format(PyExc_TypeError, "iteration over non-sequence"); + } + return res; } static PySequenceMethods Wrapper_as_sequence = { diff --git a/tests.py b/tests.py index 4264ae5..d6b2e20 100644 --- a/tests.py +++ b/tests.py @@ -1719,8 +1719,9 @@ def test_proxying(): iterating... [42] - Finally let's check that https://bugs.launchpad.net/zope2/+bug/360761 - has been fixed: + Next let's check that the wrapper's __iter__ proxy falls back + to using the object's __getitem__ if it has no __iter__. See + https://bugs.launchpad.net/zope2/+bug/360761 . >>> class C(Acquisition.Implicit): ... l=[1,2,3] @@ -1739,6 +1740,59 @@ def test_proxying(): >>> list(c2) [1, 2, 3] + The __iter__proxy should also pass the wrapped object as self to + the __iter__ of objects defining __iter__:: + + >>> class C(Acquisition.Implicit): + ... def __iter__(self): + ... print 'iterating...' + ... for i in range(5): + ... yield i, self.aq_parent.name + >>> c = C() + >>> i = Impl() + >>> i.c = c + >>> i.name = 'i' + >>> list(i.c) + iterating... + [(0, 'i'), (1, 'i'), (2, 'i'), (3, 'i'), (4, 'i')] + + And it should pass the wrapped object as self to + the __getitem__ of objects without an __iter__:: + + >>> class C(Acquisition.Implicit): + ... def __getitem__(self, i): + ... return self.aq_parent.l[i] + >>> c = C() + >>> i = Impl() + >>> i.c = c + >>> i.l = range(5) + >>> list(i.c) + [0, 1, 2, 3, 4] + + Finally let's make sure errors are still correctly raised after having + to use a modified version of `PyObject_GetIter` for iterator support:: + + >>> class C(Acquisition.Implicit): + ... pass + >>> c = C() + >>> i = Impl() + >>> i.c = c + >>> list(i.c) + Traceback (most recent call last): + ... + TypeError: iteration over non-sequence + + >>> class C(Acquisition.Implicit): + ... def __iter__(self): + ... return [42] + >>> c = C() + >>> i = Impl() + >>> i.c = c + >>> list(i.c) + Traceback (most recent call last): + ... + TypeError: iter() returned non-iterator of type 'list' + """