Hardwire decorator to FunctionWrapper and remove factory layer.

This commit is contained in:
Graham Dumpleton
2013-08-09 23:22:33 +08:00
parent d62e7ec1dc
commit e62438afb0

View File

@@ -9,153 +9,135 @@ from .wrappers import FunctionWrapper
from .exceptions import (UnexpectedDefaultParameters,
MissingDefaultParameter, UnexpectedParameters)
def _decorator_factory(wrapper_type):
# This decorator factory serves as a way of parameterising our
# decorators based on the wrapper type. The wrapper type determines
# whether or not descriptor behaviour is supported and the number of
# arguments which are then in turn passed to the wrapper function
# supplied by the user, which implements their decorator. In other
# words, it saves us from duplicating all of this code for the
# different decorator types we provide for constructing the users
# decorators.
WRAPPER_ARGLIST = ('wrapped', 'instance', 'args', 'kwargs')
def _decorator_binder(wrapper=None, target=None, **default_params):
# The binder works out whether the user decorator will have its
# own parameters. Parameters for the user decorator must always
# be specified using keyword arguments and must always have
# defaults. The user cannot use 'wrapper' or 'adapter' for their
# own parameters as we use them ourselves and so they are
# effectively reserved. The 'wrapper' argument being how the
# user's wrapper function is passed in. The 'adapter' argument
# is used to optionally denote a function which is an adapter,
# which changes the effective prototype of the wrapped function.
# The latter is used to ensure that any function argument
# specification returned by the final result of any decorator is
# correct and reflects that of the adapter and not the wrapped
# function.
def decorator(wrapper=None, target=None, **default_params):
# The decorator works out whether the user decorator will have its
# own parameters. Parameters for the user decorator must always
# be specified using keyword arguments and must always have
# defaults. The user cannot use 'wrapper' or 'adapter' for their
# own parameters as we use them ourselves and so they are
# effectively reserved. The 'wrapper' argument being how the
# user's wrapper function is passed in. The 'adapter' argument
# is used to optionally denote a function which is an adapter,
# which changes the effective prototype of the wrapped function.
# The latter is used to ensure that any function argument
# specification returned by the final result of any decorator is
# correct and reflects that of the adapter and not the wrapped
# function.
if 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:
# 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.
expected_arglist = wrapper_type.WRAPPER_ARGLIST
complete_arglist = getargspec(wrapper).args
expected_arglist = WRAPPER_ARGLIST
complete_arglist = getargspec(wrapper).args
received_names = set(default_params.keys())
expected_names = complete_arglist[len(expected_arglist):]
received_names = set(default_params.keys())
expected_names = complete_arglist[len(expected_arglist):]
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, wrapper.__name__))
if received_names:
raise UnexpectedDefaultParameters('Unexpected default '
'parameters %r supplied for decorator %r.' % (
list(received_names), wrapper.__name__))
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, wrapper.__name__))
if received_names:
raise UnexpectedDefaultParameters('Unexpected default '
'parameters %r supplied for decorator %r.' % (
list(received_names), 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 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
# parameters, return a partial wrapper to collect the
# parameters.
if len(complete_arglist) > len(expected_arglist):
# For the case where the decorator is able to accept
# parameters, return a partial wrapper to collect the
# parameters.
@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
# wrapped function is called.
@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
# wrapped function is called.
if len(decorator_args) > len(expected_names):
raise UnexpectedParameters('Expected at most %r '
'positional parameters for decorator %r, '
'but received %r.' % (len(expected_names),
wrapper.__name__, len(decorator_args)))
if len(decorator_args) > len(expected_names):
raise UnexpectedParameters('Expected at most %r '
'positional parameters for decorator %r, '
'but received %r.' % (len(expected_names),
wrapper.__name__, len(decorator_args)))
unexpected_params = []
for name in decorator_kwargs:
if name not in default_params:
unexpected_params.append(name)
unexpected_params = []
for name in decorator_kwargs:
if name not in default_params:
unexpected_params.append(name)
if unexpected_params:
raise UnexpectedParameters('Unexpected parameters '
'%r supplied for decorator %r.' % (
unexpected_params, wrapper.__name__))
if unexpected_params:
raise UnexpectedParameters('Unexpected parameters '
'%r supplied for decorator %r.' % (
unexpected_params, wrapper.__name__))
complete_params = dict(default_params)
complete_params = dict(default_params)
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],
wrapper.__name__))
decorator_kwargs[expected_names[i]] = arg
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],
wrapper.__name__))
decorator_kwargs[expected_names[i]] = arg
complete_params.update(decorator_kwargs)
complete_params.update(decorator_kwargs)
# Now create and return the final wrapper which
# combines the parameters with the wrapped function.
# Now create and return the final wrapper which
# combines the parameters with the wrapped function.
def _wrapper(func):
return wrapper_type(wrapped=func, wrapper=wrapper,
target=target, params=complete_params)
return _wrapper
# Here is where the partial wrapper is returned. This is
# effectively the users decorator.
return _partial
else:
# No parameters so create and return the final wrapper.
# This is effectively the users decorator.
@wraps(wrapper)
def _wrapper(func):
return wrapper_type(wrapped=func, wrapper=wrapper,
target=target)
return FunctionWrapper(wrapped=func, wrapper=wrapper,
target=target, params=complete_params)
return _wrapper
# Here is where the partial wrapper is returned. This is
# effectively the users decorator.
return _partial
else:
# The wrapper still has not been provided, so we are just
# collecting the optional default keyword parameters for the
# users decorator at this point. Return the binder again as
# a partial using the collected default parameters and the
# adapter function if one is being used.
# No parameters so create and return the final wrapper.
# This is effectively the users decorator.
return partial(_decorator_binder, target=target,
**default_params)
@wraps(wrapper)
def _wrapper(func):
return FunctionWrapper(wrapped=func, wrapper=wrapper,
target=target)
return _wrapper
# Override the binder function name to assist debugging.
else:
# The wrapper still has not been provided, so we are just
# collecting the optional default keyword parameters for the
# users decorator at this point. Return the decorator again as
# a partial using the collected default parameters and the
# adapter function if one is being used.
_decorator_binder.__name__ = 'decorator(%s)' % wrapper_type.__name__
return _decorator_binder
def decorator(*args, **kwargs):
return _decorator_factory(FunctionWrapper)(*args, **kwargs)
return partial(decorator, target=target, **default_params)
def adapter(target):
@decorator(target=target)