[Zope-Checkins] SVN: Zope/branches/2.10/ backport fix for LP #360761 (r105349:105351) to Zope 2.10

Andreas Zeidler az at zitc.de
Thu Oct 29 05:40:08 EDT 2009


Log message for revision 105352:
  backport fix for LP #360761 (r105349:105351) to Zope 2.10
  

Changed:
  U   Zope/branches/2.10/doc/CHANGES.txt
  U   Zope/branches/2.10/lib/python/Acquisition/_Acquisition.c
  U   Zope/branches/2.10/lib/python/Acquisition/tests.py

-=-
Modified: Zope/branches/2.10/doc/CHANGES.txt
===================================================================
--- Zope/branches/2.10/doc/CHANGES.txt	2009-10-29 09:18:53 UTC (rev 105351)
+++ Zope/branches/2.10/doc/CHANGES.txt	2009-10-29 09:40:08 UTC (rev 105352)
@@ -8,6 +8,9 @@
 
     Bugs fixed
 
+      - LP #360761 (backported from Acquisition trunk): fix iteration proxy
+        to pass `self` acquisition-wrapped into `__iter__` and `__getitem__`.
+
       - LP #414757 (backported from Zope trunk): don't emit a IEndRequestEvent
         when clearing a cloned request.
 

Modified: Zope/branches/2.10/lib/python/Acquisition/_Acquisition.c
===================================================================
--- Zope/branches/2.10/lib/python/Acquisition/_Acquisition.c	2009-10-29 09:18:53 UTC (rev 105351)
+++ Zope/branches/2.10/lib/python/Acquisition/_Acquisition.c	2009-10-29 09:40:08 UTC (rev 105352)
@@ -819,10 +819,35 @@
   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 = {

Modified: Zope/branches/2.10/lib/python/Acquisition/tests.py
===================================================================
--- Zope/branches/2.10/lib/python/Acquisition/tests.py	2009-10-29 09:18:53 UTC (rev 105351)
+++ Zope/branches/2.10/lib/python/Acquisition/tests.py	2009-10-29 09:40:08 UTC (rev 105352)
@@ -1719,8 +1719,9 @@
     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 @@
     >>> 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'
+
     """
 
 



More information about the Zope-Checkins mailing list