Improvements to ability to bind an alternate function prototype to the wrapper for inspect.getargspec().
This commit is contained in:
@@ -2,4 +2,4 @@ __version_info__ = ('0', '9', '0')
|
||||
__version__ = '.'.join(__version_info__)
|
||||
|
||||
from .wrappers import ObjectProxy, FunctionWrapper
|
||||
from .decorators import decorator, adapter
|
||||
from .decorators import decorator
|
||||
|
||||
788
src/_wrappers.c
788
src/_wrappers.c
File diff suppressed because it is too large
Load Diff
@@ -133,21 +133,6 @@ def _validate_parameters(func, spec, *positional, **named):
|
||||
_missing_arguments(f_name, kwonlyargs, False, arg2value)
|
||||
return arg2value
|
||||
|
||||
# Copy name attributes from a wrapped function onto an wrapper. This is
|
||||
# only used in mapping the name from the final wrapped function to which
|
||||
# an adapter is applied onto the adapter itself. All other details come
|
||||
# from the adapter function via the function wrapper so we don't update
|
||||
# __dict__ or __wrapped__.
|
||||
|
||||
def _update_adapter(wrapper, target):
|
||||
for attr in ('__module__', '__name__', '__qualname__'):
|
||||
try:
|
||||
value = getattr(target, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
setattr(wrapper, attr, value)
|
||||
|
||||
# Decorators for creating other decorators. These decorators and the
|
||||
# wrappers which they use are designed to properly preserve any name
|
||||
# attributes, function signatures etc, in addition to the wrappers
|
||||
@@ -157,16 +142,19 @@ def _update_adapter(wrapper, target):
|
||||
|
||||
WRAPPER_ARGLIST = ('wrapped', 'instance', 'args', 'kwargs')
|
||||
|
||||
def decorator(wrapper=None, target=None, validate=True):
|
||||
def decorator(wrapper=None, adapter=None, validate=True):
|
||||
# The decorator takes some optional keyword parameters to change its
|
||||
# behaviour. The decorator works out whether parameters have been
|
||||
# passed based on whether the first positional argument, which is
|
||||
# the wrapper which implements the user decorator, has been
|
||||
# supplied. The 'target' argument is used to optionally denote a
|
||||
# function which is wrapped by an adapter decorator. In that case
|
||||
# the name attributes are copied from the target function rather
|
||||
# than those of the adapter function. The 'validate' argument, which
|
||||
# defaults to True, indicates whether validation of decorator
|
||||
# supplied. The 'adapter' argument is used to optionally denote a
|
||||
# separate function which is notionally used by an adapter
|
||||
# decorator. In that case the function '__code__' and '__defaults__'
|
||||
# attributes are used from the adapter function rather than those of
|
||||
# the wrapped function. This allows for the argument specification
|
||||
# from inspect.getargspec() to be overridden with a prototype for a
|
||||
# different function than what was wrapped. The 'validate' argument,
|
||||
# which defaults to True, indicates whether validation of decorator
|
||||
# parameters should be validated at the time the decorator is
|
||||
# applied, or whether a failure is allow to occur only when the
|
||||
# wrapped function is later called and the user wrapper function
|
||||
@@ -210,10 +198,9 @@ def decorator(wrapper=None, target=None, validate=True):
|
||||
|
||||
def _wrapper(func):
|
||||
result = FunctionWrapper(wrapped=func,
|
||||
wrapper=wrapper, args=decorator_args,
|
||||
kwargs=decorator_kwargs)
|
||||
if target:
|
||||
_update_adapter(result, target)
|
||||
wrapper=wrapper, wrapper_args=decorator_args,
|
||||
wrapper_kwargs=decorator_kwargs,
|
||||
adapter=adapter)
|
||||
return result
|
||||
return _wrapper
|
||||
|
||||
@@ -228,9 +215,8 @@ def decorator(wrapper=None, target=None, validate=True):
|
||||
|
||||
@wraps(wrapper)
|
||||
def _wrapper(func):
|
||||
result = FunctionWrapper(wrapped=func, wrapper=wrapper)
|
||||
if target:
|
||||
_update_adapter(result, target)
|
||||
result = FunctionWrapper(wrapped=func, wrapper=wrapper,
|
||||
adapter=adapter)
|
||||
return result
|
||||
return _wrapper
|
||||
|
||||
@@ -241,10 +227,4 @@ def decorator(wrapper=None, target=None, validate=True):
|
||||
# a partial using the collected default parameters and the
|
||||
# adapter function if one is being used.
|
||||
|
||||
return partial(decorator, target=target, validate=validate)
|
||||
|
||||
def adapter(target):
|
||||
@decorator(target=target)
|
||||
def wrapper(wrapped, instance, args, kwargs):
|
||||
return wrapped(*args, **kwargs)
|
||||
return wrapper
|
||||
return partial(decorator, adapter=adapter, validate=validate)
|
||||
|
||||
115
src/wrappers.py
115
src/wrappers.py
@@ -51,7 +51,7 @@ class _ObjectProxyMetaType(type):
|
||||
class ObjectProxy(six.with_metaclass(_ObjectProxyMetaType)):
|
||||
|
||||
def __init__(self, wrapped):
|
||||
self._self_wrapped = wrapped
|
||||
object.__setattr__(self, '_self_wrapped', wrapped)
|
||||
|
||||
# Python 3.2+ has the __qualname__ attribute, but it does not
|
||||
# allow it to be overridden using a property and it must instead
|
||||
@@ -360,14 +360,67 @@ class ObjectProxy(six.with_metaclass(_ObjectProxyMetaType)):
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._self_wrapped(*args, **kwargs)
|
||||
|
||||
class _BoundFunctionWrapper(ObjectProxy):
|
||||
class _FunctionWrapperBase(ObjectProxy):
|
||||
|
||||
def __init__(self, wrapped, instance, wrapper, args=(), kwargs={}):
|
||||
super(_BoundFunctionWrapper, self).__init__(wrapped)
|
||||
self._self_instance = instance
|
||||
self._self_wrapper = wrapper
|
||||
self._self_wrapper_args = args
|
||||
self._self_wrapper_kwargs = kwargs
|
||||
def __init__(self, wrapped, instance, wrapper, wrapper_args=(),
|
||||
wrapper_kwargs={}, adapter=None, bound_type=None):
|
||||
|
||||
super(_FunctionWrapperBase, self).__init__(wrapped)
|
||||
|
||||
object.__setattr__(self, '_self_instance', instance)
|
||||
object.__setattr__(self, '_self_wrapper', wrapper)
|
||||
object.__setattr__(self, '_self_wrapper_args', wrapper_args)
|
||||
object.__setattr__(self, '_self_wrapper_kwargs', wrapper_kwargs)
|
||||
object.__setattr__(self, '_self_adapter', adapter)
|
||||
object.__setattr__(self, '_self_bound_type', bound_type)
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
# If we have already been bound to an instance of something, we
|
||||
# do not do it again and return ourselves again. This appears to
|
||||
# mirror what Python itself does.
|
||||
|
||||
if self._self_bound_type is None:
|
||||
return self
|
||||
|
||||
descriptor = self._self_wrapped.__get__(instance, owner)
|
||||
|
||||
return self._self_bound_type(descriptor, instance, self._self_wrapper,
|
||||
self._self_wrapper_args, self._self_wrapper_kwargs,
|
||||
self._self_adapter)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# This is generally invoked when the wrapped function is being
|
||||
# called as a normal function and is not bound to a class as an
|
||||
# instance method. This is also invoked in the case where the
|
||||
# wrapped function was a method, but this wrapper was in turn
|
||||
# wrapped using the staticmethod decorator.
|
||||
|
||||
return self._self_wrapper(self._self_wrapped, self._self_instance,
|
||||
args, kwargs, *self._self_wrapper_args,
|
||||
**self._self_wrapper_kwargs)
|
||||
|
||||
# If an adapter function was provided we want to return certain
|
||||
# attributes of the function from the adapter rather than the
|
||||
# wrapped function so things like inspect.getargspec() will reflect
|
||||
# the prototype of the adapter and not the wrapped function.
|
||||
|
||||
@property
|
||||
def __code__(self):
|
||||
if self._self_adapter:
|
||||
return self._self_adapter.__code__
|
||||
return self._self_wrapped.__code__
|
||||
|
||||
@property
|
||||
def __defaults__(self):
|
||||
if self._self_adapter:
|
||||
return self._self_adapter.__defaults__
|
||||
return self._self_wrapped.__defaults__
|
||||
|
||||
if six.PY2:
|
||||
func_code = __code__
|
||||
func_defaults = __defaults__
|
||||
|
||||
class _BoundFunctionWrapper(_FunctionWrapperBase):
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# As in this case we would be dealing with a classmethod or
|
||||
@@ -389,14 +442,7 @@ class _BoundFunctionWrapper(ObjectProxy):
|
||||
args, kwargs, *self._self_wrapper_args,
|
||||
**self._self_wrapper_kwargs)
|
||||
|
||||
class _BoundMethodWrapper(ObjectProxy):
|
||||
|
||||
def __init__(self, wrapped, instance, wrapper, args=(), kwargs={}):
|
||||
super(_BoundMethodWrapper, self).__init__(wrapped)
|
||||
self._self_instance = instance
|
||||
self._self_wrapper = wrapper
|
||||
self._self_wrapper_args = args
|
||||
self._self_wrapper_kwargs = kwargs
|
||||
class _BoundMethodWrapper(_FunctionWrapperBase):
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if self._self_instance is None:
|
||||
@@ -416,13 +462,10 @@ class _BoundMethodWrapper(ObjectProxy):
|
||||
args, kwargs, *self._self_wrapper_args,
|
||||
**self._self_wrapper_kwargs)
|
||||
|
||||
class FunctionWrapper(ObjectProxy):
|
||||
class FunctionWrapper(_FunctionWrapperBase):
|
||||
|
||||
def __init__(self, wrapped, wrapper, args=(), kwargs={}):
|
||||
super(FunctionWrapper, self).__init__(wrapped)
|
||||
self._self_wrapper = wrapper
|
||||
self._self_wrapper_args = args
|
||||
self._self_wrapper_kwargs = kwargs
|
||||
def __init__(self, wrapped, wrapper, wrapper_args=(), wrapper_kwargs={},
|
||||
adapter=None):
|
||||
|
||||
# We need to do special fixups on the args in the case of an
|
||||
# instancemethod where called via the class and the instance is
|
||||
@@ -434,10 +477,7 @@ class FunctionWrapper(ObjectProxy):
|
||||
# Note that there isn't strictly a fool proof method of knowing
|
||||
# which is occuring because if a decorator using this code wraps
|
||||
# other decorators and they are poorly implemented they can
|
||||
# throw away important information needed to determine it. Some
|
||||
# ways that it could be determined in Python 2 are also not
|
||||
# possible in Python 3 due to the concept of unbound methods
|
||||
# being done away with.
|
||||
# throw away important information needed to determine it.
|
||||
#
|
||||
# Anyway, the best we can do is look at the original type of the
|
||||
# object which was wrapped prior to any binding being done and
|
||||
@@ -453,26 +493,13 @@ class FunctionWrapper(ObjectProxy):
|
||||
# that those other decorators be fixed. It is also only an issue
|
||||
# if a decorator wants to actually do things with the arguments.
|
||||
|
||||
if isinstance(self._self_wrapped, (classmethod, staticmethod)):
|
||||
self._self_bound_type = _BoundFunctionWrapper
|
||||
if isinstance(wrapped, (classmethod, staticmethod)):
|
||||
bound_type = _BoundFunctionWrapper
|
||||
else:
|
||||
self._self_bound_type = _BoundMethodWrapper
|
||||
bound_type = _BoundMethodWrapper
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
descriptor = self._self_wrapped.__get__(instance, owner)
|
||||
|
||||
return self._self_bound_type(descriptor, instance, self._self_wrapper,
|
||||
self._self_wrapper_args, self._self_wrapper_kwargs)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# This is invoked when the wrapped function is being called as a
|
||||
# normal function and is not bound to a class as an instance
|
||||
# method. This is also invoked in the case where the wrapped
|
||||
# function was a method, but this wrapper was in turn wrapped
|
||||
# using the staticmethod decorator.
|
||||
|
||||
return self._self_wrapper(self._self_wrapped, None, args,
|
||||
kwargs, *self._self_wrapper_args, **self._self_wrapper_kwargs)
|
||||
super(FunctionWrapper, self).__init__(wrapped, None, wrapper,
|
||||
wrapper_args, wrapper_kwargs, adapter, bound_type)
|
||||
|
||||
try:
|
||||
from ._wrappers import ObjectProxy as C_ObjectProxy
|
||||
|
||||
@@ -11,12 +11,11 @@ from wrapt import six
|
||||
DECORATORS_CODE = """
|
||||
import wrapt
|
||||
|
||||
def adapter1(func):
|
||||
@wrapt.adapter(target=func)
|
||||
def _adapter(arg):
|
||||
'''adapter documentation'''
|
||||
return func(arg, arg)
|
||||
return _adapter
|
||||
def prototype(arg): pass
|
||||
@wrapt.decorator(adapter=prototype)
|
||||
def adapter1(wrapped, instance, args, kwargs):
|
||||
'''adapter documentation'''
|
||||
return wrapped(*args, **kwargs)
|
||||
"""
|
||||
|
||||
decorators = imp.new_module('decorators')
|
||||
@@ -58,14 +57,16 @@ class TestNamingAdapter(unittest.TestCase):
|
||||
self.assertEqual(function1d.__module__, __name__)
|
||||
|
||||
def test_doc_string(self):
|
||||
# Test preservation of function __doc__ attribute from the
|
||||
# adapter rather than from the original target wrapped function.
|
||||
# Test preservation of function __doc__ attribute. It is
|
||||
# still the documentation from the wrapped function, not
|
||||
# of the adapter.
|
||||
|
||||
self.assertEqual(function1d.__doc__, 'adapter documentation')
|
||||
self.assertEqual(function1d.__doc__, 'documentation')
|
||||
|
||||
def test_argspec(self):
|
||||
# Test preservation of function argument specification. It
|
||||
# actually needs to match that of the adapter function.
|
||||
# actually needs to match that of the adapter function the
|
||||
# prototype of which was supplied via the dummy function.
|
||||
|
||||
def _adapter(arg): pass
|
||||
|
||||
@@ -78,61 +79,5 @@ class TestNamingAdapter(unittest.TestCase):
|
||||
|
||||
self.assertTrue(isinstance(function1d, type(function1o)))
|
||||
|
||||
class TestAdapter(unittest.TestCase):
|
||||
|
||||
def test_no_arguments(self):
|
||||
events = []
|
||||
|
||||
def _adapter(events):
|
||||
def _trace(func):
|
||||
@wrapt.adapter(target=func)
|
||||
def _wrapper():
|
||||
if events is not None:
|
||||
events.append('in')
|
||||
try:
|
||||
return func()
|
||||
finally:
|
||||
if events is not None:
|
||||
events.append('out')
|
||||
return _wrapper
|
||||
return _trace
|
||||
|
||||
@_adapter(events=events)
|
||||
def _function():
|
||||
'''documentation'''
|
||||
events.append('call')
|
||||
|
||||
_function()
|
||||
|
||||
self.assertEqual(events, ['in', 'call', 'out'])
|
||||
|
||||
def test_add_argument(self):
|
||||
events = []
|
||||
|
||||
def _adapter(events):
|
||||
def _trace(func):
|
||||
@wrapt.adapter(target=func)
|
||||
def _wrapper():
|
||||
if events is not None:
|
||||
events.append('in')
|
||||
try:
|
||||
return func(1)
|
||||
finally:
|
||||
if events is not None:
|
||||
events.append('out')
|
||||
return _wrapper
|
||||
return _trace
|
||||
|
||||
@_adapter(events=events)
|
||||
def _function(*args):
|
||||
'''documentation'''
|
||||
events.append('call')
|
||||
return args
|
||||
|
||||
result = _function()
|
||||
|
||||
self.assertEqual(result, (1,))
|
||||
self.assertEqual(events, ['in', 'call', 'out'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user