Source code for plaid.utils.deprecation

"""Implementation of deprecation utilities."""

# -*- coding: utf-8 -*-
#
# This file is subject to the terms and conditions defined in
# file 'LICENSE.txt', which is part of this source code package.
#
#

# %% Imports

import functools
import warnings
from typing import Optional, Union

from packaging.version import Version

import plaid
from plaid.utils.base import DeprecatedError

try:
    from warnings import deprecated as deprecated_builtin  # Python 3.13+
except ImportError:
[docs] deprecated_builtin = None
# %% Functions
[docs] def deprecated( reason: str, version: Optional[Union[str, Version]] = None, removal: Optional[Union[str, Version]] = None, ): """Decorator to mark a function, method, or class as deprecated. Uses built-in `warnings.deprecated` when running on Python 3.13+, otherwise falls back to a custom warning wrapper. Args: reason (str): Explanation and suggested replacement. version (Union[str,Version], optional): Version since deprecation. removal (Union[str,Version], optional): Planned removal version. """ message_parts = [reason] if version: if isinstance(version, str): version = Version(version) message_parts.append(f"[since v{version}]") if removal: if isinstance(removal, str): removal = Version(removal) message_parts.append(f"(will be removed in v{removal})") full_message = " ".join(message_parts) if removal and Version(plaid.__version__) >= removal: # pragma: no cover full_message = [f"Removed in v{removal}, {reason}"] def decorator(_func): def wrapper(*_args, **_kwargs): raise DeprecatedError(full_message) return wrapper if deprecated_builtin is not None: # pragma: no cover def decorator(obj): return deprecated_builtin( full_message, category=DeprecationWarning, stacklevel=2 )(obj) return decorator def decorator(obj): if isinstance(obj, type): orig_init = obj.__init__ @functools.wraps(orig_init) def new_init(self, *args, **kwargs): warnings.warn(full_message, DeprecationWarning, stacklevel=2) return orig_init(self, *args, **kwargs) obj.__init__ = new_init return obj elif callable(obj): @functools.wraps(obj) def wrapper(*args, **kwargs): warnings.warn(full_message, DeprecationWarning, stacklevel=2) return obj(*args, **kwargs) return wrapper else: raise TypeError( "@deprecated decorator with non-None category must be applied to " f"a class or callable, not {obj!r}" ) return decorator
[docs] def deprecated_argument( old_arg: str, new_arg: str, converter=lambda x: x, version: Optional[Union[str, Version]] = None, removal: Optional[Union[str, Version]] = None, ): """Decorator to mark a function argument as deprecated and redirect it to a new argument. Args: old_arg (str): Name of the old argument. new_arg (str): Name of the new argument. converter (callable): Function to convert the old value into the new format. version (Union[str,Version], optional): Version since deprecation. removal (Union[str,Version], optional): Planned removal version. """ if isinstance(removal, str): removal = Version(removal) if removal and Version(plaid.__version__) >= removal: # pragma: no cover full_message = [ f"Argument `{old_arg}` has been removed in v{removal}, use `{new_arg}` instead." ] def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): if old_arg in kwargs: raise DeprecatedError(full_message) return func(*args, **kwargs) return wrapper else: if isinstance(version, str): version = Version(version) message_parts = [ f"Argument `{old_arg}` is deprecated, use `{new_arg}` instead." ] if version: message_parts.append(f"[since v{version}]") if removal: message_parts.append(f"(will be removed in v{removal})") full_message = " ".join(message_parts) def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): if old_arg in kwargs: if new_arg in kwargs: raise ValueError( f"Arguments `{old_arg}` and `{new_arg}` cannot be both set." ) # Emit deprecation warning if deprecated_builtin is not None: # pragma: no cover # In Python 3.13+, link warning to the function itself decorated = deprecated_builtin( full_message, category=DeprecationWarning, stacklevel=2 )(func) return decorated( *args, **{new_arg: converter(kwargs.pop(old_arg)), **kwargs} ) else: warnings.warn(full_message, DeprecationWarning, stacklevel=2) kwargs[new_arg] = converter(kwargs.pop(old_arg)) return func(*args, **kwargs) return wrapper return decorator