1"""Utility for deprecating functions.""" 2 3import functools 4import textwrap 5import warnings 6from typing import Callable, TypeVar 7from typing_extensions import ParamSpec 8 9 10_T = TypeVar("_T") 11_P = ParamSpec("_P") 12 13 14def deprecated( 15 since: str, removed_in: str, instructions: str 16) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]: 17 """Marks functions as deprecated. 18 19 It will result in a warning when the function is called and a note in the 20 docstring. 21 22 Args: 23 since: The version when the function was first deprecated. 24 removed_in: The version when the function will be removed. 25 instructions: The action users should take. 26 """ 27 28 def decorator(function: Callable[_P, _T]) -> Callable[_P, _T]: 29 @functools.wraps(function) 30 def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T: 31 warnings.warn( 32 f"'{function.__module__}.{function.__name__}' " 33 f"is deprecated in version {since} and will be " 34 f"removed in {removed_in}. Please {instructions}.", 35 category=FutureWarning, 36 stacklevel=2, 37 ) 38 return function(*args, **kwargs) 39 40 # Add a deprecation note to the docstring. 41 docstring = function.__doc__ or "" 42 43 # Add a note to the docstring. 44 deprecation_note = textwrap.dedent( 45 f"""\ 46 .. deprecated:: {since} 47 Deprecated and will be removed in version {removed_in}. 48 Please {instructions}. 49 """ 50 ) 51 52 # Split docstring at first occurrence of newline 53 summary_and_body = docstring.split("\n\n", 1) 54 55 if len(summary_and_body) > 1: 56 summary, body = summary_and_body 57 58 # Dedent the body. We cannot do this with the presence of the summary because 59 # the body contains leading whitespaces when the summary does not. 60 body = textwrap.dedent(body) 61 62 new_docstring_parts = [deprecation_note, "\n\n", summary, body] 63 else: 64 summary = summary_and_body[0] 65 66 new_docstring_parts = [deprecation_note, "\n\n", summary] 67 68 wrapper.__doc__ = "".join(new_docstring_parts) 69 70 return wrapper 71 72 return decorator 73