[Checkins] SVN: Acquisition/branches/erp5-aq_dynamic/src/Acquisition/ Add _aq_dynamic functionality to Acquisition in the erp5-aq_dynamic branch

Leonardo Rochael Almeida leorochael at gmail.com
Wed Sep 23 12:03:10 EDT 2009


Log message for revision 104452:
  Add _aq_dynamic functionality to Acquisition in the erp5-aq_dynamic branch

Changed:
  U   Acquisition/branches/erp5-aq_dynamic/src/Acquisition/_Acquisition.c
  A   Acquisition/branches/erp5-aq_dynamic/src/Acquisition/test_dynamic_acquisition.py
  U   Acquisition/branches/erp5-aq_dynamic/src/Acquisition/tests.py

-=-
Modified: Acquisition/branches/erp5-aq_dynamic/src/Acquisition/_Acquisition.c
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/src/Acquisition/_Acquisition.c	2009-09-23 15:54:42 UTC (rev 104451)
+++ Acquisition/branches/erp5-aq_dynamic/src/Acquisition/_Acquisition.c	2009-09-23 16:03:09 UTC (rev 104452)
@@ -447,6 +447,64 @@
 }
 
 static PyObject *
+Wrapper_GetAttr(PyObject *self, PyObject *attr_name, PyObject *orig)
+{
+  /* This function retrieves an attribute from an object by PyObject_GetAttr.
+
+     The main difference between Wrapper_GetAttr and PyObject_GetAttr is that
+     Wrapper_GetAttr calls _aq_dynamic to generate an attribute dynamically, if
+     the attribute is not found.
+  */
+  PyObject *r, *v, *tb;
+  PyObject *d, *m;
+  PyObject *o;
+
+  if (isWrapper (self))
+    o = WRAPPER(self)->obj;
+  else
+    o = self;
+  
+  /* Try to get an attribute in the normal way first.  */
+  r = PyObject_GetAttr(o, attr_name);
+  if (r)
+    return r;
+
+  /* If an unexpected error happens, return immediately.  */
+  PyErr_Fetch(&r,&v,&tb);
+  if (r != PyExc_AttributeError)
+    {
+      PyErr_Restore(r,v,tb);
+      return NULL;
+    }
+
+  /* Try to get _aq_dynamic.  */
+  m = PyObject_GetAttrString(o, "_aq_dynamic");
+  if (! m) {
+    PyErr_Restore(r,v,tb);
+    return NULL;
+  }
+
+  /* Call _aq_dynamic in the context of the original acquisition wrapper.  */
+  if (PyECMethod_Check(m) && PyECMethod_Self(m)==o)
+    ASSIGN(m,PyECMethod_New(m,OBJECT(self)));
+  else if (has__of__(m)) ASSIGN(m,__of__(m,OBJECT(self)));
+  d = PyObject_CallFunction(m, "O", attr_name);
+  Py_DECREF(m);
+
+  /* In the case of None, assume that the attribute is not found.  */
+  if (d == Py_None) {
+    Py_DECREF(d);
+    PyErr_Restore(r,v,tb);
+    return NULL;
+  }
+
+  Py_XDECREF(r);
+  Py_XDECREF(v);
+  Py_XDECREF(tb);
+  return d;
+}
+
+static PyObject *
 Wrapper_acquire(Wrapper *self, PyObject *oname, 
 		PyObject *filter, PyObject *extra, PyObject *orig,
 		int explicit, int containment);
@@ -544,8 +602,8 @@
 	  Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb);
 	  r=NULL;
 	}
