diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index a0beed4a8c77fb..dbcbcff4992364 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -281,6 +281,7 @@ callables, the :data:`Concatenate` operator may be used. They take the form ``Callable[ParamSpecVariable, ReturnType]`` and ``Callable[Concatenate[Arg1Type, Arg2Type, ..., ParamSpecVariable], ReturnType]`` respectively. +To copy the call from one function to another use :func:`copy_func_params`. .. versionchanged:: 3.10 ``Callable`` now supports :class:`ParamSpec` and :data:`Concatenate`. @@ -2864,6 +2865,45 @@ Functions and decorators runtime we intentionally don't check anything (we want this to be as fast as possible). +.. decorator:: copy_func_params(source_func) + + Cast the decorated function's call signature to the *source_func*'s. + + Use this decorator enhancing an upstream function while keeping its + call signature. + Returns the original function with the *source_func*'s call signature. + + Usage:: + + from typing import copy_func_params, Any + + def upstream_func(a: int, b: float, *, double: bool = False) -> float: + ... + + @copy_func_params(upstream_func) + def enhanced( + a: int, b: float, *args: Any, double: bool = False, **kwargs: Any + ) -> str: + ... + + .. note:: + + Include ``*args`` and ``**kwargs`` in the signature of the decorated + function in order to avoid a :py:class:`TypeError` when the call signature of + *source_func* changes. + + .. versionadded:: 3.14 + + +.. decorator:: copy_method_params(source_method) + + Cast the decorated method's call signature to the source_method's + + Same as :py:func:`copy_func_params` but intended to be used with methods. + It keeps the first argument (``self``/``cls``) of the decorated method. + + .. versionadded:: 3.14 + .. function:: assert_type(val, typ, /) Ask a static type checker to confirm that *val* has an inferred type of *typ*. diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index aaa4702d53df93..2d104e035cc7d7 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1084,6 +1084,10 @@ symtable (Contributed by Bénédikt Tran in :gh:`120029`.) +typing +------ +* Add :func:`~typing.copy_func_params` that copies/applies + the :class:`~typing.ParamSpec` from one function to another. sys --- diff --git a/Lib/typing.py b/Lib/typing.py index f70dcd0b5b7b5c..03ffeed9d1e0d9 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -125,6 +125,8 @@ 'assert_never', 'cast', 'clear_overloads', + 'copy_func_params', + 'copy_method_params', 'dataclass_transform', 'evaluate_forward_ref', 'final', @@ -3752,6 +3754,61 @@ def get_protocol_members(tp: type, /) -> frozenset[str]: return frozenset(tp.__protocol_attrs__) +def copy_func_params[**Param, RV]( + source_func: Callable[Param, Any] +) -> Callable[[Callable[..., RV]], Callable[Param, RV]]: + """Cast the decorated function's call signature to the source_func's. + + Use this decorator enhancing an upstream function while keeping its + call signature. + Returns the original function with the source_func's call signature. + + Usage:: + + from typing import copy_func_params, Any + + def upstream_func(a: int, b: float, *, double: bool = False) -> float: + ... + + @copy_func_params(upstream_func) + def enhanced( + a: int, b: float, *args: Any, double: bool = False, **kwargs: Any + ) -> str: + ... + + .. note:: + + Include ``*args`` and ``**kwargs`` in the signature of the decorated + function in order to avoid TypeErrors when the call signature of + *source_func* changes. + """ + + def return_func(func: Callable[..., RV]) -> Callable[Param, RV]: + return cast(Callable[Param, RV], func) + + return return_func + + +def copy_method_params[**Param, Arg1, RV]( + source_method: Callable[Concatenate[Any, Param], Any] +) -> Callable[ + [Callable[Concatenate[Arg1, ...], RV]], + Callable[Concatenate[Arg1, Param], RV] +]: + """Cast the decorated method's call signature to the source_method's. + + Same as :func:`copy_func_params` but intended to be used with methods. + It keeps the first argument (``self``/``cls``) of the decorated method. + """ + + def return_func( + func: Callable[Concatenate[Arg1, ...], RV] + ) -> Callable[Concatenate[Arg1, Param], RV]: + return cast(Callable[Concatenate[Arg1, Param], RV], func) + + return return_func + + def __getattr__(attr): """Improve the import time of the typing module. diff --git a/Misc/NEWS.d/next/Library/2024-07-13-13-20-43.gh-issue-107001.fRSPOX.rst b/Misc/NEWS.d/next/Library/2024-07-13-13-20-43.gh-issue-107001.fRSPOX.rst new file mode 100644 index 00000000000000..6b11ae26546598 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-13-13-20-43.gh-issue-107001.fRSPOX.rst @@ -0,0 +1,4 @@ +Add :func:`~typing.copy_func_params` and :func:`~typing.copy_func_params` +to :mod:`typing` that copies/applies the +:class:`~typing.ParamSpec` from one function/method to another. +Patch by Carli Freudenberg.