Source code for argopy.utils.decorators

from functools import wraps
import warnings
import logging
from typing import List


log = logging.getLogger("argopy.utils.decorators")


class DocInherit(object):
    """Docstring inheriting method descriptor

    The class itself is also used as a decorator

    Usage:

    class Foo(object):
        def foo(self):
            "Frobber"
            pass

    class Bar(Foo):
        @doc_inherit
        def foo(self):
            pass

    Now, Bar.foo.__doc__ == Bar().foo.__doc__ == Foo.foo.__doc__ == "Frobber"

    src: https://code.activestate.com/recipes/576862/
    """

    def __init__(self, mthd):
        self.mthd = mthd
        self.name = mthd.__name__

    def __get__(self, obj, cls):
        if obj:
            return self.get_with_inst(obj, cls)
        else:
            return self.get_no_inst(cls)

    def get_with_inst(self, obj, cls):
        overridden = getattr(super(cls, obj), self.name, None)

        @wraps(self.mthd, assigned=("__name__", "__module__"))
        def f(*args, **kwargs):
            return self.mthd(obj, *args, **kwargs)

        return self.use_parent_doc(f, overridden)

    def get_no_inst(self, cls):
        for parent in cls.__mro__[1:]:
            overridden = getattr(parent, self.name, None)
            if overridden:
                break

        @wraps(self.mthd, assigned=("__name__", "__module__"))
        def f(*args, **kwargs):
            return self.mthd(*args, **kwargs)

        return self.use_parent_doc(f, overridden)

    def use_parent_doc(self, func, source):
        if source is None:
            raise NameError("Can't find '%s' in parents" % self.name)
        func.__doc__ = source.__doc__
        return func


doc_inherit = DocInherit


[docs] def deprecated(reason: str = None, version: str = None, ignore_caller: List = []): """Deprecation warning decorator This is a decorator which can be used to mark functions as deprecated. It will result in a warning being emitted when the function is used. Parameters ---------- reason: str, optional, default=None Text message to send with deprecation warning version: str, optional, default=None ignore_caller: List, optional, default=[] Examples -------- The @deprecated can be used with a 'reason' and a 'version' .. code-block:: python @deprecated("please, use another function", version='0.2') def old_function(x, y): pass or without: .. code-block:: python @deprecated def old_function(x, y): pass The @deprecated can also be ignored from specific callers. .. code-block:: python @deprecated("please, use another function", version='0.2', ignore_caller='postprocessing') def old_function(x, y): pass References ---------- This decorator is largely inspired by https://stackoverflow.com/a/40301488 """ import inspect ignore_caller = [ignore_caller] if isinstance(reason, str): def decorator(func): if inspect.isclass(func): fmt = "\nCall to deprecated class '{name}' ({reason})" else: fmt = "\nCall to deprecated function '{name}' ({reason})" if version is not None: fmt = "%s -- Deprecated since version {version}" % fmt @wraps(func) def new_func(*args, **kwargs): raise_deprec = True stack = inspect.stack() for s in stack: if "<module>" in s.function: break elif s.function in ignore_caller: raise_deprec = False if raise_deprec: warnings.simplefilter("always", DeprecationWarning) warnings.warn( fmt.format(name=func.__qualname__, reason=reason, version=version), category=DeprecationWarning, stacklevel=2, ) warnings.simplefilter("default", DeprecationWarning) else: log.warning(fmt.format(name=func.__qualname__, reason=reason, version=version)) return func(*args, **kwargs) return new_func return decorator elif inspect.isclass(reason) or inspect.isfunction(reason): func = reason if inspect.isclass(func): fmt = "\nCall to deprecated class '{name}'." else: fmt = "\nCall to deprecated function '{name}'." @wraps(func) def new_func(*args, **kwargs): raise_deprec = True stack = inspect.stack() for s in stack: if "<module>" in s.function: break elif s.function in ignore_caller: raise_deprec = False if raise_deprec: warnings.simplefilter("always", DeprecationWarning) warnings.warn( fmt.format(name=func.__qualname__), category=DeprecationWarning, stacklevel=2, ) warnings.simplefilter("default", DeprecationWarning) else: log.warning(fmt.format(name=func.__qualname__, reason=reason)) return func(*args, **kwargs) return new_func else: raise TypeError(repr(type(reason)))
class AccessorRegistrationWarning(Warning): """Warning for conflicts in accessor registration. Disclosure ---------- This class was copied from [xarray](https://github.com/pydata/xarray/blob/main/xarray/core/extensions.py) under Apache License 2.0 """ class _CachedAccessor: """Custom property-like object (descriptor) for caching accessors. Disclosure ---------- This class was copied from [xarray](https://github.com/pydata/xarray/blob/main/xarray/core/extensions.py) under Apache License 2.0 """ def __init__(self, name, accessor): self._name = name self._accessor = accessor def __get__(self, obj, cls): if obj is None: # we're accessing the attribute of the class, i.e., Dataset.argo.canyon return self._accessor # Use the same dict as @pandas.util.cache_readonly. # It must be explicitly declared in obj.__slots__. try: cache = obj._cache except AttributeError: cache = obj._cache = {} try: return cache[self._name] except KeyError: pass try: accessor_obj = self._accessor(obj) except AttributeError: # __getattr__ on data object will swallow any AttributeErrors # raised when initializing the accessor, so we need to raise as # something else (GH933): raise RuntimeError(f"error initializing {self._name!r} accessor.") cache[self._name] = accessor_obj return accessor_obj def register_accessor(name, cls): def decorator(accessor): if hasattr(cls, name): warnings.warn( f"registration of accessor {accessor!r} under name {name!r} for type {cls!r} is " "overriding a preexisting attribute with the same name.", AccessorRegistrationWarning, stacklevel=2, ) setattr(cls, name, _CachedAccessor(name, accessor)) return accessor return decorator