-
Notifications
You must be signed in to change notification settings - Fork 231
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor code so pure Python version usable even when extension compi…
…led.
- Loading branch information
1 parent
8567e8b
commit f7a28f4
Showing
7 changed files
with
261 additions
and
237 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import os | ||
|
||
from .wrappers import (ObjectProxy, CallableObjectProxy, | ||
PartialCallableObjectProxy, FunctionWrapper, | ||
BoundFunctionWrapper, _FunctionWrapperBase) | ||
|
||
try: | ||
if not os.environ.get('WRAPT_DISABLE_EXTENSIONS'): | ||
from ._wrappers import (ObjectProxy, CallableObjectProxy, | ||
PartialCallableObjectProxy, FunctionWrapper, | ||
BoundFunctionWrapper, _FunctionWrapperBase) | ||
|
||
except ImportError: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import inspect | ||
import sys | ||
|
||
PY2 = sys.version_info[0] == 2 | ||
|
||
if PY2: | ||
string_types = basestring, | ||
else: | ||
string_types = str, | ||
|
||
from .variants import FunctionWrapper | ||
|
||
# Helper functions for applying wrappers to existing functions. | ||
|
||
def resolve_path(module, name): | ||
if isinstance(module, string_types): | ||
__import__(module) | ||
module = sys.modules[module] | ||
|
||
parent = module | ||
|
||
path = name.split('.') | ||
attribute = path[0] | ||
|
||
# We can't just always use getattr() because in doing | ||
# that on a class it will cause binding to occur which | ||
# will complicate things later and cause some things not | ||
# to work. For the case of a class we therefore access | ||
# the __dict__ directly. To cope though with the wrong | ||
# class being given to us, or a method being moved into | ||
# a base class, we need to walk the class hierarchy to | ||
# work out exactly which __dict__ the method was defined | ||
# in, as accessing it from __dict__ will fail if it was | ||
# not actually on the class given. Fallback to using | ||
# getattr() if we can't find it. If it truly doesn't | ||
# exist, then that will fail. | ||
|
||
def lookup_attribute(parent, attribute): | ||
if inspect.isclass(parent): | ||
for cls in inspect.getmro(parent): | ||
if attribute in vars(cls): | ||
return vars(cls)[attribute] | ||
else: | ||
return getattr(parent, attribute) | ||
else: | ||
return getattr(parent, attribute) | ||
|
||
original = lookup_attribute(parent, attribute) | ||
|
||
for attribute in path[1:]: | ||
parent = original | ||
original = lookup_attribute(parent, attribute) | ||
|
||
return (parent, attribute, original) | ||
|
||
def apply_patch(parent, attribute, replacement): | ||
setattr(parent, attribute, replacement) | ||
|
||
def wrap_object(module, name, factory, args=(), kwargs={}): | ||
(parent, attribute, original) = resolve_path(module, name) | ||
wrapper = factory(original, *args, **kwargs) | ||
apply_patch(parent, attribute, wrapper) | ||
return wrapper | ||
|
||
# Function for applying a proxy object to an attribute of a class | ||
# instance. The wrapper works by defining an attribute of the same name | ||
# on the class which is a descriptor and which intercepts access to the | ||
# instance attribute. Note that this cannot be used on attributes which | ||
# are themselves defined by a property object. | ||
|
||
class AttributeWrapper(object): | ||
|
||
def __init__(self, attribute, factory, args, kwargs): | ||
self.attribute = attribute | ||
self.factory = factory | ||
self.args = args | ||
self.kwargs = kwargs | ||
|
||
def __get__(self, instance, owner): | ||
value = instance.__dict__[self.attribute] | ||
return self.factory(value, *self.args, **self.kwargs) | ||
|
||
def __set__(self, instance, value): | ||
instance.__dict__[self.attribute] = value | ||
|
||
def __delete__(self, instance): | ||
del instance.__dict__[self.attribute] | ||
|
||
def wrap_object_attribute(module, name, factory, args=(), kwargs={}): | ||
path, attribute = name.rsplit('.', 1) | ||
parent = resolve_path(module, path)[2] | ||
wrapper = AttributeWrapper(attribute, factory, args, kwargs) | ||
apply_patch(parent, attribute, wrapper) | ||
return wrapper | ||
|
||
# Functions for creating a simple decorator using a FunctionWrapper, | ||
# plus short cut functions for applying wrappers to functions. These are | ||
# for use when doing monkey patching. For a more featured way of | ||
# creating decorators see the decorator decorator instead. | ||
|
||
def function_wrapper(wrapper): | ||
def _wrapper(wrapped, instance, args, kwargs): | ||
target_wrapped = args[0] | ||
if instance is None: | ||
target_wrapper = wrapper | ||
elif inspect.isclass(instance): | ||
target_wrapper = wrapper.__get__(None, instance) | ||
else: | ||
target_wrapper = wrapper.__get__(instance, type(instance)) | ||
return FunctionWrapper(target_wrapped, target_wrapper) | ||
return FunctionWrapper(wrapper, _wrapper) | ||
|
||
def wrap_function_wrapper(module, name, wrapper): | ||
return wrap_object(module, name, FunctionWrapper, (wrapper,)) | ||
|
||
def patch_function_wrapper(module, name, enabled=None): | ||
def _wrapper(wrapper): | ||
return wrap_object(module, name, FunctionWrapper, (wrapper, enabled)) | ||
return _wrapper | ||
|
||
def transient_function_wrapper(module, name): | ||
def _decorator(wrapper): | ||
def _wrapper(wrapped, instance, args, kwargs): | ||
target_wrapped = args[0] | ||
if instance is None: | ||
target_wrapper = wrapper | ||
elif inspect.isclass(instance): | ||
target_wrapper = wrapper.__get__(None, instance) | ||
else: | ||
target_wrapper = wrapper.__get__(instance, type(instance)) | ||
def _execute(wrapped, instance, args, kwargs): | ||
(parent, attribute, original) = resolve_path(module, name) | ||
replacement = FunctionWrapper(original, target_wrapper) | ||
setattr(parent, attribute, replacement) | ||
try: | ||
return wrapped(*args, **kwargs) | ||
finally: | ||
setattr(parent, attribute, original) | ||
return FunctionWrapper(target_wrapped, _execute) | ||
return FunctionWrapper(wrapper, _wrapper) | ||
return _decorator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import functools | ||
import weakref | ||
|
||
from .variants import ObjectProxy, _FunctionWrapperBase | ||
|
||
# A weak function proxy. This will work on instance methods, class | ||
# methods, static methods and regular functions. Special treatment is | ||
# needed for the method types because the bound method is effectively a | ||
# transient object and applying a weak reference to one will immediately | ||
# result in it being destroyed and the weakref callback called. The weak | ||
# reference is therefore applied to the instance the method is bound to | ||
# and the original function. The function is then rebound at the point | ||
# of a call via the weak function proxy. | ||
|
||
def _weak_function_proxy_callback(ref, proxy, callback): | ||
if proxy._self_expired: | ||
return | ||
|
||
proxy._self_expired = True | ||
|
||
# This could raise an exception. We let it propagate back and let | ||
# the weakref.proxy() deal with it, at which point it generally | ||
# prints out a short error message direct to stderr and keeps going. | ||
|
||
if callback is not None: | ||
callback(proxy) | ||
|
||
class WeakFunctionProxy(ObjectProxy): | ||
|
||
__slots__ = ('_self_expired', '_self_instance') | ||
|
||
def __init__(self, wrapped, callback=None): | ||
# We need to determine if the wrapped function is actually a | ||
# bound method. In the case of a bound method, we need to keep a | ||
# reference to the original unbound function and the instance. | ||
# This is necessary because if we hold a reference to the bound | ||
# function, it will be the only reference and given it is a | ||
# temporary object, it will almost immediately expire and | ||
# the weakref callback triggered. So what is done is that we | ||
# hold a reference to the instance and unbound function and | ||
# when called bind the function to the instance once again and | ||
# then call it. Note that we avoid using a nested function for | ||
# the callback here so as not to cause any odd reference cycles. | ||
|
||
_callback = callback and functools.partial( | ||
_weak_function_proxy_callback, proxy=self, | ||
callback=callback) | ||
|
||
self._self_expired = False | ||
|
||
if isinstance(wrapped, _FunctionWrapperBase): | ||
self._self_instance = weakref.ref(wrapped._self_instance, | ||
_callback) | ||
|
||
if wrapped._self_parent is not None: | ||
super(WeakFunctionProxy, self).__init__( | ||
weakref.proxy(wrapped._self_parent, _callback)) | ||
|
||
else: | ||
super(WeakFunctionProxy, self).__init__( | ||
weakref.proxy(wrapped, _callback)) | ||
|
||
return | ||
|
||
try: | ||
self._self_instance = weakref.ref(wrapped.__self__, _callback) | ||
|
||
super(WeakFunctionProxy, self).__init__( | ||
weakref.proxy(wrapped.__func__, _callback)) | ||
|
||
except AttributeError: | ||
self._self_instance = None | ||
|
||
super(WeakFunctionProxy, self).__init__( | ||
weakref.proxy(wrapped, _callback)) | ||
|
||
def __call__(*args, **kwargs): | ||
def _unpack_self(self, *args): | ||
return self, args | ||
|
||
self, args = _unpack_self(*args) | ||
|
||
# We perform a boolean check here on the instance and wrapped | ||
# function as that will trigger the reference error prior to | ||
# calling if the reference had expired. | ||
|
||
instance = self._self_instance and self._self_instance() | ||
function = self.__wrapped__ and self.__wrapped__ | ||
|
||
# If the wrapped function was originally a bound function, for | ||
# which we retained a reference to the instance and the unbound | ||
# function we need to rebind the function and then call it. If | ||
# not just called the wrapped function. | ||
|
||
if instance is None: | ||
return self.__wrapped__(*args, **kwargs) | ||
|
||
return function.__get__(instance, type(instance))(*args, **kwargs) |
Oops, something went wrong.