Commit fd0e8f72 authored by Jason Madden's avatar Jason Madden

Handle descriptors defined in PyProxyBase subclasses the same way the C...

Handle descriptors defined in PyProxyBase subclasses the same way the C version does. This fixes #5.
parent 65ebb9d1
......@@ -4,7 +4,9 @@ Changes
4.1.5 (unreleased)
------------------
- TBD
- The pure Python implementation handles descriptors defined in
subclasses like the C version. See
https://github.com/zopefoundation/zope.proxy/issues/5.
4.1.4 (2014-03-19)
------------------
......
......@@ -14,6 +14,7 @@
"""More convenience functions for dealing with proxies.
"""
import operator
import os
import pickle
import sys
......@@ -33,6 +34,22 @@ def ProxyIterator(p):
_MARKER = object()
def _WrapperType_Lookup(type_, name):
"""
Looks up information in class dictionaries in MRO
order, ignoring the proxy type itself.
Returns the first found object, or _MARKER
"""
for base in type_.mro():
if base is PyProxyBase:
continue
res = base.__dict__.get(name, _MARKER)
if res is not _MARKER:
return res
return _MARKER
class PyProxyBase(object):
"""Reference implementation.
"""
......@@ -95,19 +112,30 @@ class PyProxyBase(object):
wrapped = super(PyProxyBase, self).__getattribute__('_wrapped')
if name == '_wrapped':
return wrapped
try:
mine = super(PyProxyBase, self).__getattribute__(name)
except AttributeError:
mine = _MARKER
else:
if isinstance(mine, PyNonOverridable): #pragma NO COVER PyPy
return mine.desc.__get__(self)
try:
if name == '__class__':
# __class__ is special cased in the C implementation
return wrapped.__class__
# First, look for descriptors in this object's type
type_self = type(self)
descriptor = _WrapperType_Lookup(type_self, name)
if descriptor is _MARKER:
# Nothing in the class, go straight to the wrapped object
return getattr(wrapped, name)
except AttributeError:
if mine is not _MARKER:
return mine
raise
if hasattr(descriptor, '__get__'):
if not hasattr(descriptor, '__set__'):
# Non-data-descriptor: call through to the wrapped object
# to see if it's there
try:
return getattr(wrapped, name)
except AttributeError:
raise
# Data-descriptor on this type. Call it
return descriptor.__get__(self, type_self)
return descriptor
def __getattr__(self, name):
return getattr(self._wrapped, name)
......@@ -115,12 +143,16 @@ class PyProxyBase(object):
def __setattr__(self, name, value):
if name == '_wrapped':
return super(PyProxyBase, self).__setattr__(name, value)
try:
mine = super(PyProxyBase, self).__getattribute__(name)
except AttributeError:
# First, look for descriptors in this object's type
type_self = type(self)
descriptor = _WrapperType_Lookup(type_self, name)
if descriptor is _MARKER or not hasattr(descriptor, '__set__'):
# Nothing in the class that's a descriptor,
# go straight to the wrapped object
return setattr(self._wrapped, name, value)
else:
return object.__setattr__(self, name, value)
return object.__setattr__(self, name, value)
def __delattr__(self, name):
if name == '_wrapped':
......@@ -417,11 +449,14 @@ def py_removeAllProxies(obj):
obj = obj._wrapped
return obj
class PyNonOverridable(object):
def __init__(self, method_desc): #pragma NO COVER PyPy
self.desc = method_desc
_c_available = False
if 'PURE_PYTHON' not in os.environ:
try:
from zope.proxy._zope_proxy_proxy import ProxyBase as _c_available
except ImportError: #pragma NO COVER
pass
try:
if _c_available:
# Python API: not used in this module
from zope.proxy._zope_proxy_proxy import ProxyBase
from zope.proxy._zope_proxy_proxy import getProxiedObject
......@@ -434,7 +469,8 @@ try:
# API for proxy-using C extensions.
from zope.proxy._zope_proxy_proxy import _CAPI
except ImportError: #pragma NO COVER
else: #pragma NO COVER
# no C extension available, fall back
ProxyBase = PyProxyBase
getProxiedObject = py_getProxiedObject
......@@ -444,7 +480,6 @@ except ImportError: #pragma NO COVER
queryProxy = py_queryProxy
queryInnerProxy = py_queryInnerProxy
removeAllProxies = py_removeAllProxies
non_overridable = PyNonOverridable
else:
def non_overridable(func):
return property(lambda self: func.__get__(self))
def non_overridable(func):
return property(lambda self: func.__get__(self))
......@@ -135,6 +135,30 @@ class SpecificationDecoratorBaseTests(unittest.TestCase):
proxy = self._makeOne(foo)
self.assertEqual(list(providedBy(proxy)), list(providedBy(foo)))
def test_proxy_that_provides_interface_as_well_as_wrapped(self):
# If both the wrapper and the wrapped object provide
# interfaces, the wrapper provides the sum
from zope.interface import Interface
from zope.interface import implementer
from zope.interface import providedBy
class IFoo(Interface):
pass
@implementer(IFoo)
class Foo(object):
from_foo = 1
class IWrapper(Interface):
pass
@implementer(IWrapper)
class Proxy(self._getTargetClass()):
pass
foo = Foo()
proxy = Proxy(foo)
self.assertEqual(proxy.from_foo, 1)
self.assertEqual(list(providedBy(proxy)), [IFoo,IWrapper])
def test_suite():
return unittest.TestSuite((
......
......@@ -418,7 +418,7 @@ class PyProxyBaseTestCase(unittest.TestCase):
"complex(x)",
]
if not PY3: # long is gone in Python 3
ops.append("long(x)")
ops.append("long(x)")
return ops
def test_unops(self):
......@@ -581,6 +581,78 @@ class PyProxyBaseTestCase(unittest.TestCase):
w = self._makeOne(o)
self.assertTrue(w.__class__ is o.__class__)
def test_descriptor__set___only_in_proxy_subclass(self):
class Descriptor(object):
value = None
instance = None
def __set__(self, instance, value):
self.value = value
self.instance = instance
descriptor = Descriptor()
class Proxy(self._getTargetClass()):
attr = descriptor
proxy = Proxy(object())
proxy.attr = 42
self.assertEqual(proxy.attr, descriptor)
self.assertEqual(descriptor.value, 42)
self.assertEqual(descriptor.instance, proxy)
def test_descriptor__get___set___in_proxy_subclass(self):
class Descriptor(object):
value = None
instance = None
cls = None
def __get__(self, instance, cls):
self.cls = cls
return self.value
def __set__(self, instance, value):
self.value = value
self.instance = instance
descriptor = Descriptor()
descriptor.value = "descriptor value"
class Proxy(self._getTargetClass()):
attr = descriptor
proxy = Proxy(object())
self.assertEqual(proxy.attr, "descriptor value")
self.assertEqual(descriptor.cls, Proxy)
proxy.attr = 42
self.assertEqual(descriptor.value, 42)
self.assertEqual(descriptor.instance, proxy)
def test_non_descriptor_in_proxy_subclass__dict__(self):
# Non-descriptors in the class dict of the subclass
# are always passed through to the wrapped instance
class Proxy(self._getTargetClass()):
attr = "constant value"
proxy = Proxy(object())
self.assertEqual(proxy.attr, "constant value")
self.assertRaises(AttributeError, setattr, proxy, 'attr', 42)
self.assertEqual(proxy.attr, "constant value")
def test_string_to_int(self):
# XXX Implementation difference: This works in the
# Pure-Python version, but fails in CPython.
# See https://github.com/zopefoundation/zope.proxy/issues/4
proxy = self._makeOne("14")
try:
self.assertEqual(14, int(proxy))
except TypeError:
from zope.proxy import PyProxyBase
self.assertNotEqual(self._getTargetClass, PyProxyBase)
class ProxyBaseTestCase(PyProxyBaseTestCase):
......@@ -588,7 +660,6 @@ class ProxyBaseTestCase(PyProxyBaseTestCase):
from zope.proxy import ProxyBase
return ProxyBase
class Test_py_getProxiedObject(unittest.TestCase):
def _callFUT(self, *args):
......@@ -620,7 +691,6 @@ class Test_py_getProxiedObject(unittest.TestCase):
proxy2 = self._makeProxy(proxy)
self.assertTrue(self._callFUT(proxy2) is proxy)
class Test_getProxiedObject(Test_py_getProxiedObject):
def _callFUT(self, *args):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment