Updated decorator to 4.4.2

This commit is contained in:
Labrys of Knossos 2022-12-01 17:34:33 -05:00
commit 5e3641ac23

View file

@ -1,6 +1,6 @@
# ######################### LICENSE ############################ # # ######################### LICENSE ############################ #
# Copyright (c) 2005-2021, Michele Simionato # Copyright (c) 2005-2018, Michele Simionato
# All rights reserved. # All rights reserved.
# Redistribution and use in source and binary forms, with or without # Redistribution and use in source and binary forms, with or without
@ -28,26 +28,55 @@
# DAMAGE. # DAMAGE.
""" """
Decorator module, see Decorator module, see http://pypi.python.org/pypi/decorator
https://github.com/micheles/decorator/blob/master/docs/documentation.md
for the documentation. for the documentation.
""" """
from __future__ import print_function
import re import re
import sys import sys
import inspect import inspect
import operator import operator
import itertools import itertools
from contextlib import _GeneratorContextManager import collections
from inspect import getfullargspec, iscoroutinefunction, isgeneratorfunction
__version__ = '4.4.2'
if sys.version_info >= (3,):
from inspect import getfullargspec
def get_init(cls):
return cls.__init__
else:
FullArgSpec = collections.namedtuple(
'FullArgSpec', 'args varargs varkw defaults '
'kwonlyargs kwonlydefaults annotations')
def getfullargspec(f):
"A quick and dirty replacement for getfullargspec for Python 2.X"
return FullArgSpec._make(inspect.getargspec(f) + ([], None, {}))
def get_init(cls):
return cls.__init__.__func__
try:
iscoroutinefunction = inspect.iscoroutinefunction
except AttributeError:
# let's assume there are no coroutine functions in old Python
def iscoroutinefunction(f):
return False
try:
from inspect import isgeneratorfunction
except ImportError:
# assume no generator function in old Python versions
def isgeneratorfunction(caller):
return False
__version__ = '5.1.1'
DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(')
POS = inspect.Parameter.POSITIONAL_OR_KEYWORD
EMPTY = inspect.Parameter.empty
# this is not used anymore in the core, but kept for backward compatibility # basic functionality
class FunctionMaker(object): class FunctionMaker(object):
""" """
An object with the ability to create functions with a given signature. An object with the ability to create functions with a given signature.
@ -71,7 +100,7 @@ class FunctionMaker(object):
self.name = '_lambda_' self.name = '_lambda_'
self.doc = func.__doc__ self.doc = func.__doc__
self.module = func.__module__ self.module = func.__module__
if inspect.isroutine(func): if inspect.isfunction(func):
argspec = getfullargspec(func) argspec = getfullargspec(func)
self.annotations = getattr(func, '__annotations__', {}) self.annotations = getattr(func, '__annotations__', {})
for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs',
@ -114,9 +143,7 @@ class FunctionMaker(object):
raise TypeError('You are decorating a non function: %s' % func) raise TypeError('You are decorating a non function: %s' % func)
def update(self, func, **kw): def update(self, func, **kw):
""" "Update the signature of func with the data in self"
Update the signature of func with the data in self
"""
func.__name__ = self.name func.__name__ = self.name
func.__doc__ = getattr(self, 'doc', None) func.__doc__ = getattr(self, 'doc', None)
func.__dict__ = getattr(self, 'dict', {}) func.__dict__ = getattr(self, 'dict', {})
@ -133,9 +160,7 @@ class FunctionMaker(object):
func.__dict__.update(kw) func.__dict__.update(kw)
def make(self, src_templ, evaldict=None, addsource=False, **attrs): def make(self, src_templ, evaldict=None, addsource=False, **attrs):
""" "Make a new function from a given template and update the signature"
Make a new function from a given template and update the signature
"""
src = src_templ % vars(self) # expand name and signature src = src_templ % vars(self) # expand name and signature
evaldict = evaldict or {} evaldict = evaldict or {}
mo = DEF.search(src) mo = DEF.search(src)
@ -196,128 +221,106 @@ class FunctionMaker(object):
return self.make(body, evaldict, addsource, **attrs) return self.make(body, evaldict, addsource, **attrs)
def fix(args, kwargs, sig): def decorate(func, caller, extras=()):
""" """
Fix args and kwargs to be consistent with the signature decorate(func, caller) decorates a function using a caller.
If the caller is a generator function, the resulting function
will be a generator function.
""" """
ba = sig.bind(*args, **kwargs) evaldict = dict(_call_=caller, _func_=func)
ba.apply_defaults() # needed for test_dan_schult es = ''
return ba.args, ba.kwargs for i, extra in enumerate(extras):
ex = '_e%d_' % i
evaldict[ex] = extra
es += ex + ', '
if '3.5' <= sys.version < '3.6':
def decorate(func, caller, extras=(), kwsyntax=False): # with Python 3.5 isgeneratorfunction returns True for all coroutines
""" # however we know that it is NOT possible to have a generator
Decorates a function/generator/coroutine using a caller. # coroutine in python 3.5: PEP525 was not there yet
If kwsyntax is True calling the decorated functions with keyword generatorcaller = isgeneratorfunction(
syntax will pass the named arguments inside the ``kw`` dictionary, caller) and not iscoroutinefunction(caller)
even if such argument are positional, similarly to what functools.wraps
does. By default kwsyntax is False and the the arguments are untouched.
"""
sig = inspect.signature(func)
if iscoroutinefunction(caller):
async def fun(*args, **kw):
if not kwsyntax:
args, kw = fix(args, kw, sig)
return await caller(func, *(extras + args), **kw)
elif isgeneratorfunction(caller):
def fun(*args, **kw):
if not kwsyntax:
args, kw = fix(args, kw, sig)
for res in caller(func, *(extras + args), **kw):
yield res
else: else:
def fun(*args, **kw): generatorcaller = isgeneratorfunction(caller)
if not kwsyntax: if generatorcaller:
args, kw = fix(args, kw, sig) fun = FunctionMaker.create(
return caller(func, *(extras + args), **kw) func, "for res in _call_(_func_, %s%%(shortsignature)s):\n"
fun.__name__ = func.__name__ " yield res" % es, evaldict, __wrapped__=func)
fun.__doc__ = func.__doc__ else:
fun.__wrapped__ = func fun = FunctionMaker.create(
fun.__signature__ = sig func, "return _call_(_func_, %s%%(shortsignature)s)" % es,
evaldict, __wrapped__=func)
if hasattr(func, '__qualname__'):
fun.__qualname__ = func.__qualname__ fun.__qualname__ = func.__qualname__
# builtin functions like defaultdict.__setitem__ lack many attributes
try:
fun.__defaults__ = func.__defaults__
except AttributeError:
pass
try:
fun.__kwdefaults__ = func.__kwdefaults__
except AttributeError:
pass
try:
fun.__annotations__ = func.__annotations__
except AttributeError:
pass
try:
fun.__module__ = func.__module__
except AttributeError:
pass
try:
fun.__dict__.update(func.__dict__)
except AttributeError:
pass
return fun return fun
def decoratorx(caller): def decorator(caller, _func=None):
""" """decorator(caller) converts a caller function into a decorator"""
A version of "decorator" implemented via "exec" and not via the
Signature object. Use this if you are want to preserve the `.__code__`
object properties (https://github.com/micheles/decorator/issues/129).
"""
def dec(func):
return FunctionMaker.create(
func,
"return _call_(_func_, %(shortsignature)s)",
dict(_call_=caller, _func_=func),
__wrapped__=func, __qualname__=func.__qualname__)
return dec
def decorator(caller, _func=None, kwsyntax=False):
"""
decorator(caller) converts a caller function into a decorator
"""
if _func is not None: # return a decorated function if _func is not None: # return a decorated function
# this is obsolete behavior; you should use decorate instead # this is obsolete behavior; you should use decorate instead
return decorate(_func, caller, (), kwsyntax) return decorate(_func, caller)
# else return a decorator function # else return a decorator function
sig = inspect.signature(caller) defaultargs, defaults = '', ()
dec_params = [p for p in sig.parameters.values() if p.kind is POS] if inspect.isclass(caller):
name = caller.__name__.lower()
def dec(func=None, *args, **kw): doc = 'decorator(%s) converts functions/generators into ' \
na = len(args) + 1 'factories of %s objects' % (caller.__name__, caller.__name__)
extras = args + tuple(kw.get(p.name, p.default) elif inspect.isfunction(caller):
for p in dec_params[na:] if caller.__name__ == '<lambda>':
if p.default is not EMPTY) name = '_lambda_'
if func is None:
return lambda func: decorate(func, caller, extras, kwsyntax)
else: else:
return decorate(func, caller, extras, kwsyntax) name = caller.__name__
dec.__signature__ = sig.replace(parameters=dec_params) doc = caller.__doc__
dec.__name__ = caller.__name__ nargs = caller.__code__.co_argcount
dec.__doc__ = caller.__doc__ ndefs = len(caller.__defaults__ or ())
dec.__wrapped__ = caller defaultargs = ', '.join(caller.__code__.co_varnames[nargs-ndefs:nargs])
dec.__qualname__ = caller.__qualname__ if defaultargs:
dec.__kwdefaults__ = getattr(caller, '__kwdefaults__', None) defaultargs += ','
dec.__dict__.update(caller.__dict__) defaults = caller.__defaults__
else: # assume caller is an object with a __call__ method
name = caller.__class__.__name__.lower()
doc = caller.__call__.__doc__
evaldict = dict(_call=caller, _decorate_=decorate)
dec = FunctionMaker.create(
'%s(func, %s)' % (name, defaultargs),
'if func is None: return lambda func: _decorate_(func, _call, (%s))\n'
'return _decorate_(func, _call, (%s))' % (defaultargs, defaultargs),
evaldict, doc=doc, module=caller.__module__, __wrapped__=caller)
if defaults:
dec.__defaults__ = (None,) + defaults
return dec return dec
# ####################### contextmanager ####################### # # ####################### contextmanager ####################### #
try: # Python >= 3.2
from contextlib import _GeneratorContextManager
except ImportError: # Python >= 2.5
from contextlib import GeneratorContextManager as _GeneratorContextManager
class ContextManager(_GeneratorContextManager): class ContextManager(_GeneratorContextManager):
def __init__(self, g, *a, **k):
_GeneratorContextManager.__init__(self, g, a, k)
def __call__(self, func): def __call__(self, func):
def caller(f, *a, **k): """Context manager decorator"""
with self.__class__(self.func, *self.args, **self.kwds): return FunctionMaker.create(
return f(*a, **k) func, "with _self_: return _func_(%(shortsignature)s)",
return decorate(func, caller) dict(_self_=self, _func_=func), __wrapped__=func)
init = getfullargspec(_GeneratorContextManager.__init__)
n_args = len(init.args)
if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7
def __init__(self, g, *a, **k):
return _GeneratorContextManager.__init__(self, g(*a, **k))
ContextManager.__init__ = __init__
elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4
pass
elif n_args == 4: # (self, gen, args, kwds) Python 3.5
def __init__(self, g, *a, **k):
return _GeneratorContextManager.__init__(self, g, a, k)
ContextManager.__init__ = __init__
_contextmanager = decorator(ContextManager) _contextmanager = decorator(ContextManager)