Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Z
zope.proxy
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
zope.proxy
Commits
37e9ea6a
Commit
37e9ea6a
authored
May 19, 2015
by
Tres Seaver
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6 from NextThought/python-provided
Handle descriptors defined in PyProxyBase subclasses like C
parents
65ebb9d1
7d0d502e
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
213 additions
and
37 deletions
+213
-37
CHANGES.rst
CHANGES.rst
+3
-1
src/zope/proxy/__init__.py
src/zope/proxy/__init__.py
+100
-32
src/zope/proxy/tests/test_decorator.py
src/zope/proxy/tests/test_decorator.py
+24
-0
src/zope/proxy/tests/test_proxy.py
src/zope/proxy/tests/test_proxy.py
+85
-3
tox.ini
tox.ini
+1
-1
No files found.
CHANGES.rst
View file @
37e9ea6a
...
...
@@ -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)
------------------
...
...
src/zope/proxy/__init__.py
View file @
37e9ea6a
...
...
@@ -14,6 +14,7 @@
"""More convenience functions for dealing with proxies.
"""
import
operator
import
os
import
pickle
import
sys
...
...
@@ -33,13 +34,45 @@ def ProxyIterator(p):
_MARKER
=
object
()
class
PyProxyBase
(
object
):
"""Reference implementation.
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
"""
__slots__
=
(
'_wrapped'
,
)
def
__new__
(
cls
,
value
):
inst
=
super
(
PyProxyBase
,
cls
).
__new__
(
cls
)
for
base
in
type_
.
mro
():
if
base
is
AbstractPyProxyBase
:
continue
res
=
base
.
__dict__
.
get
(
name
,
_MARKER
)
if
res
is
not
_MARKER
:
return
res
return
_MARKER
def
_get_wrapped
(
self
):
"""
Helper method to access the wrapped object.
"""
return
super
(
AbstractPyProxyBase
,
self
).
__getattribute__
(
'_wrapped'
)
class
AbstractPyProxyBase
(
object
):
"""
A reference implementation that cannot be instantiated. Most users
will want to use :class:`PyProxyBase`.
This type is intended to be used in multiple-inheritance
scenarios, where another super class already has defined
``__slots__``. In order to subclass both that class and this
class, you must include the ``_wrapped`` value in your own
``__slots__`` definition (or else you will get the infamous
TypeError: "multiple bases have instance lay-out conflicts")
"""
__slots__
=
()
def
__new__
(
cls
,
value
=
None
):
# Some subclasses (zope.security.proxy) fail to pass the object
inst
=
super
(
AbstractPyProxyBase
,
cls
).
__new__
(
cls
)
inst
.
_wrapped
=
value
return
inst
...
...
@@ -92,34 +125,55 @@ class PyProxyBase(object):
# Attribute protocol
def
__getattribute__
(
self
,
name
):
wrapped
=
super
(
PyProxyBase
,
self
).
__getattribute__
(
'_wrapped'
)
# Try to avoid accessing the _wrapped value until we need to.
# We don't know how subclasses may be storing it
# (e.g., persistent subclasses)
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
)
return
_get_wrapped
(
self
)
if
name
==
'__class__'
:
# __class__ is special cased in the C implementation
return
_get_wrapped
(
self
).
__class__
if
name
in
(
'__reduce__'
,
'__reduce_ex__'
):
# These things we specifically override and no one
# can stop us, not even a subclass
return
object
.
__getattribute__
(
self
,
name
)
# 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
(
_get_wrapped
(
self
),
name
)
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
)
return
getattr
(
_get_wrapped
(
self
)
,
name
)
except
AttributeError
:
if
mine
is
not
_MARKER
:
return
mine
raise
pass
# Data-descriptor on this type. Call it
return
descriptor
.
__get__
(
self
,
type_self
)
return
descriptor
def
__getattr__
(
self
,
name
):
return
getattr
(
self
.
_wrapped
,
name
)
def
__setattr__
(
self
,
name
,
value
):
if
name
==
'_wrapped'
:
return
super
(
PyProxyBase
,
self
).
__setattr__
(
name
,
value
)
try
:
mine
=
super
(
PyProxyBase
,
self
).
__getattribute__
(
name
)
except
AttributeError
:
return
super
(
AbstractPyProxyBase
,
self
).
__setattr__
(
name
,
value
)
# 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
)
def
__delattr__
(
self
,
name
):
...
...
@@ -368,6 +422,12 @@ class PyProxyBase(object):
self
.
_wrapped
=
pow
(
self
.
_wrapped
,
other
,
modulus
)
return
self
class
PyProxyBase
(
AbstractPyProxyBase
):
"""Reference implementation.
"""
__slots__
=
(
'_wrapped'
,
)
def
py_getProxiedObject
(
obj
):
if
isinstance
(
obj
,
PyProxyBase
):
return
obj
.
_wrapped
...
...
@@ -417,11 +477,19 @@ def py_removeAllProxies(obj):
obj
=
obj
.
_wrapped
return
obj
_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
class
PyNonOverridable
(
object
):
"Deprecated, only for BWC."
def
__init__
(
self
,
method_desc
):
#pragma NO COVER PyPy
self
.
desc
=
method_desc
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 +502,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 +513,6 @@ except ImportError: #pragma NO COVER
queryProxy
=
py_queryProxy
queryInnerProxy
=
py_queryInnerProxy
removeAllProxies
=
py_removeAllProxies
non_overridable
=
PyNonOverridable
else
:
def
non_overridable
(
func
):
def
non_overridable
(
func
):
return
property
(
lambda
self
:
func
.
__get__
(
self
))
src/zope/proxy/tests/test_decorator.py
View file @
37e9ea6a
...
...
@@ -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
((
...
...
src/zope/proxy/tests/test_proxy.py
View file @
37e9ea6a
...
...
@@ -581,6 +581,90 @@ 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_method_in_proxy_subclass
(
self
):
class
Proxy
(
self
.
_getTargetClass
()):
def
__getitem__
(
self
,
k
):
return
k
proxy
=
Proxy
(
object
())
# Both when called by the interpreter, which bypasses
# __getattribute__
self
.
assertEqual
(
proxy
[
42
],
42
)
# And when asked for as an attribute
self
.
assertNotEqual
(
getattr
(
proxy
,
'__getitem__'
),
self
)
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 +672,6 @@ class ProxyBaseTestCase(PyProxyBaseTestCase):
from
zope.proxy
import
ProxyBase
return
ProxyBase
class
Test_py_getProxiedObject
(
unittest
.
TestCase
):
def
_callFUT
(
self
,
*
args
):
...
...
@@ -620,7 +703,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
):
...
...
tox.ini
View file @
37e9ea6a
...
...
@@ -27,7 +27,7 @@ commands =
[testenv:coverage]
basepython
=
python2.
6
python2.
7
commands
=
# The installed version messes up nose's test discovery / coverage reporting
# So, we uninstall that from the environment, and then install the editable
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment