Source code for bender_hooks

import functools
import inspect


def _get_only_args_spec(f):
    spec = inspect.getargspec(f)
    assert spec.varargs is None, 'func %s cannot contain *args' % f
    assert spec.keywords is None, 'func %s cannot contain ***kwargs' % f
    assert spec.defaults is None, 'func %s cannot contain defaults' % f
    return spec


[docs]def make_decorator(hook_decl, inputs=()): ''' Responsible for turning a hook declaration into a decorator. :param callable hook_decl: The definition that will be turn into a decorator. Example:: def grettings(greet, name): """ Called in scripts that want to print greetings. :param greet: unicode :param name: unicode """ If any code is placed inside the given definition it will be ignored. :type inputs: tuple | list :param inputs: Parameters that must be given to decorator. Example:: dec = bender_hooks.make_decorator(foo, inputs=('alpha', 'bravo')) @dec('A', 'B') def my_func(): pass my_func.inputs['alpha'] == 'A' my_func.inputs['bravo'] == 'B' :rtype: callable :returns: The created decorator. :raises HookError: when given arguments are not valid. ''' hook_spec = _get_only_args_spec(hook_decl) inputs = inputs if type(inputs) in (tuple, list) else [inputs] def make_decorated(f): @functools.wraps(f) def decorated(*args, **kwargs): return f(*args, **kwargs) decorated.hook_name = hook_decl.__name__ decorated.spec = spec = _get_only_args_spec(f) diff_specs = set(spec.args).difference(hook_spec.args) diff_specs.discard('self') if diff_specs: msg = 'function <{name}>: argument names {args} are not valid for '\ 'hook "{hook}"' raise HookError(msg.format(name=f.__name__, args=list(diff_specs), hook=hook_decl.__name__)) return decorated if inputs: def decorator(*args): def inner(f): wrapped = make_decorated(f) wrapped.inputs = dict((k, args[i]) for (i, k) in enumerate(inputs)) return wrapped return inner else: def decorator(f): inner = make_decorated(f) inner.inputs = {} return inner return decorator
[docs]def call(hook, **kwargs): ''' Responsible for invoking given ``hook``. As hooks not necessarily defines all arguments, this function will make sure that the given one will satisfy the ``hook``. Example:: def foo(a, b): """ My decorator definition. """ # Creating decorator. dec = bender_hooks.make_decorator(foo) # Decorating. Only one parameter defined. @foo def bar(b): print(b) # Invoking. bender_hooks.call(bar, a=1) # Invalid: ``a`` is not defined at ``bar`` bender_hooks.call(bar, b=2) # Valid bender_hooks.call(bar, c=3) # Invalid: ``c`` is not defined at ``bar`` neither ``foo`` bender_hooks.call(bar, a=1, b=2) # Valid: ``a`` is defined at ``foo`` but will be ignored. bender_hooks.call(bar, b=2, c=3) # Invalid bender_hooks.call(bar, a=1, b=2, c=3) # Invalid :param callable hook: Already decorated function. :returns: It depends on what will be returned by given ``hook``. :raises HookError: if given ``hook`` is not decorated. ''' if not hasattr(hook, 'hook_name'): raise HookError('%s is not a hook' % hook) if hook.spec.args: accepts_kwargs = set(hook.spec.args).intersection(kwargs) new_kwargs = dict((k, kwargs[k]) for k in accepts_kwargs) else: new_kwargs = {} return hook(**new_kwargs)
[docs]def find_hooks(obj, hook_name): ''' Responsible for search at ``obj`` hooks with the given ``hook_name``. :param obj: Object (can be a module or instance) to search for hooks. :param unicode hook_name: Name of hook to search. :rtype: list(callable) :returns: All hooks found into the given ``obj``. ''' result = [] for name in dir(obj): value = getattr(obj, name) if getattr(value, 'hook_name', None) == hook_name: result.append(value) return result
[docs]def call_all_hooks(obj, hook_name, **kwargs): ''' Responsible for search and invoke all hooks with the given ``hook_name`` under ``obj``. .. seealso:: :func:`.find_hooks` .. seealso:: :func:`.call` ''' for hook in find_hooks(obj, hook_name): call(hook, **kwargs) return None
[docs]def call_unique_hook(obj, hook_name, **kwargs): ''' Responsible for search and invoke a hook with the given ``hook_name`` under ``obj``, making sure that only one hook exists. :raises HookError: if more than one hooks are found. .. seealso:: :func:`.find_hooks` .. seealso:: :func:`.call` ''' found = find_hooks(obj, hook_name) if len(found) > 1: raise HookError( '%s can implement %s at most one time' % (obj, hook_name)) elif len(found) == 1: return call(found[0], **kwargs)
[docs]class HookError(RuntimeError): ''' Error raised for expected and known cases of |bh|. '''