"""
Deprecation
=============================
This module provides decorators for marking classes and functions as deprecated.
It also offers a decorator to support deprecated argument names in functions by
automatically mapping them to their new names and issuing a warning.
Functions and Decorators
------------------------
Class Example:
.. code-block:: python
@deprecated_class(new="NewClass")
class OldClass:
pass
Function Example:
.. code-block:: python
@deprecated_func(new="new_function")
def old_function():
pass
Aliases Example:
.. code-block:: python
@deprecated_aliases(old_arg="new_arg")
def some_function(new_arg):
pass
# then you can use old args but deprecate warning
some_function(old_arg=...)
"""
import functools
import inspect
import warnings
__all__ = ['deprecated_class',
'deprecated_func',
'deprecated_aliases']
[docs]
def deprecated_class(*, new: str | None = None,
remarks: str | None = None,
removal_version: str | None = None):
"""Mark deprecated class
:param new: The renamed new usage
:param remarks: Further remarks to be shown
:param removal_version: Optional version or date when the function is planned to be removed
"""
def _decorator(cls):
ori_init = cls.__init__
@functools.wraps(ori_init)
def new_init(self, *args, **kwargs):
msg = _build_deprecated_message(cls.__qualname__, new, remarks, removal_version)
warnings.warn(
msg,
DeprecationWarning,
stacklevel=2
)
ori_init(self, *args, **kwargs)
cls.__init__ = new_init
#
if cls.__doc__ is None:
cls.__doc__ = f"DEPRECATED ({remarks}). "
else:
cls.__doc__ = f"DEPRECATED ({remarks}). " + cls.__doc__
return cls
return _decorator
[docs]
def deprecated_func(*, new: str | None = None,
remarks: str | None = None,
removal_version: str | None = None):
"""Mark deprecated functions.
:param new: The renamed new usage
:param remarks: The reason why the function is deprecated
:param removal_version: Optional version or date when the function is planned to be removed
"""
def _decorator(f):
@functools.wraps(f)
def _deprecated_func(*args, **kwargs):
msg = _build_deprecated_message(f.__qualname__, new, remarks, removal_version)
warnings.warn(
msg,
DeprecationWarning,
stacklevel=2
)
return f(*args, **kwargs)
if f.__doc__ is None:
_deprecated_func.__doc__ = f"DEPRECATED ({remarks}). "
else:
_deprecated_func.__doc__ = f"DEPRECATED ({remarks}). " + f.__doc__
return _deprecated_func
return _decorator
def _build_deprecated_message(target: str,
alternation: str | None = None,
remarks: str | None = None,
removal: str | None = None) -> str:
msg = f'{target} is deprecated'
if removal is not None:
msg += f' and will be removed in a future release (after version {removal}).'
else:
msg += '.'
if alternation is not None:
msg += f' Please use "{alternation}" instead.'
if remarks is not None:
msg += f' NOTE: {remarks}.'
return msg
[docs]
def deprecated_aliases(**aliases: str):
"""
Mark deprecated argument names and map them to new argument names in a function
This decorator allows you to support old argument names while transitioning to new ones.
It will raise a ``DeprecationWarning`` if the old argument name is used and will automatically
map the value to the new argument name.
:param aliases: mapping of `old argument names` to `new argument names`.
:return: Decorated function with support for deprecated argument names.
:raises RuntimeError: If a new argument name does not exist in the function's signature.
:raises ValueError: If both old and new argument names are provided simultaneously.
"""
def _decorator(f):
sig = inspect.signature(f)
@functools.wraps(f)
def _wrapper(*args, **kwargs):
for old_arg, new_arg in aliases.items():
if new_arg not in sig.parameters:
raise RuntimeError(f'New argument: {new_arg} is not in the function arg.')
if new_arg in kwargs and old_arg in kwargs:
raise ValueError(f'Cannot specify both {old_arg} and {new_arg} at the same time')
if old_arg in kwargs:
warnings.warn(
f'"{old_arg}" is deprecated and will be removed in future version. Use "{new_arg}" instead',
DeprecationWarning,
stacklevel=2
)
# If the new argument is not already in kwargs, move the value from the old argument
if new_arg not in kwargs:
kwargs[new_arg] = kwargs.pop(old_arg)
return f(*args, **kwargs)
return _wrapper
return _decorator