Skip to content

Commit 32f55d1

Browse files
authored
bpo-45678: Add more singledispatchmethod tests (GH-29412)
In order to fix a bug in the 3.9 branch in #29394, more tests were added to ``test_functools.py`` to ensure that ``singledispatchmethod`` still correctly wrapped a target method, even if the target method had already been wrapped by multiple other decorators. This PR brings the new tests into the 3.11 and 3.10 branches as well.
1 parent 3509b26 commit 32f55d1

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

Lib/test/test_functools.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2519,6 +2519,105 @@ def static_func(arg: int) -> str:
25192519
self.assertEqual(A.static_func.__name__, 'static_func')
25202520
self.assertEqual(A().static_func.__name__, 'static_func')
25212521

2522+
def test_double_wrapped_methods(self):
2523+
def classmethod_friendly_decorator(func):
2524+
wrapped = func.__func__
2525+
@classmethod
2526+
@functools.wraps(wrapped)
2527+
def wrapper(*args, **kwargs):
2528+
return wrapped(*args, **kwargs)
2529+
return wrapper
2530+
2531+
class WithoutSingleDispatch:
2532+
@classmethod
2533+
@contextlib.contextmanager
2534+
def cls_context_manager(cls, arg: int) -> str:
2535+
try:
2536+
yield str(arg)
2537+
finally:
2538+
return 'Done'
2539+
2540+
@classmethod_friendly_decorator
2541+
@classmethod
2542+
def decorated_classmethod(cls, arg: int) -> str:
2543+
return str(arg)
2544+
2545+
class WithSingleDispatch:
2546+
@functools.singledispatchmethod
2547+
@classmethod
2548+
@contextlib.contextmanager
2549+
def cls_context_manager(cls, arg: int) -> str:
2550+
"""My function docstring"""
2551+
try:
2552+
yield str(arg)
2553+
finally:
2554+
return 'Done'
2555+
2556+
@functools.singledispatchmethod
2557+
@classmethod_friendly_decorator
2558+
@classmethod
2559+
def decorated_classmethod(cls, arg: int) -> str:
2560+
"""My function docstring"""
2561+
return str(arg)
2562+
2563+
# These are sanity checks
2564+
# to test the test itself is working as expected
2565+
with WithoutSingleDispatch.cls_context_manager(5) as foo:
2566+
without_single_dispatch_foo = foo
2567+
2568+
with WithSingleDispatch.cls_context_manager(5) as foo:
2569+
single_dispatch_foo = foo
2570+
2571+
self.assertEqual(without_single_dispatch_foo, single_dispatch_foo)
2572+
self.assertEqual(single_dispatch_foo, '5')
2573+
2574+
self.assertEqual(
2575+
WithoutSingleDispatch.decorated_classmethod(5),
2576+
WithSingleDispatch.decorated_classmethod(5)
2577+
)
2578+
2579+
self.assertEqual(WithSingleDispatch.decorated_classmethod(5), '5')
2580+
2581+
# Behavioural checks now follow
2582+
for method_name in ('cls_context_manager', 'decorated_classmethod'):
2583+
with self.subTest(method=method_name):
2584+
self.assertEqual(
2585+
getattr(WithSingleDispatch, method_name).__name__,
2586+
getattr(WithoutSingleDispatch, method_name).__name__
2587+
)
2588+
2589+
self.assertEqual(
2590+
getattr(WithSingleDispatch(), method_name).__name__,
2591+
getattr(WithoutSingleDispatch(), method_name).__name__
2592+
)
2593+
2594+
for meth in (
2595+
WithSingleDispatch.cls_context_manager,
2596+
WithSingleDispatch().cls_context_manager,
2597+
WithSingleDispatch.decorated_classmethod,
2598+
WithSingleDispatch().decorated_classmethod
2599+
):
2600+
with self.subTest(meth=meth):
2601+
self.assertEqual(meth.__doc__, 'My function docstring')
2602+
self.assertEqual(meth.__annotations__['arg'], int)
2603+
2604+
self.assertEqual(
2605+
WithSingleDispatch.cls_context_manager.__name__,
2606+
'cls_context_manager'
2607+
)
2608+
self.assertEqual(
2609+
WithSingleDispatch().cls_context_manager.__name__,
2610+
'cls_context_manager'
2611+
)
2612+
self.assertEqual(
2613+
WithSingleDispatch.decorated_classmethod.__name__,
2614+
'decorated_classmethod'
2615+
)
2616+
self.assertEqual(
2617+
WithSingleDispatch().decorated_classmethod.__name__,
2618+
'decorated_classmethod'
2619+
)
2620+
25222621
def test_invalid_registrations(self):
25232622
msg_prefix = "Invalid first argument to `register()`: "
25242623
msg_suffix = (
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add tests for scenarios in which :class:`functools.singledispatchmethod` is
2+
stacked on top of a method that has already been wrapped by two other
3+
decorators. Patch by Alex Waygood.

0 commit comments

Comments
 (0)