Skip to content

Commit

Permalink
Make subclasses of ProxyBase delegate __module__ to the wrapped objec…
Browse files Browse the repository at this point in the history
…t. Also fix differences between the C and Python implementations in handling __module__, whether or not a subclass is involved.
  • Loading branch information
jamadden committed May 28, 2015
1 parent 476893f commit 4f3a07f
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 6 deletions.
3 changes: 2 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
------------------
Expand Down
8 changes: 5 additions & 3 deletions src/zope/proxy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,11 @@ def __getattribute__(self, name):
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
Expand Down
4 changes: 3 additions & 1 deletion src/zope/proxy/_zope_proxy_proxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
88 changes: 87 additions & 1 deletion src/zope/proxy/tests/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,13 +729,95 @@ def test_string_to_int(self):
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):
Expand Down Expand Up @@ -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),
))

0 comments on commit 4f3a07f

Please sign in to comment.