Skip to content

Commit 108f448

Browse files
Backport __init_subclass__ fix for @deprecated from CPython (#673)
1 parent d5feeb1 commit 108f448

File tree

3 files changed

+38
-16
lines changed

3 files changed

+38
-16
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Unreleased
22

3+
- Fix `__init_subclass__()` behavior in the presence of multiple inheritance involving
4+
an `@deprecated`-decorated base class. Backport of CPython PR
5+
[#138210](https://github.com/python/cpython/pull/138210) by Brian Schubert.
36
- Raise `TypeError` when attempting to subclass `typing_extensions.ParamSpec` on
47
Python 3.9. The `typing` implementation has always raised an error, and the
58
`typing_extensions` implementation has raised an error on Python 3.10+ since

src/test_typing_extensions.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,25 @@ class D(C, x=3):
813813

814814
self.assertEqual(D.inited, 3)
815815

816+
def test_existing_init_subclass_in_sibling_base(self):
817+
@deprecated("A will go away soon")
818+
class A:
819+
pass
820+
class B:
821+
def __init_subclass__(cls, x):
822+
super().__init_subclass__()
823+
cls.inited = x
824+
825+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
826+
class C(A, B, x=42):
827+
pass
828+
self.assertEqual(C.inited, 42)
829+
830+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
831+
class D(B, A, x=42):
832+
pass
833+
self.assertEqual(D.inited, 42)
834+
816835
def test_init_subclass_has_correct_cls(self):
817836
init_subclass_saw = None
818837

src/typing_extensions.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2903,9 +2903,9 @@ def method(self) -> None:
29032903
return arg
29042904

29052905

2906-
# Python 3.13.3+ contains a fix for the wrapped __new__
2907-
# Breakpoint: https://github.com/python/cpython/pull/132160
2908-
if sys.version_info >= (3, 13, 3):
2906+
# Python 3.13.8+ and 3.14.1+ contain a fix for the wrapped __init_subclass__
2907+
# Breakpoint: https://github.com/python/cpython/pull/138210
2908+
if ((3, 13, 8) <= sys.version_info < (3, 14)) or sys.version_info >= (3, 14, 1):
29092909
deprecated = warnings.deprecated
29102910
else:
29112911
_T = typing.TypeVar("_T")
@@ -2998,27 +2998,27 @@ def __new__(cls, /, *args, **kwargs):
29982998

29992999
arg.__new__ = staticmethod(__new__)
30003000

3001-
original_init_subclass = arg.__init_subclass__
3002-
# We need slightly different behavior if __init_subclass__
3003-
# is a bound method (likely if it was implemented in Python)
3004-
if isinstance(original_init_subclass, MethodType):
3005-
original_init_subclass = original_init_subclass.__func__
3001+
if "__init_subclass__" in arg.__dict__:
3002+
# __init_subclass__ is directly present on the decorated class.
3003+
# Synthesize a wrapper that calls this method directly.
3004+
original_init_subclass = arg.__init_subclass__
3005+
# We need slightly different behavior if __init_subclass__
3006+
# is a bound method (likely if it was implemented in Python).
3007+
# Otherwise, it likely means it's a builtin such as
3008+
# object's implementation of __init_subclass__.
3009+
if isinstance(original_init_subclass, MethodType):
3010+
original_init_subclass = original_init_subclass.__func__
30063011

30073012
@functools.wraps(original_init_subclass)
30083013
def __init_subclass__(*args, **kwargs):
30093014
warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
30103015
return original_init_subclass(*args, **kwargs)
3011-
3012-
arg.__init_subclass__ = classmethod(__init_subclass__)
3013-
# Or otherwise, which likely means it's a builtin such as
3014-
# object's implementation of __init_subclass__.
30153016
else:
3016-
@functools.wraps(original_init_subclass)
3017-
def __init_subclass__(*args, **kwargs):
3017+
def __init_subclass__(cls, *args, **kwargs):
30183018
warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
3019-
return original_init_subclass(*args, **kwargs)
3019+
return super(arg, cls).__init_subclass__(*args, **kwargs)
30203020

3021-
arg.__init_subclass__ = __init_subclass__
3021+
arg.__init_subclass__ = classmethod(__init_subclass__)
30223022

30233023
arg.__deprecated__ = __new__.__deprecated__ = msg
30243024
__init_subclass__.__deprecated__ = msg

0 commit comments

Comments
 (0)