Source code for sqlspec.utils.deprecation

"""Deprecation utilities for SQLSpec.

Provides decorators and warning functions for marking deprecated functionality.
Used to communicate API changes and migration paths to users.
"""

import functools
import inspect
import types
from collections.abc import Callable
from typing import Generic, Literal, cast
from warnings import warn

from typing_extensions import ParamSpec, TypeVar

__all__ = ("deprecated", "warn_deprecation")


T = TypeVar("T")
P = ParamSpec("P")
DeprecatedKind = Literal["function", "method", "classmethod", "attribute", "property", "class", "parameter", "import"]


[docs] def warn_deprecation( version: str, deprecated_name: str, kind: DeprecatedKind, *, removal_in: str | None = None, alternative: str | None = None, info: str | None = None, pending: bool = False, stacklevel: int = 2, ) -> None: """Warn about a call to a deprecated function. Args: version: SQLSpec version where the deprecation will occur deprecated_name: Name of the deprecated function removal_in: SQLSpec version where the deprecated function will be removed alternative: Name of a function that should be used instead info: Additional information pending: Use :class:`warnings.PendingDeprecationWarning` instead of :class:`warnings.DeprecationWarning` kind: Type of the deprecated thing stacklevel: Warning stacklevel to report the correct caller site. """ parts = [] if kind == "import": access_type = "Import of" elif kind in {"function", "method"}: access_type = "Call to" else: access_type = "Use of" if pending: parts.append(f"{access_type} {kind} awaiting deprecation '{deprecated_name}'") # pyright: ignore[reportUnknownMemberType] else: parts.append(f"{access_type} deprecated {kind} '{deprecated_name}'") # pyright: ignore[reportUnknownMemberType] parts.extend( # pyright: ignore[reportUnknownMemberType] (f"Deprecated in SQLSpec {version}", f"This {kind} will be removed in {removal_in or 'the next major version'}") ) if alternative: parts.append(f"Use {alternative!r} instead") # pyright: ignore[reportUnknownMemberType] if info: parts.append(info) # pyright: ignore[reportUnknownMemberType] text = ". ".join(parts) # pyright: ignore[reportUnknownArgumentType] warning_class = PendingDeprecationWarning if pending else DeprecationWarning warn(text, warning_class, stacklevel=stacklevel)
[docs] def deprecated( version: str, *, removal_in: str | None = None, alternative: str | None = None, info: str | None = None, pending: bool = False, kind: Literal["function", "method", "classmethod", "property"] | None = None, ) -> Callable[[Callable[P, T]], Callable[P, T]]: """Create a decorator wrapping a function, method or property with a deprecation warning. Args: version: SQLSpec version where the deprecation will occur removal_in: SQLSpec version where the deprecated function will be removed alternative: Name of a function that should be used instead info: Additional information pending: Use :class:`warnings.PendingDeprecationWarning` instead of :class:`warnings.DeprecationWarning` kind: Type of the deprecated callable. If ``None``, will use ``inspect`` to figure out if it's a function or method Returns: A decorator wrapping the function call with a warning """ return cast( "Callable[[Callable[P, T]], Callable[P, T]]", _DeprecatedFactory( version=version, removal_in=removal_in, alternative=alternative, info=info, pending=pending, kind=kind ), )
def _infer_deprecated_kind(func: Callable[P, T]) -> DeprecatedKind: if inspect.ismethod(func): return "method" qualname = getattr(func, "__qualname__", "") if inspect.isfunction(func) and "." in qualname and "<locals>" not in qualname: return "method" return "function" class _DeprecatedWrapper(Generic[P, T]): """Callable class replacing the nested wrapper closure in _DeprecatedFactory.__call__.""" __slots__ = ("__dict__", "_alternative", "_func", "_info", "_kind", "_pending", "_removal_in", "_version") def __init__( self, func: "Callable[P, T]", version: str, removal_in: "str | None", alternative: "str | None", info: "str | None", pending: bool, kind: DeprecatedKind, ) -> None: self._func = func self._version = version self._removal_in = removal_in self._alternative = alternative self._info = info self._pending = pending self._kind: DeprecatedKind = kind functools.update_wrapper(self, func) def __call__(self, *args: "P.args", **kwargs: "P.kwargs") -> T: warn_deprecation( version=self._version, deprecated_name=self._func.__name__, info=self._info, alternative=self._alternative, pending=self._pending, removal_in=self._removal_in, kind=self._kind, ) return self._func(*args, **kwargs) def __get__(self, obj: object, _objtype: "type | None" = None) -> "Callable[P, T]": if obj is None: return self return types.MethodType(self, obj) class _DeprecatedFactory(Generic[P, T]): __slots__ = ("_alternative", "_info", "_kind", "_pending", "_removal_in", "_version") def __init__( self, *, version: str, removal_in: str | None, alternative: str | None, info: str | None, pending: bool, kind: Literal["function", "method", "classmethod", "property"] | None, ) -> None: self._version = version self._removal_in = removal_in self._alternative = alternative self._info = info self._pending = pending self._kind: Literal["function", "method", "classmethod", "property"] | None = kind def __call__(self, func: "Callable[P, T]") -> "Callable[P, T]": kind: DeprecatedKind = self._kind or _infer_deprecated_kind(func) return _DeprecatedWrapper( func, self._version, self._removal_in, self._alternative, self._info, self._pending, kind )