# coding: utf-8
"""Decorators."""
from __future__ import annotations
import time
import functools
import weakref
import inspect
from typing import Callable
from textwrap import dedent
[docs]
def return_straceback_ifexc(func: Callable):
"""
Decorator for functions that are supposed to return a string for logging purposes (e.g. str)
Instead of raising an exception, the decorated function returns a string with the
traceback so that execution can continue.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception:
import traceback
return traceback.format_exc()
return wrapper
[docs]
def timeit(method):
"""
timeit decorator adapted from:
https://medium.com/pythonhive/python-decorator-to-measure-the-execution-time-of-methods-fa04cb6bb36d
sets the timing of the routine as an attribute of the class
"""
def timed(self, *args, **kw):
ts = time.time()
result = method(self, *args, **kw)
te = time.time()
setattr(self,"time_" + method.__name__, (te - ts) * 1000)
return result
return timed
[docs]
def memoized_method(*lru_args, **lru_kwargs):
"""
Implements lru_cache for class methods.
It takes the exact same parameters as lru_cache, and works exactly the same.
However it never passes self to lru_cache and instead uses a per-instance lru_cache.
Taken from: https://stackoverflow.com/questions/33672412/python-functools-lru-cache-with-class-methods-release-object
... example::
@memoized_method(maxsize=12, typed=False)
def method(self, a, b):
....
"""
def decorator(func):
@functools.wraps(func)
def wrapped_func(self, *args, **kwargs):
# We're storing the wrapped method inside the instance. If we had
# a strong reference to self the instance would never die.
self_weak = weakref.ref(self)
@functools.wraps(func)
@functools.lru_cache(*lru_args, **lru_kwargs)
def cached_method(*args, **kwargs):
return func(self_weak(), *args, **kwargs)
setattr(self, func.__name__, cached_method)
return cached_method(*args, **kwargs)
return wrapped_func
return decorator
[docs]
def dump_args(func: Callable):
"""
Decorator to print function call details.
This includes parameters names and effective values.
"""
def wrapper(*args, **kwargs):
func_args = inspect.signature(func).bind(*args, **kwargs).arguments
func_args_str = ", ".join(
"{} = {!r}".format(*item) for item in func_args.items()
)
print(f"{func.__module__}.{func.__qualname__} ( {func_args_str} )")
return func(*args, **kwargs)
return wrapper
[docs]
class Appender:
r"""
A function decorator that appends an addendum to the docstring of the target function.
This decorator should be robust even if func.__doc__ is None
(for example, if -OO was passed to the interpreter).
Usage: construct a docstring.Appender with a string to be joined to
the original docstring. An optional 'join' parameter may be supplied
which will be used to join the docstring and addendum. e.g.
add_copyright = Appender("Copyright (c) 2009", join='\n')
@add_copyright
def my_dog(has='fleas'):
"This docstring will have a copyright below"
pass
MG took it from:
https://github.com/pandas-dev/pandas/blob/3a7f956c30528736beaae5784f509a76d892e229/pandas/util/_decorators.py#L156
MG: Added dedent and debug args.
"""
def __init__(self, addendum, join='', indents=0, dedent=True, debug=False):
if indents > 0:
self.addendum = indent(addendum, indents=indents)
else:
self.addendum = addendum
self.join = join
# MG additional stuff
self.dedent = dedent
self.debug = debug
def __call__(self, func):
func.__doc__ = func.__doc__ if func.__doc__ else ''
self.addendum = self.addendum if self.addendum else ''
if self.dedent:
docitems = [dedent(func.__doc__), dedent(self.addendum)]
else:
docitems = [func.__doc__, self.addendum]
if self.debug:
print("func.__doc__:\n", func.__doc__)
print("addendum:\n", self.addendum)
func.__doc__ = dedent(self.join.join(docitems))
if self.debug:
print("new func.__doc__:\n", func.__doc__)
return func
[docs]
def indent(text: str, indents=1) -> str:
if not text or not isinstance(text, str):
return ''
jointext = ''.join(['\n'] + [' '] * indents)
return jointext.join(text.split('\n'))