[Checkins] SVN: Acquisition/branches/erp5-aq_dynamic/ Sync to Acquisition trunk: __getnewargs__ fix and ZF license compliance

Leonardo Rochael Almeida leorochael at gmail.com
Thu Apr 8 00:16:33 EDT 2010


Log message for revision 110629:
  Sync to Acquisition trunk: __getnewargs__ fix and ZF license compliance

Changed:
  U   Acquisition/branches/erp5-aq_dynamic/CHANGES.txt
  A   Acquisition/branches/erp5-aq_dynamic/COPYRIGHT.txt
  A   Acquisition/branches/erp5-aq_dynamic/LICENSE.txt
  U   Acquisition/branches/erp5-aq_dynamic/setup.py
  U   Acquisition/branches/erp5-aq_dynamic/src/Acquisition/Acquisition.h
  U   Acquisition/branches/erp5-aq_dynamic/src/Acquisition/_Acquisition.c
  U   Acquisition/branches/erp5-aq_dynamic/src/Acquisition/interfaces.py
  U   Acquisition/branches/erp5-aq_dynamic/src/Acquisition/tests.py

-=-
Modified: Acquisition/branches/erp5-aq_dynamic/CHANGES.txt
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/CHANGES.txt	2010-04-08 04:08:50 UTC (rev 110628)
+++ Acquisition/branches/erp5-aq_dynamic/CHANGES.txt	2010-04-08 04:16:33 UTC (rev 110629)
@@ -1,10 +1,25 @@
 Changelog
 =========
 
-2.13.2 (unreleased)
+2.13.3 (unreleased)
 -------------------
 
 
+2.13.2 (2010-04-04)
+-------------------
+
+- Give both wrapper classes a ``__getnewargs__`` method, which causes the ZODB
+  optimization to fail and create persistent references using the ``_p_oid``
+  alone. This happens to be the persistent oid of the wrapped object. This lets
+  these objects to be persisted correctly, even though they are passed to the
+  ZODB in a wrapped state.
+
+- Added failing tests for http://dev.plone.org/plone/ticket/10318. This shows
+  an edge-case where AQ wrappers can be pickled using the specific combination
+  of cPickle, pickle protocol one and a custom Pickler class with an
+  ``inst_persistent_id`` hook. Unfortunately this is the exact combination used
+  by ZODB3.
+
 2.13.1 (2010-02-23)
 -------------------
 

Copied: Acquisition/branches/erp5-aq_dynamic/COPYRIGHT.txt (from rev 110628, Acquisition/branches/erp5-aq_dynamic-mergepoint4-fromtrunk/COPYRIGHT.txt)
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/COPYRIGHT.txt	                        (rev 0)
+++ Acquisition/branches/erp5-aq_dynamic/COPYRIGHT.txt	2010-04-08 04:16:33 UTC (rev 110629)
@@ -0,0 +1 @@
+Zope Foundation and Contributors
\ No newline at end of file