-	  /* normal attribute lookup */
-      else if ((r=PyObject_GetAttr(self->obj,oname)))
+      /* Give _aq_dynamic a chance, then normal attribute lookup */
+      else if ((r=Wrapper_GetAttr(OBJECT(self),oname,orig)))
 	{
 	  if (r==Acquired)
 	    {
@@ -669,7 +727,7 @@
           Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb);
           r=NULL;
 
-	  if ((r=PyObject_GetAttr(self->container,oname))) {
+	  if ((r=Wrapper_GetAttr(self->container,oname,orig))) {
 	    if (r == Acquired) {
 	      Py_DECREF(r);
 	    }
@@ -706,7 +764,7 @@
 Wrapper_getattro(Wrapper *self, PyObject *oname)
 {
   if (self->obj || self->container)
-    return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 1, 0, 0);
+    return Wrapper_findattr(self, oname, NULL, NULL, OBJECT(self), 1, 1, 0, 0);
 
   /* Maybe we are getting initialized? */
   return Py_FindAttr(OBJECT(self),oname);
@@ -723,7 +781,7 @@
     return Py_FindAttr(OBJECT(self),oname);
 
   if (self->obj || self->container)
-    return Wrapper_findattr(self, oname, NULL, NULL, NULL, 1, 0, 0, 0);
+    return Wrapper_findattr(self, oname, NULL, NULL, OBJECT(self), 1, 0, 0, 0);
 
   /* Maybe we are getting initialized? */
   return Py_FindAttr(OBJECT(self),oname);

Added: Acquisition/branches/erp5-aq_dynamic/src/Acquisition/test_dynamic_acquisition.py
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/src/Acquisition/test_dynamic_acquisition.py	                        (rev 0)
+++ Acquisition/branches/erp5-aq_dynamic/src/Acquisition/test_dynamic_acquisition.py	2009-09-23 16:03:09 UTC (rev 104452)
@@ -0,0 +1,160 @@
+##############################################################################
+#
+# Copyright (c) 1996-2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+#
+##############################################################################
+from ExtensionClass import Base
+import Acquisition
+
+def checkContext(self, o):
+    # Python equivalent to aq_inContextOf
+    from Acquisition import aq_base, aq_parent, aq_inner
+    subob = self
+    o = aq_base(o)
+    while 1:
+        if aq_base(subob) is o:
+            return True
+        self = aq_inner(subob)
+        if self is None: break
+        subob = aq_parent(self)
+        if subob is None: break
+    return False
+
+class B(Acquisition.Implicit):
+    color='red'
+
+    def __init__(self, name='b'):
+        self.name = name
+
+    def _aq_dynamic(self, attr):
+        if attr == 'bonjour': return None
+
+        def dynmethod():
+            chain = ' <- '.join(repr(obj) for obj in Acquisition.aq_chain(self))
+            print repr(self) + '.' + attr
+            print 'chain:', chain
+
+        return dynmethod
+
+    def __repr__(self):
+        return "%s(%r)" % (self.__class__.__name__, self.name)
+
+class A(Acquisition.Implicit):
+
+    def __init__(self, name='a'):
+        self.name = name
+
+    def hi(self):
+        print self, self.color
+
+    def _aq_dynamic(self, attr):
+        return None
+
+    def __repr__(self):
+        return "%s(%r)" % (self.__class__.__name__, self.name)
+
+def test_dynamic():
+    r'''
+    The _aq_dynamic functionality allows an object to dynamically provide an
+    attribute.
+    
+    If an object doesn't have an attribute, Acquisition checks to see if the
+    object has a _aq_dynamic method, which is then called. It is functionally
+    equivalent to __getattr__, but _aq_dynamic is called with 'self' as the
+    acquisition wrapped object where as __getattr__ is called with self as the
+    unwrapped object.
+
+    Let's see how this works. In the examples below, the A class defines
+    '_aq_dynamic', but returns 'None' for all attempts, which means that no new
+    attributes should be generated dynamically. It also doesn't define 'color'
+    attribute, even though it uses it in the 'hi' method.
+
+        >>> A().hi()
+        Traceback (most recent call last):
+        ...
+        AttributeError: color
+    
+    The class B, on the other hand, generates all attributes dynamically,
+    except if it is called 'bonjour'.
+    
+    First we need to check that, even if an object provides '_aq_dynamic',
+    "regular" Aquisition attribute access should still work:
+
+        >>> b=B()
+        >>> b.a=A()
+        >>> b.a.hi()
+        A('a') red
+        >>> b.a.color='green'
+        >>> b.a.hi()
+        A('a') green
+
+    Now, let's see some dynamically generated action. B does not define a
+    'salut' method, but remember that it dynamically generates a method for
+    every attribute access:
+
+        >>> b.a.salut()
+        B('b').salut
+        chain: B('b')
+
+        >>> a=A('a1')
+        >>> a.b=B('b1')
+        >>> a.b.salut()
+        B('b1').salut
+        chain: B('b1') <- A('a1')
+
+        >>> b.a.bonjour()
+        Traceback (most recent call last):
+        ...
+        AttributeError: bonjour
+
+        >>> a.b.bonjour()
+        Traceback (most recent call last):
+        ...
+        AttributeError: bonjour
+
+    '''
+
+def test_wrapper_comparissons():
+    r'''
+
+    Test wrapper comparisons in presence of _aq_dynamic
+
+        >>> b=B()
+        >>> b.a=A()
+        >>> foo = b.a
+        >>> bar = b.a
+        >>> assert( foo == bar )
+        >>> c = A('c')
+        >>> b.c = c
+        >>> b.c.d = c
+        >>> b.c.d == c
+        True
+        >>> b.c.d == b.c
+        True
+        >>> b.c == c
+        True
+
+    Test contextuality in presence of _aq_dynamic
+
+        >>> checkContext(b.c, b)
+        True
+        >>> checkContext(b.c, b.a)
+        False
+
+        >>> assert b.a.aq_inContextOf(b)
+        >>> assert b.c.aq_inContextOf(b)
+        >>> assert b.c.d.aq_inContextOf(b)
+        >>> assert b.c.d.aq_inContextOf(c)
+        >>> assert b.c.d.aq_inContextOf(b.c)
+        >>> assert not b.c.aq_inContextOf(foo)
+        >>> assert not b.c.aq_inContextOf(b.a)
+        >>> assert not b.a.aq_inContextOf('somestring')
+'''

Modified: Acquisition/branches/erp5-aq_dynamic/src/Acquisition/tests.py
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/src/Acquisition/tests.py	2009-09-23 15:54:42 UTC (rev 104451)
+++ Acquisition/branches/erp5-aq_dynamic/src/Acquisition/tests.py	2009-09-23 16:03:09 UTC (rev 104452)
@@ -2270,6 +2270,7 @@
 def test_suite():
     return unittest.TestSuite((
         DocTestSuite(),
+        DocTestSuite('Acquisition.test_dynamic_acquisition'),
         DocFileSuite('README.txt', package='Acquisition'),
         ))
 



More information about the checkins mailing list