Source code for quantify_core.utilities.deprecation
# Repository: https://gitlab.com/quantify-os/quantify-core
# Licensed according to the LICENCE file on the main branch
"""Utilities used to maintain deprecation and reverse-compatibility of the code."""
import functools
import inspect
import os
import warnings
from typing import Callable, Type, Union
[docs]
def _find_stack_level() -> int:
"""
Find the first place in the stack that is not inside quantify-core
(tests notwithstanding).
(adopted from pandas.util._exceptions.find_stack_level)
"""
# pylint: disable=import-outside-toplevel,invalid-name
import quantify_core
pkg_dir = os.path.dirname(quantify_core.__file__)
test_dir = os.path.join(pkg_dir, "tests")
# https://stackoverflow.com/questions/17407119/python-inspect-stack-is-slow
frame = inspect.currentframe()
n = 0
while frame:
fname = inspect.getfile(frame)
if fname.startswith(pkg_dir) and not fname.startswith(test_dir):
frame = frame.f_back
n += 1
else:
break
return n
[docs]
def deprecated(
drop_version: str, message_or_alias: Union[str, Callable]
) -> Callable[[Callable], Callable]:
"""A decorator for deprecating classes and methods.
For each deprecation we must provide a version when this function or class will be
removed completely and an instruction to a user about how to port their existing
code to a new software version. This is easily done using this decorator.
If callable is passed instead of a message, this decorator assumes that
the function or class has moved to another module and generates the standard
instruction to use a new function or class. There is no need to re-implement
the function logic in two places, since the implementation of new function or class
is used in both new and old aliases.
.. seealso:: :ref:`howto-utilities-deprecation`
Parameters
----------
drop_version
A version of the package when the deprecated function or class will be dropped.
message_or_alias
Either an instruction about how to port the software to a new version without
the usage of deprecated calls (string), or the new drop-in replacement to the
deprecated class or function (callable).
"""
def deprecator(func_or_class: Union[Callable, Type]) -> Union[Callable, Type]:
old_module = inspect.getmodule(func_or_class)
if old_module is None:
raise RuntimeError("Could not determine module of the deprecated object.")
# We assume that the package name and the first part of module name are
# the same, but package name has - instead of _, for example if root module name
# is "quantify_core", package name is assumed to be "quantify-core".
package = old_module.__name__.split(".", 1)[0].replace("_", "-")
maybe_brackets = "" if isinstance(func_or_class, type) else "()"
if callable(message_or_alias):
new_module = inspect.getmodule(message_or_alias)
if new_module is None:
raise RuntimeError("Could not determine module of the moved object.")
instruction = (
f"Use {new_module.__name__}.{message_or_alias.__qualname__}"
f"{maybe_brackets} instead."
)
else:
instruction = message_or_alias
message = (
f"{'Class' if isinstance(func_or_class, type) else 'Function'} "
f"{old_module.__name__}.{func_or_class.__qualname__}{maybe_brackets} is "
f"deprecated and will be removed in {package}-{drop_version}. {instruction}"
)
if isinstance(func_or_class, type):
if isinstance(message_or_alias, type):
metaclass = type(message_or_alias)
cls = metaclass(
func_or_class.__name__,
message_or_alias.__bases__,
dict(message_or_alias.__dict__),
)
else:
cls = func_or_class
orig_init = cls.__init__ # type: ignore
@functools.wraps(orig_init)
def __init__(self, *args, **kwargs):
warnings.warn(message, FutureWarning, stacklevel=_find_stack_level())
orig_init(self, *args, **kwargs)
# Here we patch only __init__ method. For completeness, we should also patch
# all the staticmethods and classmethods, but let it be left for the future,
# if someone really needs it implemented.
cls.__init__ = __init__ # type: ignore
return cls
if callable(message_or_alias):
func = message_or_alias
else:
func = func_or_class
@functools.wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(message, FutureWarning, stacklevel=_find_stack_level())
return func(*args, **kwargs)
return wrapper
return deprecator