Skip to content

Commit b7e10f2

Browse files
authored
[functools] Support binding wrapped methods (#16515)
1 parent 4666b93 commit b7e10f2

2 files changed

Lines changed: 35 additions & 7 deletions

File tree

‎stdlib/@tests/test_cases/check_functools.py‎

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from functools import cache, cached_property, lru_cache, wraps
3+
from functools import cache, cached_property, lru_cache, update_wrapper, wraps
44
from typing import Callable, ParamSpec, TypeVar
55
from typing_extensions import assert_type
66

@@ -33,9 +33,26 @@ def wrapped(x: int) -> None: ...
3333
def identical_wrapper(x: int) -> None: ...
3434
@wraps(wrapped)
3535
def other_signature_wrapper(x: str, y: float) -> None: ...
36+
@wraps(wrapped)
37+
def no_args_wrapper() -> None: ...
38+
@wraps(wrapped)
39+
def keyword_only_wrapper(*, x: int) -> None: ...
3640

3741
identical_wrapper(3)
42+
identical_wrapper(x=3)
3843
other_signature_wrapper("parrot", 42.0)
44+
no_args_wrapper()
45+
keyword_only_wrapper(x=3)
46+
47+
48+
def check_update_wrapper() -> None:
49+
def wrapped(x: int) -> None: ...
50+
def wrapper(*, text: str) -> int:
51+
return len(text)
52+
53+
updated = update_wrapper(wrapper, wrapped)
54+
assert_type(updated(text="parrot"), int)
55+
assert_type(updated.__wrapped__(3), None)
3956

4057

4158
def check_wraps_method() -> None:
@@ -44,18 +61,19 @@ def wrapped(self, x: int) -> None: ...
4461
@wraps(wrapped)
4562
def wrapper(self, x: int) -> None: ...
4663

47-
class Wrapper: # pyright: ignore[reportUnusedClass]
64+
class Wrapper:
4865
@wraps(Wrapped.wrapped)
4966
def method(self, x: int) -> None: ...
5067

5168
@wraps(Wrapped.wrapped)
5269
def func_wrapper(x: int) -> None: ...
5370

54-
# TODO: The following should work, but currently don't.
55-
# https://github.com/python/typeshed/issues/10653
56-
# Wrapped().wrapper(3)
57-
# Wrapper().method(3)
71+
assert_type(Wrapped.wrapper(Wrapped(), x=3), None)
72+
assert_type(Wrapped().wrapper(3), None)
73+
assert_type(Wrapped().wrapper(x=3), None)
74+
assert_type(Wrapper().method(3), None)
5875
func_wrapper(3)
76+
func_wrapper(x=3)
5977

6078

6179
#

‎stdlib/functools.pyi‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ from collections.abc import Callable, Hashable, Iterable, Sized
55
from types import GenericAlias
66
from typing import (
77
Any,
8+
Concatenate,
89
Final,
910
Generic,
1011
Literal,
@@ -43,6 +44,8 @@ _PWrapped = ParamSpec("_PWrapped")
4344
_RWrapped = TypeVar("_RWrapped")
4445
_PWrapper = ParamSpec("_PWrapper")
4546
_RWrapper = TypeVar("_RWrapper")
47+
_WrapperSelfT = TypeVar("_WrapperSelfT")
48+
_PBound = ParamSpec("_PBound")
4649

4750
if sys.version_info >= (3, 14):
4851
@overload
@@ -117,10 +120,17 @@ WRAPPER_UPDATES: Final[tuple[Literal["__dict__"]]]
117120
class _Wrapped(Generic[_PWrapped, _RWrapped, _PWrapper, _RWrapper]):
118121
__wrapped__: Callable[_PWrapped, _RWrapped]
119122
def __call__(self, *args: _PWrapper.args, **kwargs: _PWrapper.kwargs) -> _RWrapper: ...
120-
# as with ``Callable``, we'll assume that these attributes exist
123+
# as with ``Callable``, we'll assume that these attributes/methods exist
121124
__name__: str
122125
__qualname__: str
123126

127+
@overload
128+
def __get__(self, instance: None, owner: type, /) -> Self: ...
129+
@overload
130+
def __get__(
131+
self: Callable[Concatenate[_WrapperSelfT, _PBound], _RWrapper], instance: _WrapperSelfT, owner: type | None = None, /
132+
) -> Callable[_PBound, _RWrapper]: ...
133+
124134
@type_check_only
125135
class _Wrapper(Generic[_PWrapped, _RWrapped]):
126136
def __call__(self, f: Callable[_PWrapper, _RWrapper]) -> _Wrapped[_PWrapped, _RWrapped, _PWrapper, _RWrapper]: ...

0 commit comments

Comments
 (0)