Derive the user decorator parameters from the wrapper itself and don't require defaults.
This commit is contained in:
@@ -3,5 +3,4 @@ __version__ = '.'.join(__version_info__)
|
||||
|
||||
from .wrappers import ObjectProxy, FunctionWrapper
|
||||
from .decorators import decorator, adapter
|
||||
from .exceptions import (UnexpectedDefaultParameters, MissingDefaultParameter,
|
||||
UnexpectedParameters)
|
||||
from .exceptions import (MissingParameter, UnexpectedParameters)
|
||||
|
||||
@@ -6,8 +6,7 @@ from functools import wraps, partial
|
||||
from inspect import getargspec
|
||||
|
||||
from .wrappers import FunctionWrapper
|
||||
from .exceptions import (UnexpectedDefaultParameters,
|
||||
MissingDefaultParameter, UnexpectedParameters)
|
||||
from .exceptions import (MissingParameter, UnexpectedParameters)
|
||||
|
||||
# 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
|
||||
@@ -28,111 +27,98 @@ def _update_adapter(wrapper, target):
|
||||
# wrappers which they use are designed to properly preserve any name
|
||||
# attributes, function signatures etc, in addition to the wrappers
|
||||
# themselves acting like a transparent proxy for the original wrapped
|
||||
# function so they the wrapper is effectively indistinguishable from
|
||||
# the original wrapped function.
|
||||
# function so the wrapper is effectively indistinguishable from the
|
||||
# original wrapped function.
|
||||
|
||||
WRAPPER_ARGLIST = ('wrapped', 'instance', 'args', 'kwargs')
|
||||
|
||||
def decorator(_wrapt_wrapper=None, _wrapt_target=None,
|
||||
**_wrapt_default_params):
|
||||
# The decorator works out whether the user decorator will have its
|
||||
# own parameters. Parameters for the user decorator must avoid any
|
||||
# keyword arguments starting with '_wrapt_' as that is reserved for
|
||||
# our own use. Our 'wrapper' argument being how the user's wrapper
|
||||
# function is passed in. The 'target' argument is used to optionally
|
||||
# denote a function which is wrapped by an adapter decorator. In
|
||||
# that case the name attrributes are copied from the target function
|
||||
# rather than those of the adapter function.
|
||||
def decorator(wrapper=None, target=None):
|
||||
# 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.
|
||||
|
||||
if _wrapt_wrapper is not None:
|
||||
# The wrapper has been provided, so we must also have any
|
||||
# optional default keyword parameters for the user decorator
|
||||
# at this point if they were supplied. Before constructing
|
||||
# the decorator we validate if the list of supplied default
|
||||
# parameters are actually the same as what the users wrapper
|
||||
# function expects.
|
||||
if wrapper is not None:
|
||||
# We now need to work out whether the users decorator is
|
||||
# to take any arguments. If there are parameters, the
|
||||
# final decorator we create needs to be constructed a bit
|
||||
# differently, as when that decorator is used it needs to
|
||||
# accept parameters. Those parameters do not need to be
|
||||
# supplied if they have defaults, but at least an empty
|
||||
# argument list needs to be used on the decorator at that
|
||||
# point. When the users decorator parameters are
|
||||
# supplied, they can be as either positional or keyword
|
||||
# arguments.
|
||||
|
||||
expected_arglist = WRAPPER_ARGLIST
|
||||
complete_arglist = getargspec(_wrapt_wrapper).args
|
||||
wrapper_argspec = getargspec(wrapper)
|
||||
|
||||
received_names = set(_wrapt_default_params.keys())
|
||||
expected_names = complete_arglist[len(expected_arglist):]
|
||||
wrapper_arglist = wrapper_argspec.args
|
||||
wrapper_defaults = (wrapper_argspec.defaults and
|
||||
wrapper_arglist[-len(wrapper_argspec.defaults):] or [])
|
||||
|
||||
for name in expected_names:
|
||||
try:
|
||||
received_names.remove(name)
|
||||
except KeyError:
|
||||
raise MissingDefaultParameter('Expected value for '
|
||||
'default parameter %r was not supplied for '
|
||||
'decorator %r.' % (name, _wrapt_wrapper.__name__))
|
||||
if received_names:
|
||||
raise UnexpectedDefaultParameters('Unexpected default '
|
||||
'parameters %r supplied for decorator %r.' % (
|
||||
list(received_names), _wrapt_wrapper.__name__))
|
||||
|
||||
# If we do have default parameters, the final decorator we
|
||||
# create needs to be constructed a bit differently as when
|
||||
# that decorator is used, it needs to accept parameters.
|
||||
# Those parameters need not be supplied, but at least an
|
||||
# empty argument list needs to be used on the decorator at
|
||||
# that point. When parameters are supplied, they can be as
|
||||
# either positional or keyword arguments.
|
||||
|
||||
if len(complete_arglist) > len(expected_arglist):
|
||||
# For the case where the decorator is able to accept
|
||||
if len(wrapper_arglist) > len(WRAPPER_ARGLIST):
|
||||
# For the case where the user decorator is able to accept
|
||||
# parameters, return a partial wrapper to collect the
|
||||
# parameters.
|
||||
|
||||
@wraps(_wrapt_wrapper)
|
||||
@wraps(wrapper)
|
||||
def _partial(*decorator_args, **decorator_kwargs):
|
||||
# Since the supply of parameters is optional due to
|
||||
# having defaults, we need to construct a final set
|
||||
# of parameters by overlaying those finally supplied
|
||||
# to the decorator at the point of use over the
|
||||
# defaults. As we accept positional parameters, we
|
||||
# need to translate those back to keyword parameters
|
||||
# in the process. This allows us to pass just one
|
||||
# dictionary of parameters and we can validate the
|
||||
# set of parameters at the point the decorator is
|
||||
# used and not only let it fail at the time the
|
||||
# We need to construct a final set of parameters
|
||||
# by merging positional parameters with the
|
||||
# keyword parameters. This allows us to pass just
|
||||
# one dictionary of parameters. We can also
|
||||
# validate the set of parameters at this point so
|
||||
# that an error occurs when the decorator is used
|
||||
# and not only let it fail at the time the
|
||||
# wrapped function is called.
|
||||
|
||||
expected_names = wrapper_arglist[len(WRAPPER_ARGLIST):]
|
||||
|
||||
if len(decorator_args) > len(expected_names):
|
||||
raise UnexpectedParameters('Expected at most %r '
|
||||
'positional parameters for decorator %r, '
|
||||
'but received %r.' % (len(expected_names),
|
||||
_wrapt_wrapper.__name__, len(decorator_args)))
|
||||
wrapper.__name__, len(decorator_args)))
|
||||
|
||||
unexpected_params = []
|
||||
for name in decorator_kwargs:
|
||||
if name not in _wrapt_default_params:
|
||||
if name not in expected_names:
|
||||
unexpected_params.append(name)
|
||||
|
||||
if unexpected_params:
|
||||
raise UnexpectedParameters('Unexpected parameters '
|
||||
'%r supplied for decorator %r.' % (
|
||||
unexpected_params, _wrapt_wrapper.__name__))
|
||||
|
||||
complete_params = dict(_wrapt_default_params)
|
||||
unexpected_params, wrapper.__name__))
|
||||
|
||||
for i, arg in enumerate(decorator_args):
|
||||
if expected_names[i] in decorator_kwargs:
|
||||
raise UnexpectedParameters('Positional parameter '
|
||||
'%r also supplied as keyword parameter '
|
||||
'to decorator %r.' % (expected_names[i],
|
||||
_wrapt_wrapper.__name__))
|
||||
wrapper.__name__))
|
||||
decorator_kwargs[expected_names[i]] = arg
|
||||
|
||||
complete_params.update(decorator_kwargs)
|
||||
received_names = set(wrapper_defaults)
|
||||
received_names.update(decorator_kwargs.keys())
|
||||
|
||||
for name in expected_names:
|
||||
if name not in received_names:
|
||||
raise MissingParameter('Expected value for '
|
||||
'parameter %r was not supplied for '
|
||||
'decorator %r.' % (name, wrapper.__name__))
|
||||
|
||||
# Now create and return the final wrapper which
|
||||
# combines the parameters with the wrapped function.
|
||||
|
||||
def _wrapper(func):
|
||||
result = FunctionWrapper(wrapped=func,
|
||||
wrapper=_wrapt_wrapper, params=complete_params)
|
||||
if _wrapt_target:
|
||||
_update_adapter(result, _wrapt_target)
|
||||
wrapper=wrapper, params=decorator_kwargs)
|
||||
if target:
|
||||
_update_adapter(result, target)
|
||||
return result
|
||||
return _wrapper
|
||||
|
||||
@@ -145,11 +131,11 @@ def decorator(_wrapt_wrapper=None, _wrapt_target=None,
|
||||
# No parameters so create and return the final wrapper.
|
||||
# This is effectively the users decorator.
|
||||
|
||||
@wraps(_wrapt_wrapper)
|
||||
@wraps(wrapper)
|
||||
def _wrapper(func):
|
||||
result = FunctionWrapper(wrapped=func, wrapper=_wrapt_wrapper)
|
||||
if _wrapt_target:
|
||||
_update_adapter(result, _wrapt_target)
|
||||
result = FunctionWrapper(wrapped=func, wrapper=wrapper)
|
||||
if target:
|
||||
_update_adapter(result, target)
|
||||
return result
|
||||
return _wrapper
|
||||
|
||||
@@ -160,11 +146,10 @@ def decorator(_wrapt_wrapper=None, _wrapt_target=None,
|
||||
# a partial using the collected default parameters and the
|
||||
# adapter function if one is being used.
|
||||
|
||||
return partial(decorator, _wrapt_target=_wrapt_target,
|
||||
**_wrapt_default_params)
|
||||
return partial(decorator, target=target)
|
||||
|
||||
def adapter(target):
|
||||
@decorator(_wrapt_target=target)
|
||||
@decorator(target=target)
|
||||
def wrapper(wrapped, instance, args, kwargs):
|
||||
return wrapped(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
class UnexpectedDefaultParameters(Exception): pass
|
||||
class MissingDefaultParameter(Exception): pass
|
||||
class UnexpectedParameters(Exception): pass
|
||||
class MissingParameter(TypeError): pass
|
||||
class UnexpectedParameters(TypeError): pass
|
||||
|
||||
@@ -27,10 +27,10 @@ class TestDecorator(unittest.TestCase):
|
||||
_args = (1, 2)
|
||||
_kwargs = { 'one': 1, 'two': 2 }
|
||||
|
||||
@wrapt.decorator(param1=1, param2=2)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
self.assertEqual(param1, 1)
|
||||
self.assertEqual(param2, 2)
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1=1, p2=2):
|
||||
self.assertEqual(p1, 1)
|
||||
self.assertEqual(p2, 2)
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
@_decorator()
|
||||
@@ -45,13 +45,13 @@ class TestDecorator(unittest.TestCase):
|
||||
_args = (1, 2)
|
||||
_kwargs = { 'one': 1, 'two': 2 }
|
||||
|
||||
@wrapt.decorator(param1=1, param2=2)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
self.assertEqual(param1, 3)
|
||||
self.assertEqual(param2, 4)
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1=1, p2=2):
|
||||
self.assertEqual(p1, 3)
|
||||
self.assertEqual(p2, 4)
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
@_decorator(param1=3, param2=4)
|
||||
@_decorator(p1=3, p2=4)
|
||||
def _function(*args, **kwargs):
|
||||
return args, kwargs
|
||||
|
||||
@@ -63,13 +63,13 @@ class TestDecorator(unittest.TestCase):
|
||||
_args = (1, 2)
|
||||
_kwargs = { 'one': 1, 'two': 2 }
|
||||
|
||||
@wrapt.decorator(param1=1, param2=2)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
self.assertEqual(param1, 1)
|
||||
self.assertEqual(param2, 4)
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1=1, p2=2):
|
||||
self.assertEqual(p1, 1)
|
||||
self.assertEqual(p2, 4)
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
@_decorator(param2=4)
|
||||
@_decorator(p2=4)
|
||||
def _function(*args, **kwargs):
|
||||
return args, kwargs
|
||||
|
||||
@@ -77,52 +77,38 @@ class TestDecorator(unittest.TestCase):
|
||||
|
||||
self.assertEqual(result, (_args, _kwargs))
|
||||
|
||||
def test_missing_default_parameter(self):
|
||||
def test_required_parameter_missing(self):
|
||||
def run(*args):
|
||||
@wrapt.decorator(param1=1)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1, p2=2):
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
self.assertRaises(wrapt.exceptions.MissingDefaultParameter,
|
||||
@_decorator()
|
||||
def _function(*args, **kwargs):
|
||||
return args, kwargs
|
||||
|
||||
self.assertRaises(wrapt.exceptions.MissingParameter,
|
||||
run, ())
|
||||
|
||||
def test_unexpected_default_parameters_one(self):
|
||||
def test_unexpected_parameters_one(self):
|
||||
def run(*args):
|
||||
@wrapt.decorator(param1=1, param2=2, param3=4)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1=1, p2=2):
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
self.assertRaises(wrapt.exceptions.UnexpectedDefaultParameters,
|
||||
run, ())
|
||||
|
||||
def test_unexpected_default_parameters_many(self):
|
||||
def run(*args):
|
||||
@wrapt.decorator(param1=1, param2=2, param3=4, param4=4)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
self.assertRaises(wrapt.exceptions.UnexpectedDefaultParameters,
|
||||
run, ())
|
||||
|
||||
def test_unexpected_override_parameters_one(self):
|
||||
def run(*args):
|
||||
@wrapt.decorator(param1=1, param2=2)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
@_decorator(param3=3)
|
||||
@_decorator(p3=3)
|
||||
def _function(*args, **kwargs):
|
||||
return args, kwargs
|
||||
|
||||
self.assertRaises(wrapt.exceptions.UnexpectedParameters, run, ())
|
||||
|
||||
def test_unexpected_override_parameters_many(self):
|
||||
def test_unexpected_parameters_many(self):
|
||||
def run(*args):
|
||||
@wrapt.decorator(param1=1, param2=2)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1=1, p2=2):
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
@_decorator(param3=3, param4=4)
|
||||
@_decorator(p3=3, p4=4)
|
||||
def _function(*args, **kwargs):
|
||||
return args, kwargs
|
||||
|
||||
@@ -132,10 +118,10 @@ class TestDecorator(unittest.TestCase):
|
||||
_args = (1, 2)
|
||||
_kwargs = { 'one': 1, 'two': 2 }
|
||||
|
||||
@wrapt.decorator(param1=1, param2=2)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
self.assertEqual(param1, 3)
|
||||
self.assertEqual(param2, 4)
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1=1, p2=2):
|
||||
self.assertEqual(p1, 3)
|
||||
self.assertEqual(p2, 4)
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
@_decorator(3, 4)
|
||||
@@ -150,10 +136,10 @@ class TestDecorator(unittest.TestCase):
|
||||
_args = (1, 2)
|
||||
_kwargs = { 'one': 1, 'two': 2 }
|
||||
|
||||
@wrapt.decorator(param1=1, param2=2)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
self.assertEqual(param1, 3)
|
||||
self.assertEqual(param2, 2)
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1=1, p2=2):
|
||||
self.assertEqual(p1, 3)
|
||||
self.assertEqual(p2, 2)
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
@_decorator(3)
|
||||
@@ -168,13 +154,13 @@ class TestDecorator(unittest.TestCase):
|
||||
_args = (1, 2)
|
||||
_kwargs = { 'one': 1, 'two': 2 }
|
||||
|
||||
@wrapt.decorator(param1=1, param2=2)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
self.assertEqual(param1, 3)
|
||||
self.assertEqual(param2, 4)
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1=1, p2=2):
|
||||
self.assertEqual(p1, 3)
|
||||
self.assertEqual(p2, 4)
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
@_decorator(3, param2=4)
|
||||
@_decorator(3, p2=4)
|
||||
def _function(*args, **kwargs):
|
||||
return args, kwargs
|
||||
|
||||
@@ -184,8 +170,8 @@ class TestDecorator(unittest.TestCase):
|
||||
|
||||
def test_override_parameters_positional_excess_one(self):
|
||||
def run(*args):
|
||||
@wrapt.decorator(param1=1, param2=2)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1=1, p2=2):
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
@_decorator(3, 4, 5)
|
||||
@@ -196,8 +182,8 @@ class TestDecorator(unittest.TestCase):
|
||||
|
||||
def test_override_parameters_positional_excess_many(self):
|
||||
def run(*args):
|
||||
@wrapt.decorator(param1=1, param2=2)
|
||||
def _decorator(wrapped, instance, args, kwargs, param1, param2):
|
||||
@wrapt.decorator
|
||||
def _decorator(wrapped, instance, args, kwargs, p1=1, p2=2):
|
||||
return wrapped(*args, **kwargs)
|
||||
|
||||
@_decorator(3, 4, 5, 6)
|
||||
|
||||
Reference in New Issue
Block a user