Skip to content

gh-107001: Add a stdlib decorator that copies/applies the ParameterSpec from one function to another #121693

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
40 changes: 40 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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*.
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand Down
57 changes: 57 additions & 0 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@
'assert_never',
'cast',
'clear_overloads',
'copy_func_params',
'copy_method_params',
'dataclass_transform',
'evaluate_forward_ref',
'final',
Expand Down Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading