Source code for tensorbay.utility.deprecated

#!/usr/bin/env python3
#
# Copyright 2021 Graviti. Licensed under MIT License.
#

"""Basic concepts of related deprecated functions."""

import inspect
import warnings
from functools import wraps
from typing import Any, Callable, Optional, Tuple, TypeVar, Union

_Callable = TypeVar("_Callable", bound=Callable[..., Any])


[docs]class Deprecated: """A decorator for deprecated functions. Arguments: since: The version the function is deprecated. removed_in: The version the function will be removed in. substitute: The substitute function. """ def __init__( self, *, since: str, removed_in: Optional[str] = None, substitute: Union[None, str, Callable[..., Any]] = None, ) -> None: self._since = since self._removed_in = removed_in if not substitute: self._substitute = None self._meth = None elif callable(substitute): self._substitute = substitute.__qualname__ self._meth = f":meth:`~{substitute.__module__}.{substitute.__qualname__}`" else: self._substitute = substitute.rsplit(".", 1)[-1] self._meth = f":meth:`~{substitute}`" def __call__(self, func: _Callable) -> _Callable: """Wrap the decorated function by adding the deprecated message. Arguments: func: The deprecated function. Returns: The wrapped function which shows the deprecated message when calling. """ messages = [f'Function "{func.__name__}" is deprecated since version {self._since}.'] if self._removed_in: messages.append(f'It will be removed in version "{self._removed_in}".') if self._substitute: messages.append(f'Use "{self._substitute}" instead.') message = " ".join(messages) @wraps(func) def wrapper(*arg: Any, **kwargs: Any) -> Any: warnings.warn(message, DeprecationWarning, 2) return func(*arg, **kwargs) wrapper.__doc__ = self._update_docstring(wrapper.__doc__) return wrapper # type: ignore[return-value] def _update_docstring(self, docstring: Optional[str]) -> str: insert_block = [f".. deprecated:: {self._since}"] if self._removed_in: insert_block.append(f" Will be removed in version {self._removed_in}.") if self._meth: insert_block.append(f" Use {self._meth} instead.") if not docstring: return "\n".join(insert_block) lines = docstring.splitlines() indent = "" for line in lines[1:]: if line: indent = line[: -len(line.lstrip())] break lines[1:1] = (f"{indent}{message}" for message in insert_block) lines.insert(1, "") return "\n".join(lines)
[docs]class KwargsDeprecated: """A decorator for the function which has deprecated keyword arguments. Arguments: keywords: The keyword arguments which need to be deprecated. since: The version the keyword arguments are deprecated. remove_in: The version the keyword arguments will be removed in. substitute: The substitute usage. """ def __init__( self, keywords: Tuple[str, ...], *, since: str, removed_in: Optional[str] = None, substitute: Optional[str] = None, ) -> None: self._keywords = keywords self._since = since self._removed_in = removed_in self._substitute = substitute def __call__(self, func: _Callable) -> _Callable: """Wrap the decorated function by adding the deprecated message. Arguments: func: The deprecated function. Returns: The wrapped function which shows the deprecated message when calling. """ keywords = tuple(f'"{keyword}"' for keyword in self._keywords) if len(keywords) == 1: keyword_message = keywords[0] argument = "argument" be = "is" # pylint: disable=invalid-name else: keyword_message = f"{' '.join(keywords[:-1])} and {keywords[-1]}" argument = "arguments" be = "are" # pylint: disable=invalid-name messages = [ ( f'The keyword {argument}: {keyword_message} in "{func.__name__}" ' f"{be} deprecated since version {self._since}." ) ] if self._removed_in: messages.append(f'Will be removed in version "{self._removed_in}".') if self._substitute: messages.append(f'Use "{self._substitute}" instead.') message = " ".join(messages) @wraps(func) def wrapper(*arg: Any, **kwargs: Any) -> Any: if set(self._keywords) & kwargs.keys(): warnings.warn(message, DeprecationWarning, 2) return func(*arg, **kwargs) return wrapper # type: ignore[return-value]
[docs]class DefaultValueDeprecated: """A decorator for the function which has deprecated argument default value. Arguments: keyword: The argument keyword whose default value needs to be deprecated. since: The version the keyword arguments are deprecated. remove_in: The version the keyword arguments will be removed in. """ def __init__( self, keyword: str, *, since: str, removed_in: Optional[str] = None, ) -> None: self._keyword = keyword self._since = since self._removed_in = removed_in def __call__(self, func: _Callable) -> _Callable: """Wrap the decorated function by adding the deprecated message. Arguments: func: The deprecated function. Returns: The wrapped function which shows the deprecated message when calling. """ signature = inspect.signature(func) default_value = signature.parameters[self._keyword].default messages = [ ( f'The argument "{self._keyword}" in "{func.__name__}" is required ' f'since version "{self._since}".' ) ] if self._removed_in: messages.append( "Its default value is deprecated and " f'will be removed in version "{self._removed_in}".' ) message = " ".join(messages) @wraps(func) def wrapper(*arg: Any, **kwargs: Any) -> Any: bound_argument = signature.bind(*arg, **kwargs) bound_argument.apply_defaults() if bound_argument.arguments[self._keyword] == default_value: warnings.warn(message, DeprecationWarning, 2) return func(*arg, **kwargs) return wrapper # type: ignore[return-value]
[docs]class Disable: """A decorator for the function which is disabled temporarily. Arguments: since: The version the function is disabled temporarily. enabled_in: The version the function will be enabled again. reason: The reason that the function is disabled temporarily. """ def __init__( self, *, since: str, enabled_in: Optional[str], reason: Optional[str], ) -> None: self._since = since self._enabled_in = enabled_in self._reason = reason def __call__(self, func: _Callable) -> _Callable: """Wrap the disabled function by adding the message. Arguments: func: The disabled function. Returns: The wrapped function which shows the message when calling. """ messages = [f'The function "{func.__name__}" will be disabled since version {self._since}.'] if self._reason: messages.append(f"The reason is {self._reason}.") if self._enabled_in: messages.append(f"Will be enabled again in version {self._enabled_in}.") message = " ".join(messages) @wraps(func) def wrapper(*arg: Any, **kwargs: Any) -> Any: raise NotImplementedError(message) return wrapper # type: ignore[return-value]