Source code for ase2sprkkr.input_parameters.test.decorators

""" Various decorators, mainly for class methods """
import functools
import inspect
import heapq
import asyncio
import warnings
import sys
from . import backward_compatibility # NOQA
from functools import cache          # NOQA


[docs] class cached_class_property: """ Decorator that converts a method with a single cls argument into a property that can be accessed directly from the class. The value is computed only once. Example ------- .. code-block:: class Cls: @class_property def cls_property(): return some_cached_value @class_property def another_cls_property(cls): return another_cached_value x = Cls.cls_property """
[docs] def __init__(self, method=None): self.fget = method functools.update_wrapper(self, method)
def __set_name__(self, owner, name): self.owner = owner def __get__(self, instance, cls=None): if self.fget.__code__.co_argcount: out = self.fget(cls) owner = cls else: out = self.fget() owner = self.owner setattr(owner, self.fget.__name__, out) return out
if hasattr(functools, 'cached_property'): std_cached_property = functools.cached_property """ Functools.cached_property decorator - the value is computed only once and then stored as an (same-name) instance attribute. You can delete the attribute to invalidate the cache. Earlier versions of python do not have this decorator, so for these versions is implemented below, otherwise it is taken from functools. """ else: # https://github.com/pydanny/cached-property/blob/master/cached_property.py class std_cached_property(object): """ A property that is only computed once per instance and then replaces itself with an ordinary attribute. Deleting the attribute resets the property. Source: `https://github.com/bottlepy/bottle/commit/fa7733e075da0d790d809aa3d2f53071897e6f76` """ # noqa def __init__(self, func): self.__doc__ = getattr(func, "__doc__") self.func = func def __get__(self, obj, cls): if obj is None: return self if asyncio and asyncio.iscoroutinefunction(self.func): return self._wrap_in_coroutine(obj) value = obj.__dict__[self.attrname] = self.func(obj) return value def __set_name__(self, owner, name): # for Py3.7, we need to use attrname and not self.func.__name__ # since in complex cases when nested decorators are used, # this information can be lost self.attrname = name def _wrap_in_coroutine(self, obj): @functools.wraps(obj) @asyncio.coroutine def wrapper(): future = asyncio.ensure_future(self.func(obj)) obj.__dict__[self.attrname] = future return future return wrapper()
[docs] class cached_property(std_cached_property): """ Cached property, that allows setter and deleter """
[docs] def __init__(self, func, fset=None, fdel=None): super().__init__(func) self.fset = fset self.fdel = fdel
def __set__(self, obj, value): if self.fset: self.fset(obj, value) else: obj.__dict__[self.attrname] = value def __delete__(self, obj): if self.fdel: self.fdel(obj) else: del obj.__dict__[self.attrname]
[docs] def setter(self, fset): return type(self)(self.func, fset, self.fdel)
[docs] def deleter(self, fdel): return type(self)(self.func, self.fset, fdel)
# for some reason, in 3.7 and lower the attribute in the dict do not override the __get__ # in subclasses if sys.version_info < (3,8): def __get__(self, obj, cls): if self.attrname in obj.__dict__: return obj.__dict__[self.attrname] return super().__get__(obj, cls)
[docs] class class_property: """ Decorator that converts a method with a single cls argument into a property that can be accessed directly from the class. The value is not cached, see :func:`cached_class_property`, if caching is desirable. Example ------- .. code-block:: class Cls: @class_property def cls_property(): return some_value x = Cls.cls_property """
[docs] def __init__(self, method=None): self.fget = method
def __get__(self, instance, cls=None): return self.fget(cls)
[docs] def add_to_signature(func, prepend=False, excluding=None, kwargs=False): """ Add the arguments in the ``func`` function to the list of arguments of the resulting function (as keyword_only arguments) The modified function has to have its arguments defined as in the following example: .. code-block:: def parent(.....): .... @add_to_signature(parent) def child(new_param, *args, **kwargs): print(f'New param is {new_param}) parent(*args, **kwargs) Parameters ---------- func A function, whose parameters will be added to the resulting function signature. prepend If True, the new positional arguments will be the first. If False, they will be after the parents positional arguments. The same-named arguments can be given as keyword-only - then they just alter the properties of the "old" arguments, retaining their position. If they are given as regular ones, they takes their new position, too. excluding Do not add arguments of this name(s) kwargs The resulting function will have kwargs argument """ if not excluding: excluding=[] elif isinstance(excluding, str): excluding=[ excluding ] signature = inspect.signature(func) pars = list( signature.parameters.values()) P = inspect.Parameter arg_ar = _arg_kw = None for i in pars: if i.kind == P.VAR_KEYWORD: _arg_kw = i.name elif i.kind == P.VAR_POSITIONAL: arg_ar = i.name def modify(mod_func): new_sig = inspect.signature(mod_func) new_pars = new_sig.parameters.values() arg_kw = _arg_kw if kwargs: for i in new_pars: if i.kind == P.VAR_KEYWORD: arg_kw = i.name break # remove args/kwargs from the childs argument new_pars = { i.name:i for i in new_pars if (i.kind != P.VAR_KEYWORD or kwargs ) and i.kind != P.VAR_POSITIONAL } used = set() def use(i): nonlocal used used.add(i.name) return i if prepend: old_names = set((i.name for i in pars if i.name not in excluding)) old_pars = [ use(i) for i in new_pars.values() if i.kind != P.KEYWORD_ONLY or i.name not in old_names ] def add_pars(): for i in pars: if i in excluding: continue if i.name not in new_pars: yield i else: n=new_pars[i.name] if n.kind != P.KEYWORD_ONLY: continue # keyword only parameters retain the old position, taken new default and annotation P(i.name, i.kind, default=n.default, annotation=n.annotation) yield n add_pars = [ i for i in add_pars() ] else: old_pars = [ use(new_pars[i.name]) if i.name in new_pars else i for i in pars if i.name not in excluding ] add_pars = [ i for i in new_pars.values() if i.name not in used ] # the easy and fast way - only keyword arguments are present, the function can be called as is, # just make its signature pretty for i in add_pars: if i.kind != P.KEYWORD_ONLY: break else: result = list(heapq.merge(old_pars, add_pars, key = lambda x: x.kind)) result = new_sig.replace(parameters = result) mod_func.__signature__ = result return mod_func result = heapq.merge(old_pars, add_pars, key = lambda x: x.kind) result = list(result) result=new_sig.replace(parameters = result) @functools.wraps(mod_func) def wrapper(*args, **kwargs): ba=result.bind(*args, **kwargs) ba.apply_defaults() nargs = [] nkwargs = {} for k,v in ba.arguments.items(): if k==arg_kw: nkwargs.update(v) elif k==arg_ar: nargs.extend(v) elif k in new_pars and new_pars[k].kind != P.KEYWORD_ONLY: nargs.append(v) else: nkwargs[k]=v return mod_func(*nargs, **nkwargs) wrapper.__signature__ = result return wrapper return modify
[docs] def add_called_class_as_argument(decorator): """ If a decorator is used on a method, the information about the defining class is lost. As a consequence, the execution of ``super()....`` failed. This decorator can be applied to another decorator: it will add the defining class as a first argument of the resulting function. The implementation is very tricky - function-style decorator is not able to handle this situation, the __set_name__ special method (in class-style decorator) has to be utilized. Moreover, class style decorator lose 'self' from decorated method, thus, the self has to be recovered using descriptor protocol. To speed up the whole thing, the first access replaces the class-style decorator class with generated method (that can already know - and knows - everything needed) Example >>> @add_called_class_as_argument ... def decorator(func): ... ... def wrapped(cls, self): ... super(cls, self).call() ... func(self) ... return wrapped ... >>> class A: ... def call(self): ... print('A', end='') ... >>> class B(A): ... @decorator ... def call(self): ... print('B', end='') ... >>> B().call() AB """ class AddCalledClassAsArgument: def __init__(self, function): self.func = function def __set_name__(self, owner, name): """ Catch the defining class """ self.cls=owner self.name=name def __call__(self, instance, *args, **kwargs): func = self.func cls = self.cls @functools.wraps(func) def wrapped(self, *args, **kwargs): return func(cls, self, *args, **kwargs) setattr(cls, self.name, wrapped) return wrapped(instance, *args, **kwargs) def __get__(self, instance, instancetype): """ Descriptor method - via this method the actual calling instance (self of the very resulting method) is obtained. """ out = functools.partial(self.__call__, instance) return functools.update_wrapper(out, self.func) def wrapper(function): function = decorator(function) return AddCalledClassAsArgument(function) return wrapper
[docs] def warnings_from_here(stacklevel=1): stacklevel+=1 def wrapper(func): @functools.wraps(func) def wrapped(*args, **kwargs): with warnings.catch_warnings(record = True) as warning_list: result = func(*args, **kwargs) for warning in warning_list: warnings.warn(warning.message, warning.category, stacklevel =stacklevel) return result return wrapped return wrapper
[docs] class maybeclassmethod(object): """ This decorator creates a method, that can behawes both as classmethod and normal method - it provides both self and cls, when self can be None """
[docs] def __init__(self, method): self.method = method self.wrapper = functools.wraps(method)
def __get__(self, instance, cls): return self.wrapper( lambda *args, **kw: self.method(instance, cls, *args, **kw) )