diff options
author | Jason Madden <jamadden@gmail.com> | 2015-05-28 07:54:29 -0500 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2015-05-28 07:54:29 -0500 |
commit | 4f3a07f7734fd514463af093049f645feb4ba90e (patch) | |
tree | 2c68747b57c8221c8cd9f073d4be776a36340793 | |
parent | 476893f7bf33a918405158ab72b111e304f7b403 (diff) | |
download | zope-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.rst | 3 | ||||
-rw-r--r-- | src/zope/proxy/__init__.py | 8 | ||||
-rw-r--r-- | src/zope/proxy/_zope_proxy_proxy.c | 4 | ||||
-rw-r--r-- | src/zope/proxy/tests/test_proxy.py | 88 |
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), )) |