Source code for xotl.tools.decorator.meta
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------
# Copyright (c) Merchise Autrement [~º/~] and Contributors
# All rights reserved.
#
# This is free software; you can do what the LICENCE file allows you to.
#
"""Decorator-making facilities.
This module provides a signature-keeping version of the
`xotl.tools.decorators.decorator`:func:, which is now deprecated in favor of
this module's version.
We scinded the decorator-making facilities from decorators per se to allow the
module `xotl.tools.deprecation`:mod: to be used by decorators and at the same
time, implement the decorator `~xotl.tools.deprecation.deprecated`:func: more
easily.
"""
from functools import partial, wraps
from types import FunctionType as function
__all__ = ("decorator",)
[docs]def decorator(caller):
"""Eases the creation of decorators with arguments. Normally a decorator
with arguments needs three nested functions like this::
def decorator(*decorator_arguments):
def real_decorator(target):
def inner(*args, **kwargs):
return target(*args, **kwargs)
return inner
return real_decorator
This decorator reduces the need of the first level by comprising both into
a single function definition. However it does not removes the need for an
``inner`` function::
>>> @decorator
... def plus(target, value):
... from functools import wraps
... @wraps(target)
... def inner(*args):
... return target(*args) + value
... return inner
>>> @plus(10)
... def ident(val):
... return val
>>> ident(1)
11
A decorator with default values for all its arguments (except, of course,
the first one which is the decorated `target`) may be invoked
without parenthesis::
>>> @decorator
... def plus2(func, value=1, missing=2):
... from functools import wraps
... @wraps(func)
... def inner(*args):
... print(missing)
... return func(*args) + value
... return inner
>>> @plus2
... def ident2(val):
... return val
>>> ident2(10)
2
11
But (if you like) you may place the parenthesis::
>>> @plus2()
... def ident3(val):
... return val
>>> ident3(10)
2
11
However, this is not for free, you cannot pass a single positional argument
which type is a function::
>>> def p():
... print('This is p!!!')
>>> @plus2(p) # doctest: +ELLIPSIS
... def dummy():
... print('This is dummy')
Traceback (most recent call last):
...
TypeError: p() takes ...
The workaround for this case is to use a keyword argument.
"""
@wraps(caller)
def outer_decorator(*args, **kwargs):
try:
from zope.interface import Interface
except ImportError:
Interface = None
# from xotl.tools.symbols import Unset as Interface
if (
len(args) == 1
and not kwargs
and (
isinstance(args[0], (function, type)) or issubclass(type(args[0]), type(Interface))
)
):
# This tries to solve the case of missing () on the decorator::
#
# @decorator
# def somedec(func, *args, **kwargs)
# ...
#
# @somedec
# def decorated(*args, **kwargs):
# pass
#
# Notice, however, that this is not general enough, since we try
# to avoid inspecting the calling frame to see if the () are in
# place.
func = args[0]
return caller(func)
elif len(args) > 0 or len(kwargs) > 0:
def _decorator(func):
return partial(caller, **kwargs)(*((func,) + args))
return _decorator
else:
return caller
return outer_decorator