Skip to content

Commit 858f1f5

Browse files
Merge branch 'main' into gh-636-backport-137281-param-order
2 parents 9023129 + 108f448 commit 858f1f5

File tree

5 files changed

+50
-25
lines changed

5 files changed

+50
-25
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
with type variables. Now, parametrized `Generic` or `Protocol` base classes always
66
dictate the number and the order of the type parameters. Patch by Brian Schubert,
77
backporting a CPython PR by Nikita Sobolev.
8+
- Fix `__init_subclass__()` behavior in the presence of multiple inheritance involving
9+
an `@deprecated`-decorated base class. Backport of CPython PR
10+
[#138210](https://github.com/python/cpython/pull/138210) by Brian Schubert.
811
- Raise `TypeError` when attempting to subclass `typing_extensions.ParamSpec` on
912
Python 3.9. The `typing` implementation has always raised an error, and the
1013
`typing_extensions` implementation has raised an error on Python 3.10+ since

doc/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@
5656
# Don't include object entries (e.g. functions, classes) in the table of contents.
5757
toc_object_entries = False
5858

59+
# Warn about all references where the target cannot be found.
60+
nitpicky = True
61+
62+
5963
class MyTranslator(HTML5Translator):
6064
"""Adds a link target to name without `typing_extensions.` prefix."""
6165
def visit_desc_signature(self, node: Element) -> None:

doc/index.rst

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ Special typing primitives
303303
``default=None`` is passed, and to :data:`NoDefault` if no value is passed.
304304

305305
Previously, passing ``None`` would result in :attr:`!__default__` being set
306-
to :py:class:`types.NoneType`, and passing no value for the parameter would
306+
to :py:data:`types.NoneType`, and passing no value for the parameter would
307307
result in :attr:`!__default__` being set to ``None``.
308308

309309
.. versionchanged:: 4.12.0
@@ -312,10 +312,9 @@ Special typing primitives
312312
with :py:class:`typing.ParamSpec` on Python 3.13+.
313313

314314
.. class:: ParamSpecArgs
315+
ParamSpecKwargs
315316

316-
.. class:: ParamSpecKwargs
317-
318-
See :py:class:`typing.ParamSpecArgs` and :py:class:`typing.ParamSpecKwargs`.
317+
See :py:data:`typing.ParamSpecArgs` and :py:data:`typing.ParamSpecKwargs`.
319318
In ``typing`` since 3.10.
320319

321320
.. class:: Protocol
@@ -471,7 +470,7 @@ Special typing primitives
471470

472471
``TypedDict`` is now a function rather than a class.
473472
This brings ``typing_extensions.TypedDict`` closer to the implementation
474-
of :py:mod:`typing.TypedDict` on Python 3.9 and higher.
473+
of :py:class:`typing.TypedDict` on Python 3.9 and higher.
475474

476475
.. versionchanged:: 4.7.0
477476

@@ -526,7 +525,7 @@ Special typing primitives
526525
``default=None`` is passed, and to :data:`NoDefault` if no value is passed.
527526

528527
Previously, passing ``None`` would result in :attr:`!__default__` being set
529-
to :py:class:`types.NoneType`, and passing no value for the parameter would
528+
to :py:data:`types.NoneType`, and passing no value for the parameter would
530529
result in :attr:`!__default__` being set to ``None``.
531530

532531
.. versionchanged:: 4.12.0
@@ -557,7 +556,7 @@ Special typing primitives
557556
``default=None`` is passed, and to :data:`NoDefault` if no value is passed.
558557

559558
Previously, passing ``None`` would result in :attr:`!__default__` being set
560-
to :py:class:`types.NoneType`, and passing no value for the parameter would
559+
to :py:data:`types.NoneType`, and passing no value for the parameter would
561560
result in :attr:`!__default__` being set to ``None``.
562561

563562
.. versionchanged:: 4.12.0
@@ -799,7 +798,7 @@ Functions
799798
* Raises :exc:`TypeError` when it encounters certain objects that are
800799
not valid type hints.
801800
* Replaces type hints that evaluate to :const:`!None` with
802-
:class:`types.NoneType`.
801+
:data:`types.NoneType`.
803802
* Supports the :attr:`Format.FORWARDREF` and
804803
:attr:`Format.STRING` formats.
805804

@@ -1369,7 +1368,7 @@ versions of Python, but all are listed here for completeness.
13691368

13701369
.. data:: Union
13711370

1372-
See :py:data:`typing.Union`.
1371+
See :py:class:`typing.Union`.
13731372

13741373
.. versionadded:: 4.7.0
13751374

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)