Skip to content

Commit bcb236c

Browse files
bpo-45678: Add more singledispatchmethod tests (GH-29412) (GH-29424)
In order to fix a bug in the 3.9 branch in GH-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. (cherry picked from commit 32f55d1) Co-authored-by: Alex Waygood <[email protected]>
1 parent 099a94f commit bcb236c

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)