Copied: Acquisition/branches/erp5-aq_dynamic/LICENSE.txt (from rev 110628, Acquisition/branches/erp5-aq_dynamic-mergepoint4-fromtrunk/LICENSE.txt)
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/LICENSE.txt	                        (rev 0)
+++ Acquisition/branches/erp5-aq_dynamic/LICENSE.txt	2010-04-08 04:16:33 UTC (rev 110629)
@@ -0,0 +1,44 @@
+Zope Public License (ZPL) Version 2.1
+
+A copyright notice accompanies this license document that identifies the
+copyright holders.
+
+This license has been certified as open source. It has also been designated as
+GPL compatible by the Free Software Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions in source code must retain the accompanying copyright
+notice, this list of conditions, and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the accompanying copyright
+notice, this list of conditions, and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+3. Names of the copyright holders must not be used to endorse or promote
+products derived from this software without prior written permission from the
+copyright holders.
+
+4. The right to distribute this software or to use it for any purpose does not
+give you the right to use Servicemarks (sm) or Trademarks (tm) of the
+copyright
+holders. Use of them is covered by separate agreement with the copyright
+holders.
+
+5. If any files are modified, you must cause the modified files to carry
+prominent notices stating that you changed the files and the date of any
+change.
+
+Disclaimer
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Modified: Acquisition/branches/erp5-aq_dynamic/setup.py
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/setup.py	2010-04-08 04:08:50 UTC (rev 110628)
+++ Acquisition/branches/erp5-aq_dynamic/setup.py	2010-04-08 04:16:33 UTC (rev 110629)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2007 Zope Corporation and Contributors.
+# Copyright (c) 2007 Zope Foundation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
@@ -17,12 +17,12 @@
 from setuptools import setup, find_packages, Extension
 
 setup(name='Acquisition',
-      version = '2.13.2dev',
+      version = '2.13.3dev',
       url='http://pypi.python.org/pypi/Acquisition',
       license='ZPL 2.1',
       description="Acquisition is a mechanism that allows objects to obtain "
       "attributes from the containment hierarchy they're in.",
-      author='Zope Corporation and Contributors',
+      author='Zope Foundation and Contributors',
       author_email='zope-dev at zope.org',
       long_description=open(
           os.path.join('src', 'Acquisition', 'README.txt')).read() + '\n' +

Modified: Acquisition/branches/erp5-aq_dynamic/src/Acquisition/Acquisition.h
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/src/Acquisition/Acquisition.h	2010-04-08 04:08:50 UTC (rev 110628)
+++ Acquisition/branches/erp5-aq_dynamic/src/Acquisition/Acquisition.h	2010-04-08 04:16:33 UTC (rev 110629)
@@ -1,6 +1,6 @@
 /*****************************************************************************
 
-  Copyright (c) 1996-2002 Zope Corporation and Contributors.
+  Copyright (c) 1996-2002 Zope Foundation and Contributors.
   All Rights Reserved.
 
   This software is subject to the provisions of the Zope Public License,

Modified: Acquisition/branches/erp5-aq_dynamic/src/Acquisition/_Acquisition.c
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/src/Acquisition/_Acquisition.c	2010-04-08 04:08:50 UTC (rev 110628)
+++ Acquisition/branches/erp5-aq_dynamic/src/Acquisition/_Acquisition.c	2010-04-08 04:16:33 UTC (rev 110629)
@@ -1,6 +1,6 @@
 /*****************************************************************************
 
-  Copyright (c) 1996-2003 Zope Corporation and Contributors.
+  Copyright (c) 1996-2003 Zope Foundation and Contributors.
   All Rights Reserved.
 
   This software is subject to the provisions of the Zope Public License,
@@ -1348,6 +1348,12 @@
   return NULL;
 }
 
+static PyObject *
+Wrapper___getnewargs__(PyObject *self)
+{
+  return PyTuple_New(0);
+}
+
 static struct PyMethodDef Wrapper_methods[] = {
   {"acquire", (PyCFunction)Wrapper_acquire_method, 
    METH_VARARGS|METH_KEYWORDS,
@@ -1357,6 +1363,8 @@
    "Get an attribute, acquiring it if necessary"},
   {"aq_inContextOf", (PyCFunction)Wrapper_inContextOf, METH_VARARGS,
    "Test whether the object is currently in the context of the argument"},
+  {"__getnewargs__", (PyCFunction)Wrapper___getnewargs__, METH_NOARGS,
+    "Get arguments to be passed to __new__"},
   {"__getstate__", (PyCFunction)Wrappers_are_not_picklable, METH_VARARGS,
    "Wrappers are not picklable"},
   {"__reduce__", (PyCFunction)Wrappers_are_not_picklable, METH_VARARGS,

Modified: Acquisition/branches/erp5-aq_dynamic/src/Acquisition/interfaces.py
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/src/Acquisition/interfaces.py	2010-04-08 04:08:50 UTC (rev 110628)
+++ Acquisition/branches/erp5-aq_dynamic/src/Acquisition/interfaces.py	2010-04-08 04:16:33 UTC (rev 110629)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
+# Copyright (c) 2005 Zope Foundation and Contributors.
 #
 # 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.

Modified: Acquisition/branches/erp5-aq_dynamic/src/Acquisition/tests.py
===================================================================
--- Acquisition/branches/erp5-aq_dynamic/src/Acquisition/tests.py	2010-04-08 04:08:50 UTC (rev 110628)
+++ Acquisition/branches/erp5-aq_dynamic/src/Acquisition/tests.py	2010-04-08 04:16:33 UTC (rev 110629)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2003 Zope Corporation and Contributors.
+# Copyright (c) 2003 Zope Foundation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
@@ -70,9 +70,9 @@
     cannot be found in 'a'.
 
     Aquisition wrappers provide access to the wrapped objects
-    through the attributes 'aq_parent', 'aq_self', 'aq_base'.  
+    through the attributes 'aq_parent', 'aq_self', 'aq_base'.
     In the example above, the expressions::
-      
+
        >>> c.a.aq_parent is c
        1
 
@@ -97,28 +97,28 @@
 
     Two styles of acquisition are supported in the current
     ExtensionClass release, implicit and explicit aquisition.
-  
+
     Implicit acquisition
-    
+
       Implicit acquisition is so named because it searches for
       attributes from the environment automatically whenever an
       attribute cannot be obtained directly from an object or
       through inheritence.
-  
+
       An attribute may be implicitly acquired if it's name does
       not begin with an underscore, '_'.
-  
+
       To support implicit acquisition, an object should inherit
       from the mix-in class 'Acquisition.Implicit'.
-  
+
     Explicit Acquisition
-  
+
       When explicit acquisition is used, attributes are not
       automatically obtained from the environment.  Instead, the
       method 'aq_aquire' must be used, as in::
-  
+
         print c.a.aq_acquire('color')
-  
+
       To support explicit acquisition, an object should inherit
       from the mix-in class 'Acquisition.Explicit'.
 
@@ -206,31 +206,31 @@
       For example, in::
 
         >>> from Acquisition import Explicit
-        
+
         >>> class HandyForTesting:
         ...     def __init__(self, name): self.name=name
         ...     def __str__(self):
         ...       return "%s(%s)" % (self.name, self.__class__.__name__)
         ...     __repr__=__str__
-        
+
         >>> class E(Explicit, HandyForTesting):
         ...     pass
-        
+
         >>> class Nice(HandyForTesting):
         ...     isNice=1
         ...     def __str__(self):
         ...        return HandyForTesting.__str__(self)+' and I am nice!'
         ...     __repr__=__str__
-        
+
         >>> a = E('a')
         >>> a.b = E('b')
         >>> a.b.c = E('c')
         >>> a.p = Nice('spam')
         >>> a.b.p = E('p')
-        
+
         >>> def find_nice(self, ancestor, name, object, extra):
         ...     return hasattr(object,'isNice') and object.isNice
-        
+
         >>> print a.b.c.aq_acquire('p', find_nice)
         spam(Nice) and I am nice!
 
@@ -258,13 +258,13 @@
     Consider the following example::
 
       >>> from Acquisition import Implicit
-      
+
       >>> class C(Implicit):
       ...     def __init__(self, name): self.name=name
       ...     def __str__(self):
       ...         return "%s(%s)" % (self.name, self.__class__.__name__)
       ...     __repr__=__str__
-      
+
       >>> a = C("a")
       >>> a.b = C("b")
       >>> a.b.pref = "spam"
@@ -323,9 +323,9 @@
     'a' is searched no more than once, even though it is wrapped three
     times.
 
-.. [1] Gil, J., Lorenz, D., 
+.. [1] Gil, J., Lorenz, D.,
    "Environmental Acquisition--A New Inheritance-Like Abstraction Mechanism",
-   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
 
 $Id$
@@ -368,7 +368,7 @@
     Traceback (most recent call last):
     ...
     AttributeError: x
-    
+
     >>> Acquisition.aq_acquire(c, 'id',
     ...  lambda searched, parent, name, ob, extra: extra)
     Traceback (most recent call last):
@@ -409,8 +409,8 @@
     >>> Acquisition.aq_self(c) is c
     1
 
-    
-    
+
+
     """
 
 def test_simple():
@@ -475,7 +475,7 @@
     Traceback (most recent call last):
     ...
     AttributeError: x
-    
+
     >>> a.b.c.aq_acquire('id',
     ...  lambda searched, parent, name, ob, extra: extra)
     Traceback (most recent call last):
@@ -497,7 +497,7 @@
 
     >>> Acquisition.aq_acquire(a.b.c, 'y')
     42
-    
+
     >>> Acquisition.aq_acquire(a.b.c, 'id',
     ...  lambda searched, parent, name, ob, extra: extra)
     Traceback (most recent call last):
@@ -685,19 +685,19 @@
 
     >>> show(a.a1.a11.a2.a21.aq_inner.aq_parent.aq_inner.aq_parent)
     a
-    
+
     >>> a.a1.a11.a2.a21.aq_chain
     [a21, a2, a11, a1, a]
-    
+
     >>> a.a1.a11.a2.a21.aq_inContextOf(a)
     1
-    
+
     >>> a.a1.a11.a2.a21.aq_inContextOf(a.a2)
     1
 
     >>> a.a1.a11.a2.a21.aq_inContextOf(a.a1)
     0
-    
+
     >>> a.a1.a11.a2.a21.aq_acquire('color')
     'red'
     >>> a.a1.a11.a2.a21.aq_acquire('id')
@@ -712,7 +712,7 @@
     >>> a.a1.a11.a2.a21.aq_acquire('color',
     ...     lambda ob, parent, name, v, extra: extra, 1)
     'red'
-    
+
     >>> a.a1.y = 42
     >>> a.a1.a11.a2.a21.aq_acquire('y')
     42
@@ -796,13 +796,13 @@
     >>> show(Acquisition.aq_parent(
     ...       a.a1.a11.a2.a21.aq_inner.aq_parent.aq_inner))
     a
-    
+
     >>> Acquisition.aq_chain(a.a1.a11.a2.a21)
     [a21, a2, a11, a1, a]
-    
+
     >>> Acquisition.aq_chain(a.a1.a11.a2.a21, 1)
     [a21, a2, a]
-    
+
     >>> Acquisition.aq_acquire(a.a1.a11.a2.a21, 'color')
     'red'
     >>> Acquisition.aq_acquire(a.a1.a11.a2.a21, 'id')
@@ -817,7 +817,7 @@
     >>> Acquisition.aq_acquire(a.a1.a11.a2.a21, 'color',
     ...     lambda ob, parent, name, v, extra: extra, 1)
     'red'
-    
+
     >>> a.a1.y = 42
     >>> Acquisition.aq_acquire(a.a1.a11.a2.a21, 'y')
     42
@@ -829,7 +829,7 @@
 
 
     """
-    
+
 def test_pinball():
     r"""
     >>> a = I('a')
@@ -943,7 +943,7 @@
     a1
     |
     a
-    
+
     """
 
 def test_explicit():
@@ -1003,7 +1003,7 @@
     Traceback (most recent call last):
     ...
     AttributeError: x
-    
+
     >>> a.b.c.aq_acquire('id',
     ...  lambda searched, parent, name, ob, extra: extra)
     Traceback (most recent call last):
@@ -1025,7 +1025,7 @@
 
     >>> Acquisition.aq_acquire(a.b.c, 'y')
     42
-    
+
     >>> Acquisition.aq_acquire(a.b.c, 'id',
     ...  lambda searched, parent, name, ob, extra: extra)
     Traceback (most recent call last):
@@ -1143,7 +1143,7 @@
     Traceback (most recent call last):
     ...
     AttributeError: x
-    
+
     >>> a.b.c.aq_acquire('id',
     ...  lambda searched, parent, name, ob, extra: extra)
     Traceback (most recent call last):
@@ -1165,7 +1165,7 @@
 
     >>> Acquisition.aq_acquire(a.b.c, 'y')
     42
-    
+
     >>> Acquisition.aq_acquire(a.b.c, 'id',
     ...  lambda searched, parent, name, ob, extra: extra)
     Traceback (most recent call last):
@@ -1221,7 +1221,7 @@
 
 
 def test_aq_inContextOf():
-    """    
+    """
     >>> from ExtensionClass import Base
     >>> import Acquisition
 
@@ -1247,9 +1247,9 @@
     ...     raise 'Program error', 'spam'
     ... except AttributeError: pass
     A()
-    
+
        New test for wrapper comparisons.
-    
+
     >>> foo = b.a
     >>> bar = b.a
     >>> foo == bar
@@ -1379,14 +1379,14 @@
     [D, C, A]
     >>> map(Acquisition.aq_base, Acquisition.aq_chain(A.B.C.D, 1))
     [D, C, A]
-    
 
+
     >>> A.B.C.D.color
     'red'
     >>> Acquisition.aq_get(A.B.C.D, "color", None)
     'red'
     >>> Acquisition.aq_get(A.B.C.D, "color", None, 1)
-    
+
     """
 
 def test_explicit_acquisition():
@@ -1414,7 +1414,7 @@
     ...     raise 'Program error', 'spam'
     ... except AttributeError: pass
     A
-    
+
     """
 
 def test_creating_wrappers_directly():
@@ -1565,8 +1565,126 @@
     TypeError: Can't pickle objects in acquisition wrappers.
     """
 
-def test_z3interfaces():
+def test_cant_persist_acquisition_wrappers_classic():
     """
+    >>> import cPickle
+
+    >>> class X:
+    ...     _p_oid = '1234'
+    ...     def __getstate__(self):
+    ...         return 1
+
+    We shouldn't be able to pickle wrappers:
+
+    >>> from Acquisition import ImplicitAcquisitionWrapper
+    >>> w = ImplicitAcquisitionWrapper(X(), X())
+    >>> cPickle.dumps(w)
+    Traceback (most recent call last):
+    ...
+    TypeError: Can't pickle objects in acquisition wrappers.
+
+    Check for pickle protocol one:
+
+    >>> cPickle.dumps(w, 1)
+    Traceback (most recent call last):
+    ...
+    TypeError: Can't pickle objects in acquisition wrappers.
+
+    Check custom pickler:
+
+    >>> from cStringIO import StringIO
+    >>> file = StringIO()
+    >>> pickler = cPickle.Pickler(file, 1)
+
+    >>> pickler.dump(w)
+    Traceback (most recent call last):
+    ...
+    TypeError: Can't pickle objects in acquisition wrappers.
+
+    Check custom pickler with a persistent_id method matching the semantics
+    in ZODB.serialize.ObjectWriter.persistent_id:
+
+    >>> file = StringIO()
+    >>> pickler = cPickle.Pickler(file, 1)
+
+    >>> def persistent_id(obj):
+    ...     klass = type(obj)
+    ...     oid = obj._p_oid
+    ...     if hasattr(klass, '__getnewargs__'):
+    ...         return oid
+    ...     return 'class_and_oid', klass
+
+    >>> pickler.inst_persistent_id = persistent_id
+    >>> _ = pickler.dump(w)
+    >>> state = file.getvalue()
+    >>> '1234' in state
+    True
+    >>> 'class_and_oid' in state
+    False
+    """
+
+
+def test_cant_persist_acquisition_wrappers_newstyle():
+    """
+    >>> import cPickle
+
+    >>> class X(object):
+    ...     _p_oid = '1234'
+    ...     def __getstate__(self):
+    ...         return 1
+
+    We shouldn't be able to pickle wrappers:
+
+    >>> from Acquisition import ImplicitAcquisitionWrapper
+    >>> w = ImplicitAcquisitionWrapper(X(), X())
+    >>> cPickle.dumps(w)
+    Traceback (most recent call last):
+    ...
+    TypeError: Can't pickle objects in acquisition wrappers.
+
+    Check for pickle protocol one:
+
+    >>> cPickle.dumps(w, 1)
+    Traceback (most recent call last):
+    ...
+    TypeError: Can't pickle objects in acquisition wrappers.
+
+    Check custom pickler:
+
+    >>> from cStringIO import StringIO
+    >>> file = StringIO()
+    >>> pickler = cPickle.Pickler(file, 1)
+
+    >>> pickler.dump(w)
+    Traceback (most recent call last):
+    ...
+    TypeError: Can't pickle objects in acquisition wrappers.
+
+    Check custom pickler with a persistent_id method matching the semantics
+    in ZODB.serialize.ObjectWriter.persistent_id:
+
+    >>> file = StringIO()
+    >>> pickler = cPickle.Pickler(file, 1)
+
+    >>> def persistent_id(obj):
+    ...     klass = type(obj)
+    ...     oid = obj._p_oid
+    ...     if hasattr(klass, '__getnewargs__'):
+    ...         return oid
+    ...     return 'class_and_oid', klass
+
+    >>> pickler.inst_persistent_id = persistent_id
+    >>> _ = pickler.dump(w)
+    >>> state = file.getvalue()
+    >>> '1234' in state
+    True
+    >>> 'class_and_oid' in state
+    False
+    """
+
+
+def test_interfaces():
+    """
     >>> from zope.interface.verify import verifyClass
 
     Explicit and Implicit implement IAcquirer:
@@ -1593,7 +1711,7 @@
 
 def show(x):
     print showaq(x).strip()
-    
+
 def showaq(m_self, indent=''):
     rval = ''
     obj = m_self
@@ -1631,11 +1749,11 @@
     >>> for B in I, E:
     ...     class C1(B):
     ...         pass
-    ... 
+    ...
     ...     class C2(Base):
     ...         def __del__(self):
     ...             print 'removed'
-    ... 
+    ...
     ...     a=C1('a')
     ...     a.b = C1('a.b')
     ...     a.b.a = a
@@ -1664,7 +1782,7 @@
     ...     class C:
     ...         def __del__(self):
     ...             print 'removed'
-    ... 
+    ...
     ...     a=B('a')
     ...     a.b = B('b')
     ...     a.a_b = a.b # circ ref through wrapper
@@ -1891,7 +2009,7 @@
 
 class Location(object):
     __parent__ = None
- 
+
 class ECLocation(ExtensionClass.Base):
     __parent__ = None
 



More information about the checkins mailing list