Source code for abipy.tools.decorators

# 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'))