Files
2026-07-13 13:39:38 +08:00

64 lines
2.0 KiB
Python

from __future__ import annotations
import functools
import inspect
from collections import defaultdict
from collections.abc import Callable
from typing import ParamSpec, TypeVar, cast
from ..log import logger
from ..types import NOT_GIVEN
from .misc import is_given
_P = ParamSpec("_P")
_R = TypeVar("_R")
_F = TypeVar("_F", bound=Callable)
def deprecate_params(
mapping: dict[str, str],
*,
target_version: str | None = None,
) -> Callable[[_F], _F]:
"""
Args:
mapping: {old_param: suggestion}
target_version: If set, the warning includes "will be removed in {target_version}".
Example:
>>> @deprecate_params({
... "old_param": "Use new_param instead",
... }, target_version="v2.0")
... def my_function(old_param: NotGivenOr[int] = NOT_GIVEN, new_param: int = 0):
... print(old_param)
>>> my_function(old_param=1)
WARNING: old_param is deprecated and will be removed in v2.0. Use new_param instead
1
>>> my_function(new_param=1) # no warning
"""
removal = f" and will be removed in {target_version}" if target_version else ""
def decorator(fn: Callable[_P, _R]) -> Callable[_P, _R]:
signature = inspect.signature(fn)
@functools.wraps(fn)
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
bound = signature.bind_partial(*args, **kwargs)
by_suggestion: defaultdict[str, list[str]] = defaultdict(list)
for name, suggestion in mapping.items():
if is_given(bound.arguments.get(name, NOT_GIVEN)):
by_suggestion[suggestion].append(name)
for suggestion, names in by_suggestion.items():
params = ", ".join(names)
logger.warning(
f"{params} {'are' if len(names) > 1 else 'is'} deprecated{removal}. {suggestion}", # noqa: E501
stacklevel=2,
)
return fn(*args, **kwargs)
return wrapper
return cast(Callable[[_F], _F], decorator)