Merged from old philikon-aq-and-__parent__ branch:

Log message for revision 71221:
  Step 2: Make aq_acquire aware of __parent__ pointers, even if the object
  isn't acquisition wrapped.

Log message for revision 71223:
  Add another test that tests acquisition wrappers with containers that have __parent__.

Log message for revision 71225:
  Cosmetics: adjust a piece of code that I added earlier to the indentation
  style of the overall file

Log message for revision 71226:
  Cleanup:
  * no need to introduce another variable where we check for a __parent__ attribute
  * clean up after failed getattr (it throws an AttributeError)
  * properly DECREF the __parent__ attribute when it's no longer needed and
    the wrapper that is temporarily created from the __parent__ attribute.

Log message for revision 75578:
  Added a test to Acquisition that shows the current segmentation fault problem, when Acquisition goes in circles.

Log message for revision 76140:
  First attempt to fix 'Acquisition problem' when encountering cyclic hierarchies via __parent__ pointers. [hannosch, nouri]


In addition, Hanno and Nouri's fix was expanded to not only cover circular __parent__
pointers but also to cover a mixture of circular __parent__ and aq_parent pointers
(which can occur when old Implicit acquisition meets new __parent__ pointer code).

Also cleaned up much of the comments and added more comments.
parent fbe184fd
...@@ -38,7 +38,8 @@ static PyObject *py__add__, *py__sub__, *py__mul__, *py__div__, ...@@ -38,7 +38,8 @@ static PyObject *py__add__, *py__sub__, *py__mul__, *py__div__,
*py__long__, *py__float__, *py__oct__, *py__hex__, *py__long__, *py__float__, *py__oct__, *py__hex__,
*py__getitem__, *py__setitem__, *py__delitem__, *py__getitem__, *py__setitem__, *py__delitem__,
*py__getslice__, *py__setslice__, *py__delslice__, *py__contains__, *py__getslice__, *py__setslice__, *py__delslice__, *py__contains__,
*py__len__, *py__of__, *py__call__, *py__repr__, *py__str__, *py__cmp__; *py__len__, *py__of__, *py__call__, *py__repr__, *py__str__, *py__cmp__,
*py__parent__;
static PyObject *Acquired=0; static PyObject *Acquired=0;
...@@ -82,7 +83,7 @@ init_py_names(void) ...@@ -82,7 +83,7 @@ init_py_names(void)
INIT_PY_NAME(__repr__); INIT_PY_NAME(__repr__);
INIT_PY_NAME(__str__); INIT_PY_NAME(__str__);
INIT_PY_NAME(__cmp__); INIT_PY_NAME(__cmp__);
INIT_PY_NAME(__parent__);
#undef INIT_PY_NAME #undef INIT_PY_NAME
} }
...@@ -414,6 +415,23 @@ static PyObject * ...@@ -414,6 +415,23 @@ static PyObject *
Wrapper_findattr(Wrapper *self, PyObject *oname, Wrapper_findattr(Wrapper *self, PyObject *oname,
PyObject *filter, PyObject *extra, PyObject *orig, PyObject *filter, PyObject *extra, PyObject *orig,
int sob, int sco, int explicit, int containment) int sob, int sco, int explicit, int containment)
/*
Parameters:
sob
Search self->obj for the 'oname' attribute
sco
Search self->container for the 'oname' attribute
explicit
Explicitly acquire 'oname' attribute from container (assumed with
implicit acquisition wrapper)
containment
Use the innermost wrapper ("aq_inner") for looking up the 'oname'
attribute.
*/
{ {
PyObject *r, *v, *tb; PyObject *r, *v, *tb;
char *name=""; char *name="";
...@@ -486,6 +504,7 @@ Wrapper_findattr(Wrapper *self, PyObject *oname, ...@@ -486,6 +504,7 @@ Wrapper_findattr(Wrapper *self, PyObject *oname,
Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb); Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb);
r=NULL; r=NULL;
} }
/* normal attribute lookup */
else if ((r=PyObject_GetAttr(self->obj,oname))) else if ((r=PyObject_GetAttr(self->obj,oname)))
{ {
if (r==Acquired) if (r==Acquired)
...@@ -520,6 +539,7 @@ Wrapper_findattr(Wrapper *self, PyObject *oname, ...@@ -520,6 +539,7 @@ Wrapper_findattr(Wrapper *self, PyObject *oname,
PyErr_Clear(); PyErr_Clear();
} }
/* Lookup has failed, acquire it from parent. */
if (sco && (*name != '_' || explicit)) if (sco && (*name != '_' || explicit))
return Wrapper_acquire(self, oname, filter, extra, orig, explicit, return Wrapper_acquire(self, oname, filter, extra, orig, explicit,
containment); containment);
...@@ -533,16 +553,19 @@ Wrapper_acquire(Wrapper *self, PyObject *oname, ...@@ -533,16 +553,19 @@ Wrapper_acquire(Wrapper *self, PyObject *oname,
PyObject *filter, PyObject *extra, PyObject *orig, PyObject *filter, PyObject *extra, PyObject *orig,
int explicit, int containment) int explicit, int containment)
{ {
PyObject *r; PyObject *r, *v, *tb;
int sob=1, sco=1; int sob=1, sco=1;
if (self->container) if (self->container)
{ {
/* If the container has an acquisition wrapper itself, we'll use
Wrapper_findattr to progress further. */
if (isWrapper(self->container)) if (isWrapper(self->container))
{ {
if (self->obj && isWrapper(self->obj)) if (self->obj && isWrapper(self->obj))
{ {
/* Try to optimize search by recognizing repeated obs in path */ /* Try to optimize search by recognizing repeated
objects in path. */
if (WRAPPER(self->obj)->container== if (WRAPPER(self->obj)->container==
WRAPPER(self->container)->container) WRAPPER(self->container)->container)
sco=0; sco=0;
...@@ -551,6 +574,14 @@ Wrapper_acquire(Wrapper *self, PyObject *oname, ...@@ -551,6 +574,14 @@ Wrapper_acquire(Wrapper *self, PyObject *oname,
sob=0; sob=0;
} }
/* Don't search the container when the container of the
container is the same object as 'self'. */
if (WRAPPER(self->container)->container == WRAPPER(self)->obj)
{
sco=0;
containment=1;
}
r=Wrapper_findattr((Wrapper*)self->container, r=Wrapper_findattr((Wrapper*)self->container,
oname, filter, extra, orig, sob, sco, explicit, oname, filter, extra, orig, sob, sco, explicit,
containment); containment);
...@@ -558,8 +589,46 @@ Wrapper_acquire(Wrapper *self, PyObject *oname, ...@@ -558,8 +589,46 @@ Wrapper_acquire(Wrapper *self, PyObject *oname,
if (r && has__of__(r)) ASSIGN(r,__of__(r,OBJECT(self))); if (r && has__of__(r)) ASSIGN(r,__of__(r,OBJECT(self)));
return r; return r;
} }
/* 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). */
else if ((r = PyObject_GetAttr(self->container, py__parent__)))
{
ASSIGN(self->container, newWrapper(self->container, r,
(PyTypeObject*)&Wrappertype));
/* Don't search the container when the parent of the parent
is the same object as 'self' */
if (WRAPPER(r)->obj == WRAPPER(self)->obj)
sco=0;
Py_DECREF(r); /* don't need __parent__ anymore */
r=Wrapper_findattr((Wrapper*)self->container,
oname, filter, extra, orig, sob, sco, explicit,
containment);
/* There's no need to DECREF the wrapper here because it's
not stored in self->container, thus 'self' owns its
reference now */
return r;
}
/* The container is the end of the acquisition chain; if we
can't look up the attribute here, we can't look it up at
all. */
else else
{ {
/* We need to clean up the AttributeError from the previous
getattr (because it has clearly failed). */
PyErr_Fetch(&r,&v,&tb);
if (r && (r != PyExc_AttributeError))
{
PyErr_Restore(r,v,tb);
return NULL;
}
Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb);
r=NULL;
if ((r=PyObject_GetAttr(self->container,oname))) { if ((r=PyObject_GetAttr(self->container,oname))) {
if (r == Acquired) { if (r == Acquired) {
Py_DECREF(r); Py_DECREF(r);
...@@ -1341,8 +1410,7 @@ static PyObject * ...@@ -1341,8 +1410,7 @@ static PyObject *
capi_aq_acquire(PyObject *self, PyObject *name, PyObject *filter, capi_aq_acquire(PyObject *self, PyObject *name, PyObject *filter,
PyObject *extra, int explicit, PyObject *defalt, int containment) PyObject *extra, int explicit, PyObject *defalt, int containment)
{ {
PyObject *result, *v, *tb;
PyObject *result;
if (filter==Py_None) filter=0; if (filter==Py_None) filter=0;
...@@ -1353,21 +1421,46 @@ capi_aq_acquire(PyObject *self, PyObject *name, PyObject *filter, ...@@ -1353,21 +1421,46 @@ capi_aq_acquire(PyObject *self, PyObject *name, PyObject *filter,
explicit || explicit ||
WRAPPER(self)->ob_type==(PyTypeObject*)&Wrappertype, WRAPPER(self)->ob_type==(PyTypeObject*)&Wrappertype,
explicit, containment); explicit, containment);
/* Not wrapped; check if we have a __parent__ pointer. If that's
the case, we create a wrapper and pretend it's business as
usual */
else if ((result = PyObject_GetAttr(self, py__parent__)))
{
self = newWrapper(self, result, (PyTypeObject*)&Wrappertype);
Py_DECREF(result); /* don't need __parent__ anymore */
result = Wrapper_findattr(WRAPPER(self), name, filter, extra,
OBJECT(self), 1, 1, explicit, containment);
/* Get rid of temporary wrapper */
Py_DECREF(self);
return result;
}
/* No wrapper and no __parent__, so just getattr. */
else
{
/* We need to clean up the AttributeError from the previous
getattr (because it has clearly failed). */
PyErr_Fetch(&result,&v,&tb);
if (result && (result != PyExc_AttributeError))
{
PyErr_Restore(result,v,tb);
return NULL;
}
Py_XDECREF(result); Py_XDECREF(v); Py_XDECREF(tb);
/* Not wrapped and no filter, so just getattr */
if (! filter) return PyObject_GetAttr(self, name); if (! filter) return PyObject_GetAttr(self, name);
/* Crap, we've got to construct a wrapper so we can use Wrapper_findattr */ /* Crap, we've got to construct a wrapper so we can use
Wrapper_findattr */
UNLESS (self=newWrapper(self, Py_None, (PyTypeObject*)&Wrappertype)) UNLESS (self=newWrapper(self, Py_None, (PyTypeObject*)&Wrappertype))
return NULL; return NULL;
result=Wrapper_findattr(WRAPPER(self), name, filter, extra, OBJECT(self), result=Wrapper_findattr(WRAPPER(self), name, filter, extra, OBJECT(self),
1, 1, explicit, containment); 1, 1, explicit, containment);
/* get rid of temp wrapper */ /* Get rid of temporary wrapper */
Py_DECREF(self); Py_DECREF(self);
return result; return result;
}
} }
static PyObject * static PyObject *
......
...@@ -1690,6 +1690,326 @@ def test_proxying(): ...@@ -1690,6 +1690,326 @@ def test_proxying():
""" """
class Location(object):
__parent__ = None
class ECLocation(ExtensionClass.Base):
__parent__ = None
def test___parent__no_wrappers():
"""
Acquisition also works with objects that aren't wrappers, as long
as they have __parent__ pointers. Let's take a hierarchy like
z --isParent--> y --isParent--> x:
>>> x = Location()
>>> y = Location()
>>> z = Location()
>>> x.__parent__ = y
>>> y.__parent__ = z
and some attributes that we want to acquire:
>>> x.hello = 'world'
>>> y.foo = 42
>>> z.foo = 43 # this should not be found
>>> z.bar = 3.145
``aq_acquire`` works we know it from implicit/acquisition wrappers:
>>> Acquisition.aq_acquire(x, 'hello')
'world'
>>> Acquisition.aq_acquire(x, 'foo')
42
>>> Acquisition.aq_acquire(x, 'bar')
3.145
TODO aq_parent, aq_chain
"""
def test_implicit_wrapper_as___parent__():
"""
Let's do the same test again, only now not all objects are of the
same kind and link to each other via __parent__ pointers. The
root is a stupid ExtensionClass object:
>>> class Root(ExtensionClass.Base):
... bar = 3.145
>>> z = Root()
The intermediate parent is an object that supports implicit
acquisition. We bind it to the root via the __of__ protocol:
>>> class Impl(Acquisition.Implicit):
... foo = 42
>>> y = Impl().__of__(z)
The child object is again a simple object with a simple __parent__
pointer:
>>> x = Location()
>>> x.hello = 'world'
>>> x.__parent__ = y
``aq_acquire`` works as expected from implicit/acquisition
wrappers:
>>> Acquisition.aq_acquire(x, 'hello')
'world'
>>> Acquisition.aq_acquire(x, 'foo')
42
>>> Acquisition.aq_acquire(x, 'bar')
3.145
Note that also the (implicit) acquisition wrapper has a __parent__
pointer, which is automatically computed from the acquisition
container (it's identical to aq_parent):
>>> y.__parent__ is z
True
Just as much as you can assign to aq_parent, you can also assign
to __parent__ to change the acquisition context of the wrapper:
>>> newroot = Root()
>>> y.__parent__ = newroot
>>> y.__parent__ is z
False
>>> y.__parent__ is newroot
True
Note that messing with the wrapper won't in any way affect the
wrapped object:
>>> Acquisition.aq_base(y).__parent__
Traceback (most recent call last):
...
AttributeError: __parent__
TODO aq_parent, aq_chain
"""
def test_explicit_wrapper_as___parent__():
"""
Let's do this test yet another time, with an explicit wrapper:
>>> class Root(ExtensionClass.Base):
... bar = 3.145
>>> z = Root()
The intermediate parent is an object that supports implicit
acquisition. We bind it to the root via the __of__ protocol:
>>> class Expl(Acquisition.Explicit):
... foo = 42
>>> y = Expl().__of__(z)
The child object is again a simple object with a simple __parent__
pointer:
>>> x = Location()
>>> x.hello = 'world'
>>> x.__parent__ = y
``aq_acquire`` works as expected from implicit/acquisition
wrappers:
>>> Acquisition.aq_acquire(x, 'hello')
'world'
>>> Acquisition.aq_acquire(x, 'foo')
42
>>> Acquisition.aq_acquire(x, 'bar')
3.145
Note that also the (explicit) acquisition wrapper has a __parent__
pointer, which is automatically computed from the acquisition
container (it's identical to aq_parent):
>>> y.__parent__ is z
True
Just as much as you can assign to aq_parent, you can also assign
to __parent__ to change the acquisition context of the wrapper:
>>> newroot = Root()
>>> y.__parent__ = newroot
>>> y.__parent__ is z
False
>>> y.__parent__ is newroot
True
Note that messing with the wrapper won't in any way affect the
wrapped object:
>>> Acquisition.aq_base(y).__parent__
Traceback (most recent call last):
...
AttributeError: __parent__
TODO aq_parent, aq_chain
"""
def test_implicit_wrapper_has_nonwrapper_as_aq_parent():
"""Let's do this the other way around: The root and the
intermediate parent is an object that doesn't support acquisition,
>>> y = ECLocation()
>>> z = Location()
>>> y.__parent__ = z
>>> y.foo = 42
>>> z.foo = 43 # this should not be found
>>> z.bar = 3.145
only the outmost object does:
>>> class Impl(Acquisition.Implicit):
... hello = 'world'
>>> x = Impl().__of__(y)
Again, acquiring objects work as usual:
>>> Acquisition.aq_acquire(x, 'hello')
'world'
>>> Acquisition.aq_acquire(x, 'foo')
42
>>> Acquisition.aq_acquire(x, 'bar')
3.145
Because the outmost object, ``x``, is wrapped in an implicit
acquisition wrapper, we can also use direct attribute access:
>>> x.hello
'world'
>>> x.foo
42
>>> x.bar
3.145
TODO aq_parent, aq_chain
"""
def test_explicit_wrapper_has_nonwrapper_as_aq_parent():
"""Let's do this the other way around: The root and the
intermediate parent is an object that doesn't support acquisition,
>>> y = ECLocation()
>>> z = Location()
>>> y.__parent__ = z
>>> y.foo = 42
>>> z.foo = 43 # this should not be found
>>> z.bar = 3.145
only the outmost object does:
>>> class Expl(Acquisition.Explicit):
... hello = 'world'
>>> x = Expl().__of__(y)
Again, acquiring objects work as usual:
>>> Acquisition.aq_acquire(x, 'hello')
'world'
>>> Acquisition.aq_acquire(x, 'foo')
42
>>> Acquisition.aq_acquire(x, 'bar')
3.145
TODO aq_parent, aq_chain
"""
def test___parent__aq_parent_circles():
"""
As a general safety belt, Acquisition won't follow a mixture of
circular __parent__ pointers and aq_parent wrappers. These can
occurr when code that uses implicit acquisition wrappers meets
code that uses __parent__ pointers.
>>> class Impl(Acquisition.Implicit):
... hello = 'world'
>>> class Impl2(Acquisition.Implicit):
... hello = 'world2'
... only = 'here'
>>> x = Impl()
>>> y = Impl2().__of__(x)
>>> x.__parent__ = y
>>> x.__parent__.aq_base is y.aq_base
True
>>> x.__parent__.__parent__ is x
True
>>> x.hello
'world'
>>> Acquisition.aq_acquire(x, 'hello')
'world'
>>> x.only
Traceback (most recent call last):
...
AttributeError: only
>>> Acquisition.aq_acquire(x, 'only')
'here'
>>> Acquisition.aq_acquire(x, 'non_existant_attr')
Traceback (most recent call last):
...
AttributeError: non_existant_attr
>>> Acquisition.aq_acquire(y, 'non_existant_attr')
Traceback (most recent call last):
...
AttributeError: non_existant_attr
>>> x.non_existant_attr
Traceback (most recent call last):
...
AttributeError: non_existant_attr
>>> y.non_existant_attr
Traceback (most recent call last):
...
AttributeError: non_existant_attr
"""
def test___parent__parent__circles():
"""
Acquisition won't follow circular __parent__ references:
>>> class Impl(Acquisition.Implicit):
... hello = 'world'
>>> class Impl2(Acquisition.Implicit):
... hello = 'world2'
... only = 'here'
>>> x = Impl()
>>> y = Impl2()
>>> x.__parent__ = y
>>> y.__parent__ = x
>>> x.__parent__.__parent__ is x
True
>>> Acquisition.aq_acquire(x, 'hello')
'world'
>>> Acquisition.aq_acquire(x, 'only')
'here'
>>> Acquisition.aq_acquire(x, 'non_existant_attr')
Traceback (most recent call last):
...
AttributeError: non_existant_attr
>>> Acquisition.aq_acquire(y, 'non_existant_attr')
Traceback (most recent call last):
...
AttributeError: non_existant_attr
"""
import unittest import unittest
from zope.testing.doctest import DocTestSuite from zope.testing.doctest import DocTestSuite
......
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