142 lines
5.1 KiB
Python
142 lines
5.1 KiB
Python
import inspect
|
|
import sys
|
|
|
|
PY2 = sys.version_info[0] == 2
|
|
|
|
if PY2:
|
|
string_types = basestring,
|
|
else:
|
|
string_types = str,
|
|
|
|
from .__wrapt__ 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
|