summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2015-05-28 07:54:29 -0500
committerJason Madden <jamadden@gmail.com>2015-05-28 07:54:29 -0500
commit4f3a07f7734fd514463af093049f645feb4ba90e (patch)
tree2c68747b57c8221c8cd9f073d4be776a36340793
parent476893f7bf33a918405158ab72b111e304f7b403 (diff)
downloadzope-proxy-4f3a07f7734fd514463af093049f645feb4ba90e.tar.gz
Make subclasses of ProxyBase delegate __module__ to the wrapped object. Also fix differences between the C and Python implementations in handling __module__, whether or not a subclass is involved.
-rw-r--r--CHANGES.rst3
-rw-r--r--src/zope/proxy/__init__.py8
-rw-r--r--src/zope/proxy/_zope_proxy_proxy.c4
-rw-r--r--src/zope/proxy/tests/test_proxy.py88
4 files changed, 97 insertions, 6 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index cf69daa..86bede0 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,7 +4,8 @@ Changes
4.1.6 (unreleased)
------------------
-- TBD
+- Made subclasses of ProxyBase properly delegate ``__module__`` to the
+ wrapped object.
4.1.5 (2015-05-19)
------------------
diff --git a/src/zope/proxy/__init__.py b/src/zope/proxy/__init__.py
index 6616f40..4a38e59 100644
--- a/src/zope/proxy/__init__.py
+++ b/src/zope/proxy/__init__.py
@@ -131,9 +131,11 @@ class AbstractPyProxyBase(object):
if name == '_wrapped':
return _get_wrapped(self)
- if name == '__class__':
- # __class__ is special cased in the C implementation
- return _get_wrapped(self).__class__
+ if name in ('__class__', '__module__'):
+ # __class__ and __module__ are special cased in the C
+ # implementation, because we will always find them on the
+ # type of this object if we are being subclassed
+ return getattr(_get_wrapped(self), name)
if name in ('__reduce__', '__reduce_ex__'):
# These things we specifically override and no one
diff --git a/src/zope/proxy/_zope_proxy_proxy.c b/src/zope/proxy/_zope_proxy_proxy.c
index 0df67b6..ccb563e 100644
--- a/src/zope/proxy/_zope_proxy_proxy.c
+++ b/src/zope/proxy/_zope_proxy_proxy.c
@@ -274,7 +274,9 @@ wrap_getattro(PyObject *self, PyObject *name)
maybe_special_name = name_as_string[0] == '_' && name_as_string[1] == '_';
- if (!(maybe_special_name && strcmp(name_as_string, "__class__") == 0)) {
+ if (!(maybe_special_name
+ && (strcmp(name_as_string, "__class__") == 0
+ || strcmp(name_as_string, "__module__") == 0))) {
descriptor = WrapperType_Lookup(self->ob_type, name);
diff --git a/src/zope/proxy/tests/test_proxy.py b/src/zope/proxy/tests/test_proxy.py
index 5622104..59b0a04 100644
--- a/src/zope/proxy/tests/test_proxy.py
+++ b/src/zope/proxy/tests/test_proxy.py
@@ -729,13 +729,95 @@ class PyProxyBaseTestCase(unittest.TestCase):
from zope.proxy import PyProxyBase
self.assertNotEqual(self._getTargetClass, PyProxyBase)
-
class ProxyBaseTestCase(PyProxyBaseTestCase):
def _getTargetClass(self):
from zope.proxy import ProxyBase
return ProxyBase
+class Test_py__module(unittest.TestCase):
+ # Historically, proxying __module__ has been troublesome,
+ # especially when subclasses of the proxy class are involved;
+ # there was also a discrepancy between the C and Python implementations
+ # in that the C implementation only failed Test_subclass__module:test__module__in_instance,
+ # whereas the Python version failed every test.
+ # See https://github.com/zopefoundation/zopetoolkit/pull/2#issuecomment-106075153
+ # and https://github.com/zopefoundation/zope.proxy/pull/8
+
+ def _getTargetClass(self):
+ from zope.proxy import PyProxyBase
+ return PyProxyBase
+
+ def _makeProxy(self, obj):
+ from zope.proxy import PyProxyBase
+ return self._getTargetClass()(obj)
+
+ def _check_module(self, obj, expected):
+ self.assertEqual(expected, obj.__module__)
+ self.assertEqual(expected, self._makeProxy(obj).__module__)
+
+ def test__module__in_instance(self):
+ # We can find __module__ in an instance dict
+ class Module(object):
+ def __init__(self):
+ self.__module__ = 'module'
+
+ self._check_module(Module(), 'module')
+
+ def test__module__in_class_instance(self):
+ # We can find module in an instance of a class
+ class Module(object):
+ pass
+
+ self._check_module(Module(), __name__)
+
+ def test__module__in_class(self):
+ # We can find module in a class itself
+ class Module(object):
+ pass
+ self._check_module(Module, __name__)
+
+ def test__module_in_eq_transitive(self):
+ # An object that uses __module__ in its implementation
+ # of __eq__ is transitively equal to a proxy of itself.
+ # Seen with zope.interface.interface.Interface
+
+ class Module(object):
+ def __init__(self):
+ self.__module__ = __name__
+ def __eq__(self, other):
+ return self.__module__ == other.__module__
+
+ module = Module()
+ # Sanity checks
+ self.assertEqual(module, module)
+ self.assertEqual(module.__module__, __name__)
+
+ # transitive equal
+ self.assertEqual(module, self._makeProxy(module))
+ self.assertEqual(self._makeProxy(module), module)
+
+class Test__module(Test_py__module):
+
+ def _getTargetClass(self):
+ from zope.proxy import ProxyBase
+ return ProxyBase
+
+class Test_py_subclass__module(Test_py__module):
+
+ def _getTargetClass(self):
+ class ProxySubclass(super(Test_py_subclass__module,self)._getTargetClass()):
+ pass
+ return ProxySubclass
+
+class Test_subclass__module(Test__module):
+
+ def _getTargetClass(self):
+ class ProxySubclass(super(Test_subclass__module,self)._getTargetClass()):
+ pass
+ return ProxySubclass
+
+
class Test_py_getProxiedObject(unittest.TestCase):
def _callFUT(self, *args):
@@ -1270,4 +1352,8 @@ def test_suite():
unittest.makeSuite(Test_removeAllProxies),
unittest.makeSuite(Test_ProxyIterator),
unittest.makeSuite(Test_nonOverridable),
+ unittest.makeSuite(Test_py__module),
+ unittest.makeSuite(Test__module),
+ unittest.makeSuite(Test_py_subclass__module),
+ unittest.makeSuite(Test_subclass__module),
))