From e62438afb0202571a384e295d8d5a4e04a101a20 Mon Sep 17 00:00:00 2001 From: Graham Dumpleton Date: Fri, 9 Aug 2013 23:22:33 +0800 Subject: [PATCH] Hardwire decorator to FunctionWrapper and remove factory layer. --- src/decorators.py | 230 +++++++++++++++++++++------------------------- 1 file changed, 106 insertions(+), 124 deletions(-) diff --git a/src/decorators.py b/src/decorators.py index ca470a7..57e8103 100644 --- a/src/decorators.py +++ b/src/decorators.py @@ -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)