Skip to content

Commit f7a28f4

Browse files
Refactor code so pure Python version usable even when extension compiled.
1 parent 8567e8b commit f7a28f4

7 files changed

Lines changed: 261 additions & 237 deletions

File tree

src/wrapt/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
__version_info__ = ('1', '16', '0rc2')
22
__version__ = '.'.join(__version_info__)
33

4-
from .wrappers import (ObjectProxy, CallableObjectProxy, FunctionWrapper,
5-
BoundFunctionWrapper, WeakFunctionProxy, PartialCallableObjectProxy,
6-
resolve_path, apply_patch, wrap_object, wrap_object_attribute,
4+
from .variants import (ObjectProxy, CallableObjectProxy, FunctionWrapper,
5+
BoundFunctionWrapper, PartialCallableObjectProxy)
6+
7+
from .patches import (resolve_path, apply_patch, wrap_object, wrap_object_attribute,
78
function_wrapper, wrap_function_wrapper, patch_function_wrapper,
89
transient_function_wrapper)
910

11+
from .weakrefs import WeakFunctionProxy
12+
1013
from .decorators import (adapter_factory, AdapterFactory, decorator,
1114
synchronized)
1215

src/wrapt/__wrapt__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import os
2+
3+
from .wrappers import (ObjectProxy, CallableObjectProxy,
4+
PartialCallableObjectProxy, FunctionWrapper,
5+
BoundFunctionWrapper, _FunctionWrapperBase)
6+
7+
try:
8+
if not os.environ.get('WRAPT_DISABLE_EXTENSIONS'):
9+
from ._wrappers import (ObjectProxy, CallableObjectProxy,
10+
PartialCallableObjectProxy, FunctionWrapper,
11+
BoundFunctionWrapper, _FunctionWrapperBase)
12+
13+
except ImportError:
14+
pass

src/wrapt/decorators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def exec_(_code_, _globs_=None, _locs_=None):
4141
except ImportError:
4242
pass
4343

44-
from .wrappers import (FunctionWrapper, BoundFunctionWrapper, ObjectProxy,
44+
from .variants import (FunctionWrapper, BoundFunctionWrapper, ObjectProxy,
4545
CallableObjectProxy)
4646

4747
# Adapter wrapper for the wrapped function which will overlay certain

src/wrapt/importer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
string_types = str,
1616
from importlib.util import find_spec
1717

18-
from .wrappers import ObjectProxy
18+
from .variants import ObjectProxy
1919

2020
# The dictionary registering any post import hooks to be triggered once
2121
# the target module has been imported. Once a module has been imported

src/wrapt/patches.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import inspect
2+
import sys
3+
4+
PY2 = sys.version_info[0] == 2
5+
6+
if PY2:
7+
string_types = basestring,
8+
else:
9+
string_types = str,
10+
11+
from .variants import FunctionWrapper
12+
13+
# Helper functions for applying wrappers to existing functions.
14+
15+
def resolve_path(module, name):
16+
if isinstance(module, string_types):
17+
__import__(module)
18+
module = sys.modules[module]
19+
20+
parent = module
21+
22+
path = name.split('.')
23+
attribute = path[0]
24+
25+
# We can't just always use getattr() because in doing
26+
# that on a class it will cause binding to occur which
27+
# will complicate things later and cause some things not
28+
# to work. For the case of a class we therefore access
29+
# the __dict__ directly. To cope though with the wrong
30+
# class being given to us, or a method being moved into
31+
# a base class, we need to walk the class hierarchy to
32+
# work out exactly which __dict__ the method was defined
33+
# in, as accessing it from __dict__ will fail if it was
34+
# not actually on the class given. Fallback to using
35+
# getattr() if we can't find it. If it truly doesn't
36+
# exist, then that will fail.
37+
38+
def lookup_attribute(parent, attribute):
39+
if inspect.isclass(parent):
40+
for cls in inspect.getmro(parent):
41+
if attribute in vars(cls):
42+
return vars(cls)[attribute]
43+
else:
44+
return getattr(parent, attribute)
45+
else:
46+
return getattr(parent, attribute)
47+
48+
original = lookup_attribute(parent, attribute)
49+
50+
for attribute in path[1:]:
51+
parent = original
52+
original = lookup_attribute(parent, attribute)
53+
54+
return (parent, attribute, original)
55+
56+
def apply_patch(parent, attribute, replacement):
57+
setattr(parent, attribute, replacement)
58+
59+
def wrap_object(module, name, factory, args=(), kwargs={}):
60+
(parent, attribute, original) = resolve_path(module, name)
61+
wrapper = factory(original, *args, **kwargs)
62+
apply_patch(parent, attribute, wrapper)
63+
return wrapper
64+
65+
# Function for applying a proxy object to an attribute of a class
66+
# instance. The wrapper works by defining an attribute of the same name
67+
# on the class which is a descriptor and which intercepts access to the
68+
# instance attribute. Note that this cannot be used on attributes which
69+
# are themselves defined by a property object.
70+
71+
class AttributeWrapper(object):
72+
73+
def __init__(self, attribute, factory, args, kwargs):
74+
self.attribute = attribute
75+
self.factory = factory
76+
self.args = args
77+
self.kwargs = kwargs
78+
79+
def __get__(self, instance, owner):
80+
value = instance.__dict__[self.attribute]
81+
return self.factory(value, *self.args, **self.kwargs)
82+
83+
def __set__(self, instance, value):
84+
instance.__dict__[self.attribute] = value
85+
86+
def __delete__(self, instance):
87+
del instance.__dict__[self.attribute]
88+
89+
def wrap_object_attribute(module, name, factory, args=(), kwargs={}):
90+
path, attribute = name.rsplit('.', 1)
91+
parent = resolve_path(module, path)[2]
92+
wrapper = AttributeWrapper(attribute, factory, args, kwargs)
93+
apply_patch(parent, attribute, wrapper)
94+
return wrapper
95+
96+
# Functions for creating a simple decorator using a FunctionWrapper,
97+
# plus short cut functions for applying wrappers to functions. These are
98+
# for use when doing monkey patching. For a more featured way of
99+
# creating decorators see the decorator decorator instead.
100+
101+
def function_wrapper(wrapper):
102+
def _wrapper(wrapped, instance, args, kwargs):
103+
target_wrapped = args[0]
104+
if instance is None:
105+
target_wrapper = wrapper
106+
elif inspect.isclass(instance):
107+
target_wrapper = wrapper.__get__(None, instance)
108+
else:
109+
target_wrapper = wrapper.__get__(instance, type(instance))
110+
return FunctionWrapper(target_wrapped, target_wrapper)
111+
return FunctionWrapper(wrapper, _wrapper)
112+
113+
def wrap_function_wrapper(module, name, wrapper):
114+
return wrap_object(module, name, FunctionWrapper, (wrapper,))
115+
116+
def patch_function_wrapper(module, name, enabled=None):
117+
def _wrapper(wrapper):
118+
return wrap_object(module, name, FunctionWrapper, (wrapper, enabled))
119+
return _wrapper
120+
121+
def transient_function_wrapper(module, name):
122+
def _decorator(wrapper):
123+
def _wrapper(wrapped, instance, args, kwargs):
124+
target_wrapped = args[0]
125+
if instance is None:
126+
target_wrapper = wrapper
127+
elif inspect.isclass(instance):
128+
target_wrapper = wrapper.__get__(None, instance)
129+
else:
130+
target_wrapper = wrapper.__get__(instance, type(instance))
131+
def _execute(wrapped, instance, args, kwargs):
132+
(parent, attribute, original) = resolve_path(module, name)
133+
replacement = FunctionWrapper(original, target_wrapper)
134+
setattr(parent, attribute, replacement)
135+
try:
136+
return wrapped(*args, **kwargs)
137+
finally:
138+
setattr(parent, attribute, original)
139+
return FunctionWrapper(target_wrapped, _execute)
140+
return FunctionWrapper(wrapper, _wrapper)
141+
return _decorator

src/wrapt/weakrefs.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import functools
2+
import weakref
3+
4+
from .variants import ObjectProxy, _FunctionWrapperBase
5+
6+
# A weak function proxy. This will work on instance methods, class
7+
# methods, static methods and regular functions. Special treatment is
8+
# needed for the method types because the bound method is effectively a
9+
# transient object and applying a weak reference to one will immediately
10+
# result in it being destroyed and the weakref callback called. The weak
11+
# reference is therefore applied to the instance the method is bound to
12+
# and the original function. The function is then rebound at the point
13+
# of a call via the weak function proxy.
14+
15+
def _weak_function_proxy_callback(ref, proxy, callback):
16+
if proxy._self_expired:
17+
return
18+
19+
proxy._self_expired = True
20+
21+
# This could raise an exception. We let it propagate back and let
22+
# the weakref.proxy() deal with it, at which point it generally
23+
# prints out a short error message direct to stderr and keeps going.
24+
25+
if callback is not None:
26+
callback(proxy)
27+
28+
class WeakFunctionProxy(ObjectProxy):
29+
30+
__slots__ = ('_self_expired', '_self_instance')
31+
32+
def __init__(self, wrapped, callback=None):
33+
# We need to determine if the wrapped function is actually a
34+
# bound method. In the case of a bound method, we need to keep a
35+
# reference to the original unbound function and the instance.
36+
# This is necessary because if we hold a reference to the bound
37+
# function, it will be the only reference and given it is a
38+
# temporary object, it will almost immediately expire and
39+
# the weakref callback triggered. So what is done is that we
40+
# hold a reference to the instance and unbound function and
41+
# when called bind the function to the instance once again and
42+
# then call it. Note that we avoid using a nested function for
43+
# the callback here so as not to cause any odd reference cycles.
44+
45+
_callback = callback and functools.partial(
46+
_weak_function_proxy_callback, proxy=self,
47+
callback=callback)
48+
49+
self._self_expired = False
50+
51+
if isinstance(wrapped, _FunctionWrapperBase):
52+
self._self_instance = weakref.ref(wrapped._self_instance,
53+
_callback)
54+
55+
if wrapped._self_parent is not None:
56+
super(WeakFunctionProxy, self).__init__(
57+
weakref.proxy(wrapped._self_parent, _callback))
58+
59+
else:
60+
super(WeakFunctionProxy, self).__init__(
61+
weakref.proxy(wrapped, _callback))
62+
63+
return
64+
65+
try:
66+
self._self_instance = weakref.ref(wrapped.__self__, _callback)
67+
68+
super(WeakFunctionProxy, self).__init__(
69+
weakref.proxy(wrapped.__func__, _callback))
70+
71+
except AttributeError:
72+
self._self_instance = None
73+
74+
super(WeakFunctionProxy, self).__init__(
75+
weakref.proxy(wrapped, _callback))
76+
77+
def __call__(*args, **kwargs):
78+
def _unpack_self(self, *args):
79+
return self, args
80+
81+
self, args = _unpack_self(*args)
82+
83+
# We perform a boolean check here on the instance and wrapped
84+
# function as that will trigger the reference error prior to
85+
# calling if the reference had expired.
86+
87+
instance = self._self_instance and self._self_instance()
88+
function = self.__wrapped__ and self.__wrapped__
89+
90+
# If the wrapped function was originally a bound function, for
91+
# which we retained a reference to the instance and the unbound
92+
# function we need to rebind the function and then call it. If
93+
# not just called the wrapped function.
94+
95+
if instance is None:
96+
return self.__wrapped__(*args, **kwargs)
97+
98+
return function.__get__(instance, type(instance))(*args, **kwargs)

0 commit comments

Comments
 (0)