64 lines
2.0 KiB
Python
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)
|