From 0a372a0f9de0804dea1d7874ec031b084c4906ef Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Mon, 25 Aug 2025 10:18:27 -0400 Subject: [PATCH 01/33] Use PEP 661 `Sentinel` for internal sentinel (#657) --- src/typing_extensions.py | 79 ++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 77f33e16..c2ecc2fc 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -160,17 +160,48 @@ # Added with bpo-45166 to 3.10.1+ and some 3.9 versions _FORWARD_REF_HAS_CLASS = "__forward_is_class__" in typing.ForwardRef.__slots__ -# The functions below are modified copies of typing internal helpers. -# They are needed by _ProtocolMeta and they provide support for PEP 646. +class Sentinel: + """Create a unique sentinel object. + + *name* should be the name of the variable to which the return value shall be assigned. + *repr*, if supplied, will be used for the repr of the sentinel object. + If not provided, "" will be used. + """ + + def __init__( + self, + name: str, + repr: typing.Optional[str] = None, + ): + self._name = name + self._repr = repr if repr is not None else f'<{name}>' -class _Sentinel: def __repr__(self): - return "" + return self._repr + + if sys.version_info < (3, 11): + # The presence of this method convinces typing._type_check + # that Sentinels are types. + def __call__(self, *args, **kwargs): + raise TypeError(f"{type(self).__name__!r} object is not callable") + # Breakpoint: https://github.com/python/cpython/pull/21515 + if sys.version_info >= (3, 10): + def __or__(self, other): + return typing.Union[self, other] + + def __ror__(self, other): + return typing.Union[other, self] + + def __getstate__(self): + raise TypeError(f"Cannot pickle {type(self).__name__!r} object") -_marker = _Sentinel() +_marker = Sentinel("sentinel") + +# The functions below are modified copies of typing internal helpers. +# They are needed by _ProtocolMeta and they provide support for PEP 646. # Breakpoint: https://github.com/python/cpython/pull/27342 if sys.version_info >= (3, 10): @@ -4207,44 +4238,6 @@ def evaluate_forward_ref( ) -class Sentinel: - """Create a unique sentinel object. - - *name* should be the name of the variable to which the return value shall be assigned. - - *repr*, if supplied, will be used for the repr of the sentinel object. - If not provided, "" will be used. - """ - - def __init__( - self, - name: str, - repr: typing.Optional[str] = None, - ): - self._name = name - self._repr = repr if repr is not None else f'<{name}>' - - def __repr__(self): - return self._repr - - if sys.version_info < (3, 11): - # The presence of this method convinces typing._type_check - # that Sentinels are types. - def __call__(self, *args, **kwargs): - raise TypeError(f"{type(self).__name__!r} object is not callable") - - # Breakpoint: https://github.com/python/cpython/pull/21515 - if sys.version_info >= (3, 10): - def __or__(self, other): - return typing.Union[self, other] - - def __ror__(self, other): - return typing.Union[other, self] - - def __getstate__(self): - raise TypeError(f"Cannot pickle {type(self).__name__!r} object") - - if sys.version_info >= (3, 14, 0, "beta"): type_repr = annotationlib.type_repr else: From d372913fd71f8f01008c20276fbfe01519ddf1df Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Mon, 25 Aug 2025 10:24:15 -0400 Subject: [PATCH 02/33] Coverage: increase percentage precision (#660) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index adfed5d4..e1775876 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,6 +123,7 @@ known-first-party = ["typing_extensions", "_typed_dict_test_helper"] [tool.coverage.report] fail_under = 96 +precision = 2 show_missing = true # Omit files that are created in temporary directories during tests. # If not explicitly omitted they will result in warnings in the report. From c8caace74a5b893405cacb6df56528850c0f1b4e Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Tue, 26 Aug 2025 02:23:21 -0400 Subject: [PATCH 03/33] Drop Python 3.9 from SQLAlchemy test matrix (#663) --- .github/workflows/third_party.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index 3a698bf7..f20ffd65 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -299,7 +299,7 @@ jobs: matrix: # PyPy is deliberately omitted here, since SQLAlchemy's tests # fail on PyPy for reasons unrelated to typing_extensions. - python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] + python-version: [ "3.10", "3.11", "3.12", "3.13" ] checkout-ref: [ "main", "rel_2_0" ] # sqlalchemy tests fail when using the Ubuntu 24.04 runner # https://github.com/sqlalchemy/sqlalchemy/commit/8d73205f352e68c6603e90494494ef21027ec68f From 8897584fde4a899ba66ce05617542b205d0cf9b5 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 27 Aug 2025 08:59:36 -0700 Subject: [PATCH 04/33] Update TypedDict docs (#664) --- doc/index.rst | 50 ++++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index 6aa95f5b..0199b9f1 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -259,6 +259,13 @@ Special typing primitives .. versionadded:: 4.12.0 +.. data:: NoExtraItems + + A sentinel used when the ``extra_items`` class argument to :class:`TypedDict` is not + provided. In ``typing`` since 3.15. + + .. versionadded:: 4.13.0 + .. data:: NotRequired See :py:data:`typing.NotRequired` and :pep:`655`. In ``typing`` since 3.11. @@ -387,12 +394,13 @@ Special typing primitives .. versionadded:: 4.10.0 -.. class:: TypedDict(dict, total=True) - - See :py:class:`typing.TypedDict` and :pep:`589`. In ``typing`` since 3.8. +.. class:: TypedDict(dict, total=True, closed=False, extra_items=) + See :py:class:`typing.TypedDict` and :pep:`589`. In ``typing`` since 3.8, but + changed and enhanced in several ways since then. ``typing_extensions`` backports various bug fixes and improvements - to ``TypedDict`` on Python 3.11 and lower. + to ``TypedDict``. + :py:class:`TypedDict` does not store runtime information about which (if any) keys are non-required in Python 3.8, and does not honor the ``total`` keyword with old-style ``TypedDict()`` in Python @@ -426,35 +434,23 @@ Special typing primitives .. versionadded:: 4.9.0 - The experimental ``closed`` keyword argument and the special key - ``__extra_items__`` proposed in :pep:`728` are supported. - - When ``closed`` is unspecified or ``closed=False`` is given, - ``__extra_items__`` behaves like a regular key. Otherwise, this becomes a - special key that does not show up in ``__readonly_keys__``, - ``__mutable_keys__``, ``__required_keys__``, ``__optional_keys``, or - ``__annotations__``. + The ``closed`` and ``extra_items`` keyword arguments introduced by + :pep:`728` and supported in Python 3.15 and newer are supported. For runtime introspection, two attributes can be looked at: .. attribute:: __closed__ A boolean flag indicating whether the current ``TypedDict`` is - considered closed. This is not inherited by the ``TypedDict``'s - subclasses. + considered closed. This reflects the ``closed`` class argument. .. versionadded:: 4.10.0 .. attribute:: __extra_items__ - The type annotation of the extra items allowed on the ``TypedDict``. - This attribute defaults to ``None`` on a TypedDict that has itself and - all its bases non-closed. This default is different from ``type(None)`` - that represents ``__extra_items__: None`` defined on a closed - ``TypedDict``. - - If ``__extra_items__`` is not defined or inherited on a closed - ``TypedDict``, this defaults to ``Never``. + The type of the extra items allowed on the ``TypedDict``. + This attribute defaults to :data:`NoExtraItems` if the ``extra_items`` + class argument is not provided. .. versionadded:: 4.10.0 @@ -495,6 +491,16 @@ Special typing primitives The keyword argument ``closed`` and the special key ``__extra_items__`` when ``closed=True`` is given were supported. + .. versionchanged:: 4.13.0 + + :pep:`728` support was updated to a newer version. Extra items are now + indicated with an ``extra_items`` class argument, not a special key + ``__extra_items__``. + + A value assigned to ``__total__`` in the class body of a + ``TypedDict`` will be overwritten by the ``total`` argument of the + ``TypedDict`` constructor. + .. class:: TypeVar(name, *constraints, bound=None, covariant=False, contravariant=False, infer_variance=False, default=NoDefault) From 7f06750cca3fe6965589ece7c14fbffbfe7f6aab Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Wed, 27 Aug 2025 20:01:10 -0400 Subject: [PATCH 05/33] Add cross-reference for `warnings.deprecated` (#665) --- doc/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/index.rst b/doc/index.rst index 0199b9f1..05cdaf9f 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -702,7 +702,8 @@ Decorators .. decorator:: deprecated(msg, *, category=DeprecationWarning, stacklevel=1) - See :pep:`702`. In the :mod:`warnings` module since Python 3.13. + See :py:func:`warnings.deprecated` and :pep:`702`. In the :mod:`warnings` module + since Python 3.13. .. versionadded:: 4.5.0 From 944a3518699a38456bd91e434a363e46d2fb629d Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Sat, 30 Aug 2025 13:07:26 -0400 Subject: [PATCH 06/33] docs: add table of contents to sidebar (#666) --- doc/conf.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index db9b5185..a5655739 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -38,6 +38,23 @@ html_theme = 'alabaster' +html_theme_options = { + "description": "Backported and experimental type hints for Python", + # Make the sidebar "sticky" so that is stays visible when scrolling. + # Also makes the sidebar appear at the top of the page on mobile. + "fixed_sidebar": True, +} + +html_sidebars = { + '**': [ + 'about.html', + 'searchfield.html', + 'localtoc.html', + ] +} + +# Don't include object entries (e.g. functions, classes) in the table of contents. +toc_object_entries = False class MyTranslator(HTML5Translator): """Adds a link target to name without `typing_extensions.` prefix.""" From ccbfd9be42fdefcbba860b9a4a17e6a2b414e132 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Sat, 30 Aug 2025 13:56:47 -0400 Subject: [PATCH 07/33] Add some miscellaneous tests (#659) Co-authored-by: Alex Waygood Co-authored-by: Daraan --- CHANGELOG.md | 7 ++ src/test_typing_extensions.py | 143 ++++++++++++++++++++++++++++++---- src/typing_extensions.py | 17 ++-- 3 files changed, 144 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2e77c1f..3356adbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# Unreleased + +- Raise `TypeError` when attempting to subclass `typing_extensions.ParamSpec` on + Python 3.9. The `typing` implementation has always raised an error, and the + `typing_extensions` implementation has raised an error on Python 3.10+ since + `typing_extensions` v4.6.0. Patch by Brian Schubert. + # Release 4.15.0 (August 25, 2025) No user-facing changes since 4.15.0rc1. diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 0986427c..551579dc 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -531,6 +531,14 @@ def test_pickle(self): pickled = pickle.dumps(self.bottom_type, protocol=proto) self.assertIs(self.bottom_type, pickle.loads(pickled)) + @skipUnless(TYPING_3_10_0, "PEP 604 has yet to be") + def test_or(self): + self.assertEqual(self.bottom_type | int, Union[self.bottom_type, int]) + self.assertEqual(int | self.bottom_type, Union[int, self.bottom_type]) + + self.assertEqual(get_args(self.bottom_type | int), (self.bottom_type, int)) + self.assertEqual(get_args(int | self.bottom_type), (int, self.bottom_type)) + class NoReturnTests(BottomTypeTestsMixin, BaseTestCase): bottom_type = NoReturn @@ -2210,6 +2218,39 @@ def test_or_and_ror(self): Union[typing_extensions.Generator, typing.Deque] ) + def test_setattr(self): + origin = collections.abc.Generator + alias = typing_extensions.Generator + original_name = alias._name + + def cleanup(): + for obj in origin, alias: + for attr in 'foo', '__dunder__': + try: + delattr(obj, attr) + except Exception: + pass + try: + alias._name = original_name + except Exception: + pass + + self.addCleanup(cleanup) + + # Attribute assignment on generic alias sets attribute on origin + alias.foo = 1 + self.assertEqual(alias.foo, 1) + self.assertEqual(origin.foo, 1) + # Except for dunders... + alias.__dunder__ = 2 + self.assertEqual(alias.__dunder__, 2) + self.assertRaises(AttributeError, lambda: origin.__dunder__) + + # ...and certain known attributes + alias._name = "NewName" + self.assertEqual(alias._name, "NewName") + self.assertRaises(AttributeError, lambda: origin._name) + class OtherABCTests(BaseTestCase): @@ -2379,6 +2420,16 @@ def test_error_message_when_subclassing(self): class ProUserId(UserId): ... + def test_module_with_incomplete_sys(self): + def does_not_exist(*args): + raise AttributeError + with ( + patch("sys._getframemodulename", does_not_exist, create=True), + patch("sys._getframe", does_not_exist, create=True), + ): + X = NewType("X", int) + self.assertEqual(X.__module__, None) + class Coordinate(Protocol): x: int @@ -5297,6 +5348,17 @@ class A(TypedDict): def test_dunder_dict(self): self.assertIsInstance(TypedDict.__dict__, dict) + @skipUnless(TYPING_3_10_0, "PEP 604 has yet to be") + def test_or(self): + class TD(TypedDict): + a: int + + self.assertEqual(TD | int, Union[TD, int]) + self.assertEqual(int | TD, Union[int, TD]) + + self.assertEqual(get_args(TD | int), (TD, int)) + self.assertEqual(get_args(int | TD), (int, TD)) + class AnnotatedTests(BaseTestCase): def test_repr(self): @@ -5519,6 +5581,19 @@ def barfoo3(x: BA2): ... BA2 ) + @skipUnless(TYPING_3_11_0, "TODO: evaluate nested forward refs in Python < 3.11") + def test_get_type_hints_genericalias(self): + def foobar(x: list['X']): ... + X = Annotated[int, (1, 10)] + self.assertEqual( + get_type_hints(foobar, globals(), locals()), + {'x': list[int]} + ) + self.assertEqual( + get_type_hints(foobar, globals(), locals(), include_extras=True), + {'x': list[Annotated[int, (1, 10)]]} + ) + def test_get_type_hints_refs(self): Const = Annotated[T, "Const"] @@ -5973,6 +6048,11 @@ def run(): # The actual test: self.assertEqual(result1, result2) + def test_subclass(self): + with self.assertRaises(TypeError): + class MyParamSpec(ParamSpec): + pass + class ConcatenateTests(BaseTestCase): def test_basics(self): @@ -6335,6 +6415,14 @@ def test_pickle(self): pickled = pickle.dumps(LiteralString, protocol=proto) self.assertIs(LiteralString, pickle.loads(pickled)) + @skipUnless(TYPING_3_10_0, "PEP 604 has yet to be") + def test_or(self): + self.assertEqual(LiteralString | int, Union[LiteralString, int]) + self.assertEqual(int | LiteralString, Union[int, LiteralString]) + + self.assertEqual(get_args(LiteralString | int), (LiteralString, int)) + self.assertEqual(get_args(int | LiteralString), (int, LiteralString)) + class SelfTests(BaseTestCase): def test_basics(self): @@ -6382,6 +6470,14 @@ def test_pickle(self): pickled = pickle.dumps(Self, protocol=proto) self.assertIs(Self, pickle.loads(pickled)) + @skipUnless(TYPING_3_10_0, "PEP 604 has yet to be") + def test_or(self): + self.assertEqual(Self | int, Union[Self, int]) + self.assertEqual(int | Self, Union[int, Self]) + + self.assertEqual(get_args(Self | int), (Self, int)) + self.assertEqual(get_args(int | Self), (int, Self)) + class UnpackTests(BaseTestCase): def test_basic_plain(self): @@ -7711,42 +7807,61 @@ class A(Generic[T, P, U]): ... self.assertEqual(A[float, [range], int].__args__, (float, (range,), int)) -class NoDefaultTests(BaseTestCase): +class SentinelTestsMixin: @skip_if_py313_beta_1 def test_pickling(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): - s = pickle.dumps(NoDefault, proto) + s = pickle.dumps(self.sentinel_type, proto) loaded = pickle.loads(s) - self.assertIs(NoDefault, loaded) + self.assertIs(self.sentinel_type, loaded) @skip_if_py313_beta_1 def test_doc(self): - self.assertIsInstance(NoDefault.__doc__, str) + self.assertIsInstance(self.sentinel_type.__doc__, str) def test_constructor(self): - self.assertIs(NoDefault, type(NoDefault)()) + self.assertIs(self.sentinel_type, type(self.sentinel_type)()) with self.assertRaises(TypeError): - type(NoDefault)(1) - - def test_repr(self): - self.assertRegex(repr(NoDefault), r'typing(_extensions)?\.NoDefault') + type(self.sentinel_type)(1) def test_no_call(self): with self.assertRaises(TypeError): - NoDefault() + self.sentinel_type() @skip_if_py313_beta_1 def test_immutable(self): with self.assertRaises(AttributeError): - NoDefault.foo = 'bar' + self.sentinel_type.foo = 'bar' with self.assertRaises(AttributeError): - NoDefault.foo + self.sentinel_type.foo # TypeError is consistent with the behavior of NoneType with self.assertRaises(TypeError): - type(NoDefault).foo = 3 + type(self.sentinel_type).foo = 3 with self.assertRaises(AttributeError): - type(NoDefault).foo + type(self.sentinel_type).foo + + +class NoDefaultTests(SentinelTestsMixin, BaseTestCase): + sentinel_type = NoDefault + + def test_repr(self): + if hasattr(typing, 'NoDefault'): + mod_name = 'typing' + else: + mod_name = "typing_extensions" + self.assertEqual(repr(NoDefault), f"{mod_name}.NoDefault") + + +class NoExtraItemsTests(SentinelTestsMixin, BaseTestCase): + sentinel_type = NoExtraItems + + def test_repr(self): + if hasattr(typing, 'NoExtraItems'): + mod_name = 'typing' + else: + mod_name = "typing_extensions" + self.assertEqual(repr(NoExtraItems), f"{mod_name}.NoExtraItems") class TypeVarInferVarianceTests(BaseTestCase): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index c2ecc2fc..38592935 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -555,7 +555,9 @@ def _is_dunder(attr): class _SpecialGenericAlias(typing._SpecialGenericAlias, _root=True): - def __init__(self, origin, nparams, *, inst=True, name=None, defaults=()): + def __init__(self, origin, nparams, *, defaults, inst=True, name=None): + assert nparams > 0, "`nparams` must be a positive integer" + assert defaults, "Must always specify a non-empty sequence for `defaults`" super().__init__(origin, nparams, inst=inst, name=name) self._defaults = defaults @@ -573,20 +575,14 @@ def __getitem__(self, params): msg = "Parameters to generic types must be types." params = tuple(typing._type_check(p, msg) for p in params) if ( - self._defaults - and len(params) < self._nparams + len(params) < self._nparams and len(params) + len(self._defaults) >= self._nparams ): params = (*params, *self._defaults[len(params) - self._nparams:]) actual_len = len(params) if actual_len != self._nparams: - if self._defaults: - expected = f"at least {self._nparams - len(self._defaults)}" - else: - expected = str(self._nparams) - if not self._nparams: - raise TypeError(f"{self} is not a generic class") + expected = f"at least {self._nparams - len(self._defaults)}" raise TypeError( f"Too {'many' if actual_len > self._nparams else 'few'}" f" arguments for {self};" @@ -1960,6 +1956,9 @@ def __reduce__(self): def __call__(self, *args, **kwargs): pass + def __init_subclass__(cls) -> None: + raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type") + # 3.9 if not hasattr(typing, 'Concatenate'): From 66146e7e0d76b84eace166584d388a2fe6a030e2 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Tue, 2 Sep 2025 18:39:20 -0400 Subject: [PATCH 08/33] Fix cross-references for `ParamSpecArgs`/`ParamSpecKwargs` (#670) --- doc/index.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index 05cdaf9f..f7c19a18 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -312,10 +312,9 @@ Special typing primitives with :py:class:`typing.ParamSpec` on Python 3.13+. .. class:: ParamSpecArgs + ParamSpecKwargs -.. class:: ParamSpecKwargs - - See :py:class:`typing.ParamSpecArgs` and :py:class:`typing.ParamSpecKwargs`. + See :py:data:`typing.ParamSpecArgs` and :py:data:`typing.ParamSpecKwargs`. In ``typing`` since 3.10. .. class:: Protocol From d5feeb1fdc7c6cc455bf94336b531b6f842713e8 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Wed, 3 Sep 2025 08:16:25 -0400 Subject: [PATCH 09/33] docs: enable nitpicky mode (#671) --- doc/conf.py | 4 ++++ doc/index.rst | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index a5655739..cef3215d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -56,6 +56,10 @@ # Don't include object entries (e.g. functions, classes) in the table of contents. toc_object_entries = False +# Warn about all references where the target cannot be found. +nitpicky = True + + class MyTranslator(HTML5Translator): """Adds a link target to name without `typing_extensions.` prefix.""" def visit_desc_signature(self, node: Element) -> None: diff --git a/doc/index.rst b/doc/index.rst index f7c19a18..e6ceb360 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -303,7 +303,7 @@ Special typing primitives ``default=None`` is passed, and to :data:`NoDefault` if no value is passed. Previously, passing ``None`` would result in :attr:`!__default__` being set - to :py:class:`types.NoneType`, and passing no value for the parameter would + to :py:data:`types.NoneType`, and passing no value for the parameter would result in :attr:`!__default__` being set to ``None``. .. versionchanged:: 4.12.0 @@ -470,7 +470,7 @@ Special typing primitives ``TypedDict`` is now a function rather than a class. This brings ``typing_extensions.TypedDict`` closer to the implementation - of :py:mod:`typing.TypedDict` on Python 3.9 and higher. + of :py:class:`typing.TypedDict` on Python 3.9 and higher. .. versionchanged:: 4.7.0 @@ -525,7 +525,7 @@ Special typing primitives ``default=None`` is passed, and to :data:`NoDefault` if no value is passed. Previously, passing ``None`` would result in :attr:`!__default__` being set - to :py:class:`types.NoneType`, and passing no value for the parameter would + to :py:data:`types.NoneType`, and passing no value for the parameter would result in :attr:`!__default__` being set to ``None``. .. versionchanged:: 4.12.0 @@ -556,7 +556,7 @@ Special typing primitives ``default=None`` is passed, and to :data:`NoDefault` if no value is passed. Previously, passing ``None`` would result in :attr:`!__default__` being set - to :py:class:`types.NoneType`, and passing no value for the parameter would + to :py:data:`types.NoneType`, and passing no value for the parameter would result in :attr:`!__default__` being set to ``None``. .. versionchanged:: 4.12.0 @@ -798,7 +798,7 @@ Functions * Raises :exc:`TypeError` when it encounters certain objects that are not valid type hints. * Replaces type hints that evaluate to :const:`!None` with - :class:`types.NoneType`. + :data:`types.NoneType`. * Supports the :attr:`Format.FORWARDREF` and :attr:`Format.STRING` formats. @@ -1368,7 +1368,7 @@ versions of Python, but all are listed here for completeness. .. data:: Union - See :py:data:`typing.Union`. + See :py:class:`typing.Union`. .. versionadded:: 4.7.0 From 108f4484a345b71b6050bbcc7243a1d94cbc3dce Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Fri, 5 Sep 2025 17:19:49 -0400 Subject: [PATCH 10/33] Backport `__init_subclass__` fix for `@deprecated` from CPython (#673) --- CHANGELOG.md | 3 +++ src/test_typing_extensions.py | 19 +++++++++++++++++++ src/typing_extensions.py | 32 ++++++++++++++++---------------- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3356adbe..e9293e79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +- Fix `__init_subclass__()` behavior in the presence of multiple inheritance involving + an `@deprecated`-decorated base class. Backport of CPython PR + [#138210](https://github.com/python/cpython/pull/138210) by Brian Schubert. - Raise `TypeError` when attempting to subclass `typing_extensions.ParamSpec` on Python 3.9. The `typing` implementation has always raised an error, and the `typing_extensions` implementation has raised an error on Python 3.10+ since diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 551579dc..9047860e 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -813,6 +813,25 @@ class D(C, x=3): self.assertEqual(D.inited, 3) + def test_existing_init_subclass_in_sibling_base(self): + @deprecated("A will go away soon") + class A: + pass + class B: + def __init_subclass__(cls, x): + super().__init_subclass__() + cls.inited = x + + with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"): + class C(A, B, x=42): + pass + self.assertEqual(C.inited, 42) + + with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"): + class D(B, A, x=42): + pass + self.assertEqual(D.inited, 42) + def test_init_subclass_has_correct_cls(self): init_subclass_saw = None diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 38592935..73502af3 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2903,9 +2903,9 @@ def method(self) -> None: return arg -# Python 3.13.3+ contains a fix for the wrapped __new__ -# Breakpoint: https://github.com/python/cpython/pull/132160 -if sys.version_info >= (3, 13, 3): +# Python 3.13.8+ and 3.14.1+ contain a fix for the wrapped __init_subclass__ +# Breakpoint: https://github.com/python/cpython/pull/138210 +if ((3, 13, 8) <= sys.version_info < (3, 14)) or sys.version_info >= (3, 14, 1): deprecated = warnings.deprecated else: _T = typing.TypeVar("_T") @@ -2998,27 +2998,27 @@ def __new__(cls, /, *args, **kwargs): arg.__new__ = staticmethod(__new__) - original_init_subclass = arg.__init_subclass__ - # We need slightly different behavior if __init_subclass__ - # is a bound method (likely if it was implemented in Python) - if isinstance(original_init_subclass, MethodType): - original_init_subclass = original_init_subclass.__func__ + if "__init_subclass__" in arg.__dict__: + # __init_subclass__ is directly present on the decorated class. + # Synthesize a wrapper that calls this method directly. + original_init_subclass = arg.__init_subclass__ + # We need slightly different behavior if __init_subclass__ + # is a bound method (likely if it was implemented in Python). + # Otherwise, it likely means it's a builtin such as + # object's implementation of __init_subclass__. + if isinstance(original_init_subclass, MethodType): + original_init_subclass = original_init_subclass.__func__ @functools.wraps(original_init_subclass) def __init_subclass__(*args, **kwargs): warnings.warn(msg, category=category, stacklevel=stacklevel + 1) return original_init_subclass(*args, **kwargs) - - arg.__init_subclass__ = classmethod(__init_subclass__) - # Or otherwise, which likely means it's a builtin such as - # object's implementation of __init_subclass__. else: - @functools.wraps(original_init_subclass) - def __init_subclass__(*args, **kwargs): + def __init_subclass__(cls, *args, **kwargs): warnings.warn(msg, category=category, stacklevel=stacklevel + 1) - return original_init_subclass(*args, **kwargs) + return super(arg, cls).__init_subclass__(*args, **kwargs) - arg.__init_subclass__ = __init_subclass__ + arg.__init_subclass__ = classmethod(__init_subclass__) arg.__deprecated__ = __new__.__deprecated__ = msg __init_subclass__.__deprecated__ = msg From a240875016894ca9d241b24d158f544d9cc1c8ee Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Tue, 9 Sep 2025 11:13:47 -0400 Subject: [PATCH 11/33] Ensure `isinstance` is unaffected by `sys.setprofile` for `Concatenate[]` and `Unpack[]` aliases; run PyPy tests under coverage (#668) --- .github/workflows/ci.yml | 11 ----- CHANGELOG.md | 6 +++ src/test_typing_extensions.py | 81 +++++++++++++++++++++++++++++++++++ src/typing_extensions.py | 9 +++- 4 files changed, 94 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1059f458..4120c3e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,13 +67,11 @@ jobs: allow-prereleases: true - name: Install coverage - if: ${{ !startsWith(matrix.python-version, 'pypy') }} run: | # Be wary that this does not install typing_extensions in the future pip install coverage - name: Test typing_extensions with coverage - if: ${{ !startsWith(matrix.python-version, 'pypy') }} run: | # Be wary of running `pip install` here, since it becomes easy for us to # accidentally pick up typing_extensions as installed by a dependency @@ -82,18 +80,9 @@ jobs: # Run tests under coverage export COVERAGE_FILE=.coverage_${{ matrix.python-version }} python -m coverage run -m unittest test_typing_extensions.py - - name: Test typing_extensions no coverage on pypy - if: ${{ startsWith(matrix.python-version, 'pypy') }} - run: | - # Be wary of running `pip install` here, since it becomes easy for us to - # accidentally pick up typing_extensions as installed by a dependency - cd src - python --version # just to make sure we're running the right one - python -m unittest test_typing_extensions.py - name: Archive code coverage results id: archive-coverage - if: ${{ !startsWith(matrix.python-version, 'pypy') }} uses: actions/upload-artifact@v4 with: name: .coverage_${{ matrix.python-version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index e9293e79..733505a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Unreleased +- Fix incorrect behaviour on Python 3.9 and Python 3.10 that meant that + calling `isinstance` with `typing_extensions.Concatenate[...]` or + `typing_extensions.Unpack[...]` as the first argument could have a different + result in some situations depending on whether or not a profiling function had been + set using `sys.setprofile`. This affected both CPython and PyPy implementations. + Patch by Brian Schubert. - Fix `__init_subclass__()` behavior in the presence of multiple inheritance involving an `@deprecated`-decorated base class. Backport of CPython PR [#138210](https://github.com/python/cpython/pull/138210) by Brian Schubert. diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 9047860e..88fa699e 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -6230,6 +6230,47 @@ def test_is_param_expr(self): self.assertTrue(typing._is_param_expr(concat)) self.assertTrue(typing._is_param_expr(typing_concat)) + def test_isinstance_results_unaffected_by_presence_of_tracing_function(self): + # See https://github.com/python/typing_extensions/issues/661 + + code = textwrap.dedent( + """\ + import sys, typing + + def trace_call(*args): + return trace_call + + def run(): + sys.modules.pop("typing_extensions", None) + from typing_extensions import Concatenate + return isinstance(Concatenate[...], typing._GenericAlias) + isinstance_result_1 = run() + sys.setprofile(trace_call) + isinstance_result_2 = run() + sys.stdout.write(f"{isinstance_result_1} {isinstance_result_2}") + """ + ) + + # Run this in an isolated process or it pollutes the environment + # and makes other tests fail: + try: + proc = subprocess.run( + [sys.executable, "-c", code], check=True, capture_output=True, text=True, + ) + except subprocess.CalledProcessError as exc: + print("stdout", exc.stdout, sep="\n") + print("stderr", exc.stderr, sep="\n") + raise + + # Sanity checks that assert the test is working as expected + self.assertIsInstance(proc.stdout, str) + result1, result2 = proc.stdout.split(" ") + self.assertIn(result1, {"True", "False"}) + self.assertIn(result2, {"True", "False"}) + + # The actual test: + self.assertEqual(result1, result2) + class TypeGuardTests(BaseTestCase): def test_basics(self): TypeGuard[int] # OK @@ -6652,6 +6693,46 @@ def test_type_var_inheritance(self): self.assertFalse(isinstance(Unpack[Ts], TypeVar)) self.assertFalse(isinstance(Unpack[Ts], typing.TypeVar)) + def test_isinstance_results_unaffected_by_presence_of_tracing_function(self): + # See https://github.com/python/typing_extensions/issues/661 + + code = textwrap.dedent( + """\ + import sys, typing + + def trace_call(*args): + return trace_call + + def run(): + sys.modules.pop("typing_extensions", None) + from typing_extensions import TypeVarTuple, Unpack + return isinstance(Unpack[TypeVarTuple("Ts")], typing.TypeVar) + isinstance_result_1 = run() + sys.setprofile(trace_call) + isinstance_result_2 = run() + sys.stdout.write(f"{isinstance_result_1} {isinstance_result_2}") + """ + ) + + # Run this in an isolated process or it pollutes the environment + # and makes other tests fail: + try: + proc = subprocess.run( + [sys.executable, "-c", code], check=True, capture_output=True, text=True, + ) + except subprocess.CalledProcessError as exc: + print("stdout", exc.stdout, sep="\n") + print("stderr", exc.stderr, sep="\n") + raise + + # Sanity checks that assert the test is working as expected + self.assertIsInstance(proc.stdout, str) + result1, result2 = proc.stdout.split(" ") + self.assertIn(result1, {"True", "False"}) + self.assertIn(result2, {"True", "False"}) + + # The actual test: + self.assertEqual(result1, result2) class TypeVarTupleTests(BaseTestCase): diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 73502af3..bd67a80a 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1986,7 +1986,9 @@ class _ConcatenateGenericAlias(list): __class__ = typing._GenericAlias def __init__(self, origin, args): - super().__init__(args) + # Cannot use `super().__init__` here because of the `__class__` assignment + # in the class body (https://github.com/python/typing_extensions/issues/661) + list.__init__(self, args) self.__origin__ = origin self.__args__ = args @@ -2545,7 +2547,10 @@ def __typing_is_unpacked_typevartuple__(self): def __getitem__(self, args): if self.__typing_is_unpacked_typevartuple__: return args - return super().__getitem__(args) + # Cannot use `super().__getitem__` here because of the `__class__` assignment + # in the class body on Python <=3.11 + # (https://github.com/python/typing_extensions/issues/661) + return typing._GenericAlias.__getitem__(self, args) @_UnpackSpecialForm def Unpack(self, parameters): From 4f42e6bf0052129bc6dae5e71699a409652d2091 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Tue, 9 Sep 2025 12:04:23 -0400 Subject: [PATCH 12/33] Add Codecov step to CI (#674) --- .github/codecov.yml | 6 ++ .github/workflows/ci.yml | 121 +++++---------------------------------- 2 files changed, 21 insertions(+), 106 deletions(-) create mode 100644 .github/codecov.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 00000000..21acaf76 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,6 @@ +# Inherits global settings from https://app.codecov.io/account/gh/python/yaml/ +# TODO: enable status checks to fail CI if coverage drops? +# https://docs.codecov.com/docs/commit-status +comment: + # https://docs.codecov.com/docs/pull-request-comments + layout: "condensed_header, diff, flags, files" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4120c3e5..63a84a85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,8 +68,9 @@ jobs: - name: Install coverage run: | - # Be wary that this does not install typing_extensions in the future - pip install coverage + # Be wary that this does not install typing_extensions in the future. + # 'toml' extra is needed to read settings from pyproject.toml on Python <3.11 + pip install 'coverage[toml]' - name: Test typing_extensions with coverage run: | @@ -78,17 +79,10 @@ jobs: cd src python --version # just to make sure we're running the right one # Run tests under coverage - export COVERAGE_FILE=.coverage_${{ matrix.python-version }} python -m coverage run -m unittest test_typing_extensions.py + # Create xml file for Codecov + coverage xml --rcfile=../pyproject.toml --fail-under=0 - - name: Archive code coverage results - id: archive-coverage - uses: actions/upload-artifact@v4 - with: - name: .coverage_${{ matrix.python-version }} - path: ./src/.coverage* - include-hidden-files: true - compression-level: 0 # no compression - name: Test CPython typing test suite # Test suite fails on PyPy even without typing_extensions if: ${{ !startsWith(matrix.python-version, 'pypy') }} @@ -97,9 +91,16 @@ jobs: # Run the typing test suite from CPython with typing_extensions installed, # because we monkeypatch typing under some circumstances. python -c 'import typing_extensions; import test.__main__' test_typing -v - outputs: - # report if coverage was uploaded - cov_uploaded: ${{ steps.archive-coverage.outputs.artifact-id }} + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 + if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') }} + with: + token: ${{ secrets.CODECOV_ORG_TOKEN }} + flags: ${{ matrix.python-version }} + directory: src + fail_ci_if_error: true + verbose: true create-issue-on-failure: name: Create an issue if daily tests failed @@ -129,95 +130,3 @@ jobs: title: `Daily tests failed on ${new Date().toDateString()}`, body: "Runs listed here: https://github.com/python/typing_extensions/actions/workflows/ci.yml", }) - - report-coverage: - name: Report coverage - - runs-on: ubuntu-latest - - needs: [tests] - - permissions: - pull-requests: write - - # Job will run even if tests failed but only if at least one artifact was uploaded - if: ${{ always() && needs.tests.outputs.cov_uploaded != '' }} - - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3" - - name: Download coverage artifacts - uses: actions/download-artifact@v4 - with: - pattern: .coverage_* - path: . - # merge only when files are named differently - merge-multiple: true - - name: Install dependencies - run: pip install coverage - - name: Combine coverage results - run: | - # List the files to see what we have - echo "Combining coverage files..." - ls -aR .coverage* - coverage combine .coverage* - echo "Creating coverage report..." - # Create xml file for further processing; Create even if below minimum - coverage xml --fail-under=0 - # Write markdown report to job summary - coverage report --fail-under=0 --format=markdown -m >> "$GITHUB_STEP_SUMMARY" - - # For future use in case we want to add a PR comment for 3rd party PRs which requires - # a workflow with elevated PR write permissions. Move below steps into a separate job. - - name: Archive code coverage report - id: cov_xml_upload - uses: actions/upload-artifact@v4 - with: - name: coverage - path: coverage.xml - - name: Code Coverage Report (console) - run: | - # Create a coverage report (console), respects fail_under in pyproject.toml - coverage report - - - name: Code Coverage Report - uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0 - # Create markdown file even if coverage report fails due to fail_under - if: ${{ always() && steps.cov_xml_upload.outputs.artifact-id != '' }} - with: - filename: coverage.xml - badge: true - fail_below_min: true - format: markdown - hide_branch_rate: false - hide_complexity: true - indicators: true - output: both # console, file or both - # Note: it appears fail below min is one off, use fail_under -1 here - thresholds: '95 98' - - - name: Add link to report badge - if: ${{ always() && steps.cov_xml_upload.outputs.artifact-id != '' }} - run: | - run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}?pr=${{ github.event.pull_request.number }}" - sed -i "1s|^\(!.*\)$|[\1]($run_url)|" code-coverage-results.md - - - name: Add Coverage PR Comment - uses: marocchino/sticky-pull-request-comment@52423e01640425a022ef5fd42c6fb5f633a02728 # v2.9.3 - # Create PR comment when the branch is on the repo, otherwise we lack PR write permissions - # -> need another workflow with access to secret token - if: >- - ${{ - always() - && github.event_name == 'pull_request' - && github.event.pull_request.head.repo.full_name == github.repository - && steps.cov_xml_upload.outputs.artifact-id != '' - }} - with: - hide_and_recreate: true - path: code-coverage-results.md From 9215c953610ca4e4ce7ae840a0a804505da70a05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:13:03 +0200 Subject: [PATCH 13/33] Bump the actions group across 1 directory with 6 updates (#675) Bumps the actions group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `4` | `5` | | [actions/setup-python](https://github.com/actions/setup-python) | `5` | `6` | | [actions/github-script](https://github.com/actions/github-script) | `7` | `8` | | [actions/download-artifact](https://github.com/actions/download-artifact) | `4` | `5` | | [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) | `1.12.4` | `1.13.0` | | [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) | `6.4.3` | `6.6.1` | Updates `actions/checkout` from 4 to 5 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) Updates `actions/setup-python` from 5 to 6 - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) Updates `actions/github-script` from 7 to 8 - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) Updates `actions/download-artifact` from 4 to 5 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) Updates `pypa/gh-action-pypi-publish` from 1.12.4 to 1.13.0 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/76f52bc884231f62b9a034ebfe128415bbaabdfc...ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e) Updates `astral-sh/setup-uv` from 6.4.3 to 6.6.1 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/e92bafb6253dcd438e0484186d7669ea7a8ca1cc...557e51de59eb14aaaba2ed9621916900a91d50c6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: pypa/gh-action-pypi-publish dependency-version: 1.13.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions - dependency-name: astral-sh/setup-uv dependency-version: 6.6.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 ++--- .github/workflows/publish.yml | 26 ++++++++++----------- .github/workflows/third_party.yml | 38 +++++++++++++++---------------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63a84a85..94ffd5b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,12 +56,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -120,7 +120,7 @@ jobs: issues: write steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e078218f..a324ad20 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,11 +23,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.x" - name: Check package metadata @@ -55,15 +55,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: python-package-distributions path: dist/ @@ -84,15 +84,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: python-package-distributions path: dist/ @@ -112,15 +112,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: persist-credentials: false - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: python-package-distributions path: dist/ @@ -152,11 +152,11 @@ jobs: steps: - name: Download all the dists - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: python-package-distributions path: dist/ - name: Ensure exactly one sdist and one wheel have been downloaded run: test "$(find dist/*.tar.gz | wc -l | xargs)" = 1 && test "$(find dist/*.whl | wc -l | xargs)" = 1 - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index f20ffd65..a7e448ab 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -51,13 +51,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 with: python-version: ${{ matrix.python-version }} - name: Checkout pydantic run: git clone --depth=1 https://github.com/pydantic/pydantic.git || git clone --depth=1 https://github.com/pydantic/pydantic.git - name: Checkout typing_extensions - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: typing-extensions-latest persist-credentials: false @@ -84,13 +84,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 with: python-version: ${{ matrix.python-version }} - name: Checkout typing_inspect run: git clone --depth=1 https://github.com/ilevkivskyi/typing_inspect.git || git clone --depth=1 https://github.com/ilevkivskyi/typing_inspect.git - name: Checkout typing_extensions - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: typing-extensions-latest persist-credentials: false @@ -119,13 +119,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 with: python-version: ${{ matrix.python-version }} - name: Check out pycroscope run: git clone --depth=1 https://github.com/JelleZijlstra/pycroscope.git || git clone --depth=1 https://github.com/JelleZijlstra/pycroscope.git - name: Checkout typing_extensions - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: typing-extensions-latest persist-credentials: false @@ -154,13 +154,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 with: python-version: ${{ matrix.python-version }} - name: Check out typeguard run: git clone --depth=1 https://github.com/agronholm/typeguard.git || git clone --depth=1 https://github.com/agronholm/typeguard.git - name: Checkout typing_extensions - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: typing-extensions-latest persist-credentials: false @@ -191,13 +191,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 with: python-version: ${{ matrix.python-version }} - name: Check out typed-argument-parser run: git clone --depth=1 https://github.com/swansonk14/typed-argument-parser.git || git clone --depth=1 https://github.com/swansonk14/typed-argument-parser.git - name: Checkout typing_extensions - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: typing-extensions-latest persist-credentials: false @@ -233,13 +233,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 with: python-version: ${{ matrix.python-version }} - name: Checkout mypy for stubtest and mypyc tests run: git clone --depth=1 https://github.com/python/mypy.git || git clone --depth=1 https://github.com/python/mypy.git - name: Checkout typing_extensions - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: typing-extensions-latest persist-credentials: false @@ -269,13 +269,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 with: python-version: ${{ matrix.python-version }} - name: Checkout cattrs run: git clone --depth=1 https://github.com/python-attrs/cattrs.git || git clone --depth=1 https://github.com/python-attrs/cattrs.git - name: Checkout typing_extensions - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: typing-extensions-latest persist-credentials: false @@ -307,13 +307,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 with: python-version: ${{ matrix.python-version }} - name: Checkout sqlalchemy run: git clone -b ${{ matrix.checkout-ref }} --depth=1 https://github.com/sqlalchemy/sqlalchemy.git || git clone -b ${{ matrix.checkout-ref }} --depth=1 https://github.com/sqlalchemy/sqlalchemy.git - name: Checkout typing_extensions - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: typing-extensions-latest persist-credentials: false @@ -338,13 +338,13 @@ jobs: python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3 + uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 with: python-version: ${{ matrix.python-version }} - name: Checkout litestar run: git clone --depth=1 https://github.com/litestar-org/litestar.git || git clone --depth=1 https://github.com/litestar-org/litestar.git - name: Checkout typing_extensions - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: path: typing-extensions-latest persist-credentials: false @@ -399,7 +399,7 @@ jobs: issues: write steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From 64a6cad1b649d04714b68a63f3db61216ca43f65 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Mon, 15 Sep 2025 17:07:28 +0200 Subject: [PATCH 14/33] Fix doctest for get_protocol_members (#680) --- src/test_typing_extensions.py | 4 ++++ src/typing_extensions.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 88fa699e..27f4059c 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -9574,6 +9574,10 @@ def test_sentinel_not_picklable(self): ): pickle.dumps(sentinel) +def load_tests(loader, tests, pattern): + import doctest + tests.addTests(doctest.DocTestSuite(typing_extensions)) + return tests if __name__ == '__main__': # pragma: no cover main() diff --git a/src/typing_extensions.py b/src/typing_extensions.py index bd67a80a..03b72e3b 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -3864,8 +3864,8 @@ def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]: >>> class P(Protocol): ... def a(self) -> str: ... ... b: int - >>> get_protocol_members(P) - frozenset({'a', 'b'}) + >>> get_protocol_members(P) == frozenset({'a', 'b'}) + True Raise a TypeError for arguments that are not Protocols. """ From 16cc1566a6a4b8e729e8b91e1a4e6a31526823a0 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Fri, 19 Sep 2025 07:22:40 -0700 Subject: [PATCH 15/33] fix test on 3.14 (#683) --- src/test_typing_extensions.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 27f4059c..f07e1eb0 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -4508,8 +4508,12 @@ def _make_td(future, class_name, annos, base, extra_names=None): child = _make_td( child_future, "Child", {"child": "int"}, "Base", {"Base": base} ) - base_anno = typing.ForwardRef("int", module="builtins") if base_future else int - child_anno = typing.ForwardRef("int", module="builtins") if child_future else int + if sys.version_info >= (3, 14): + base_anno = typing.ForwardRef("int", module="builtins", owner=base) if base_future else int + child_anno = typing.ForwardRef("int", module="builtins", owner=child) if child_future else int + else: + base_anno = typing.ForwardRef("int", module="builtins") if base_future else int + child_anno = typing.ForwardRef("int", module="builtins") if child_future else int self.assertEqual(base.__annotations__, {'base': base_anno}) self.assertEqual( child.__annotations__, {'child': child_anno, 'base': base_anno} From ca8388304d4ea14e9ecb010a0f03d3dd28eecdfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 08:41:53 +0200 Subject: [PATCH 16/33] Bump astral-sh/setup-uv from 6.6.1 to 6.8.0 in the actions group (#684) --- .github/workflows/third_party.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index a7e448ab..71c56303 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -51,7 +51,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 with: python-version: ${{ matrix.python-version }} - name: Checkout pydantic @@ -84,7 +84,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 with: python-version: ${{ matrix.python-version }} - name: Checkout typing_inspect @@ -119,7 +119,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 with: python-version: ${{ matrix.python-version }} - name: Check out pycroscope @@ -154,7 +154,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 with: python-version: ${{ matrix.python-version }} - name: Check out typeguard @@ -191,7 +191,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 with: python-version: ${{ matrix.python-version }} - name: Check out typed-argument-parser @@ -233,7 +233,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 with: python-version: ${{ matrix.python-version }} - name: Checkout mypy for stubtest and mypyc tests @@ -269,7 +269,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 with: python-version: ${{ matrix.python-version }} - name: Checkout cattrs @@ -307,7 +307,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 with: python-version: ${{ matrix.python-version }} - name: Checkout sqlalchemy @@ -338,7 +338,7 @@ jobs: python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1 + uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 with: python-version: ${{ matrix.python-version }} - name: Checkout litestar From 785c7145872f6bf7e27a189b4261d425f1932e9b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:21:39 +0100 Subject: [PATCH 17/33] [pre-commit.ci] pre-commit autoupdate (#685) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.3 → v0.13.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.3...v0.13.3) - [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0) - [github.com/python-jsonschema/check-jsonschema: 0.33.2 → 0.34.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.33.2...0.34.0) - [github.com/woodruffw/zizmor-pre-commit: v1.11.0 → v1.14.2](https://github.com/woodruffw/zizmor-pre-commit/compare/v1.11.0...v1.14.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf8fd54d..40ab76d7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,10 +1,10 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.3 + rev: v0.13.3 hooks: - id: ruff - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -21,7 +21,7 @@ repos: hooks: - id: sphinx-lint - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.33.2 + rev: 0.34.0 hooks: - id: check-dependabot - id: check-github-workflows @@ -41,7 +41,7 @@ repos: # but the integration only works if shellcheck is installed - "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0" - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.11.0 + rev: v1.14.2 hooks: - id: zizmor - repo: meta From 420f8f2e1b9a2ea6c63597856ecc78455724614f Mon Sep 17 00:00:00 2001 From: DetachHead <57028336+DetachHead@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:16:44 +1000 Subject: [PATCH 18/33] update comments about `TypeForm` since it has been deferred to python 3.15 (#687) see https://peps.python.org/pep-0747/ --- src/typing_extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 03b72e3b..ca1705e4 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -2291,10 +2291,10 @@ def f(val: Union[int, Awaitable[int]]) -> int: return typing._GenericAlias(self, (item,)) -# 3.14+? +# 3.15+? if hasattr(typing, 'TypeForm'): TypeForm = typing.TypeForm -# <=3.13 +# <=3.14 else: class _TypeFormForm(_ExtensionsSpecialForm, _root=True): # TypeForm(X) is equivalent to X but indicates to the type checker From cf95a17fbd4ea2f9d70c0e644cef40577496d7b7 Mon Sep 17 00:00:00 2001 From: Daniel Sperber Date: Sun, 19 Oct 2025 11:28:58 +0200 Subject: [PATCH 19/33] Failing pre-commit: Remove *removed* ruff rule UP038 (#691) Remove ruff UP038 --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e1775876..b024621d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,7 +90,6 @@ ignore = [ "UP014", "UP019", "UP035", - "UP038", "UP045", # X | None instead of Optional[X] # Not relevant here "RUF012", # Use ClassVar for mutables From 327ec484256637583d91421383adccd3ff9a8a5e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 19 Oct 2025 06:26:43 -0700 Subject: [PATCH 20/33] Drop typed-argument-parser 3.9 tests, add 3.14 (#690) Fixes #689. 3.9 is about to lose support and typed-argument-parser evidently already dropped support. I took the opportunity to add 3.14 to all the third-party tests now that 3.14.0 is out, but had to disable a few that don't pass yet. I also added a missing line of code for litestar. --- .github/workflows/third_party.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index 71c56303..c4619ecc 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -46,6 +46,7 @@ jobs: # PyPy is deliberately omitted here, # since pydantic's tests intermittently segfault on PyPy, # and it's nothing to do with typing_extensions + # Tests on 3.14 don't pass as of 18 October 2025 python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] runs-on: ubuntu-latest timeout-minutes: 60 @@ -79,7 +80,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] # 3.14 is not yet supported runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -114,7 +115,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -149,7 +150,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -186,7 +187,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + # 3.9 is no longer supported. 3.14 fails some tests as of 18 October 2025 + python-version: ["3.10", "3.11", "3.12", "3.13"] runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -228,6 +230,7 @@ jobs: strategy: fail-fast: false matrix: + # As of 18 October 2025, 3.14 fails a test python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] runs-on: ubuntu-latest timeout-minutes: 60 @@ -264,7 +267,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -299,7 +302,7 @@ jobs: matrix: # PyPy is deliberately omitted here, since SQLAlchemy's tests # fail on PyPy for reasons unrelated to typing_extensions. - python-version: [ "3.10", "3.11", "3.12", "3.13" ] + python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ] checkout-ref: [ "main", "rel_2_0" ] # sqlalchemy tests fail when using the Ubuntu 24.04 runner # https://github.com/sqlalchemy/sqlalchemy/commit/8d73205f352e68c6603e90494494ef21027ec68f @@ -335,6 +338,7 @@ jobs: strategy: fail-fast: false matrix: + # As of 18 October 2025 a dependency is missing 3.14 wheels python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] steps: - name: Install the latest version of uv @@ -377,6 +381,7 @@ jobs: - mypy - cattrs - sqlalchemy + - litestar if: >- ${{ @@ -392,6 +397,7 @@ jobs: || needs.mypy.result == 'failure' || needs.cattrs.result == 'failure' || needs.sqlalchemy.result == 'failure' + || needs.litestar.result == 'failure' ) }} From ca6bfde43046629effa9aa3e1391fb42bf7d572b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 1 Nov 2025 19:27:30 -0400 Subject: [PATCH 21/33] Bump the actions group with 3 updates (#692) Bumps the actions group with 3 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact), [actions/download-artifact](https://github.com/actions/download-artifact) and [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv). Updates `actions/upload-artifact` from 4 to 5 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) Updates `actions/download-artifact` from 5 to 6 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v6) Updates `astral-sh/setup-uv` from 6.8.0 to 7.1.2 - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/d0cc045d04ccac9d8b7881df0226f9e82c39688e...85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: astral-sh/setup-uv dependency-version: 7.1.2 dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish.yml | 10 +++++----- .github/workflows/third_party.yml | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a324ad20..9aa8efff 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,7 +43,7 @@ jobs: - name: Build a binary wheel and a source tarball run: python -m build - name: Store the distribution packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: python-package-distributions path: dist/ @@ -63,7 +63,7 @@ jobs: with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: python-package-distributions path: dist/ @@ -92,7 +92,7 @@ jobs: with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: python-package-distributions path: dist/ @@ -120,7 +120,7 @@ jobs: with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: python-package-distributions path: dist/ @@ -152,7 +152,7 @@ jobs: steps: - name: Download all the dists - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: python-package-distributions path: dist/ diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index c4619ecc..c50269df 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -52,7 +52,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: python-version: ${{ matrix.python-version }} - name: Checkout pydantic @@ -85,7 +85,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: python-version: ${{ matrix.python-version }} - name: Checkout typing_inspect @@ -120,7 +120,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: python-version: ${{ matrix.python-version }} - name: Check out pycroscope @@ -155,7 +155,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: python-version: ${{ matrix.python-version }} - name: Check out typeguard @@ -193,7 +193,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: python-version: ${{ matrix.python-version }} - name: Check out typed-argument-parser @@ -236,7 +236,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: python-version: ${{ matrix.python-version }} - name: Checkout mypy for stubtest and mypyc tests @@ -272,7 +272,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: python-version: ${{ matrix.python-version }} - name: Checkout cattrs @@ -310,7 +310,7 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: python-version: ${{ matrix.python-version }} - name: Checkout sqlalchemy @@ -342,7 +342,7 @@ jobs: python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0 + uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 with: python-version: ${{ matrix.python-version }} - name: Checkout litestar From 196082138aebe36af7f5a1d08691b84dab950eea Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 2 Nov 2025 21:58:37 -0800 Subject: [PATCH 22/33] Drop 3.9 third-party tests (#694) Also try to see if any of the previously failing 3.14 tests started working. Fixes #693 --- .github/workflows/third_party.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index c50269df..c569c9f4 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -46,8 +46,8 @@ jobs: # PyPy is deliberately omitted here, # since pydantic's tests intermittently segfault on PyPy, # and it's nothing to do with typing_extensions - # Tests on 3.14 don't pass as of 18 October 2025 - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + # Tests on 3.14 don't pass as of 2 November 2025 + python-version: ["3.10", "3.11", "3.12", "3.13"] runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -80,7 +80,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] # 3.14 is not yet supported + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -115,7 +115,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -150,7 +150,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -187,7 +187,7 @@ jobs: strategy: fail-fast: false matrix: - # 3.9 is no longer supported. 3.14 fails some tests as of 18 October 2025 + # 3.9 is no longer supported. 3.14 fails some tests as of 2 November 2025 python-version: ["3.10", "3.11", "3.12", "3.13"] runs-on: ubuntu-latest timeout-minutes: 60 @@ -230,8 +230,8 @@ jobs: strategy: fail-fast: false matrix: - # As of 18 October 2025, 3.14 fails a test - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + # 3.14 fails a test as of 2 November 2025 + python-version: ["3.10", "3.11", "3.12", "3.13"] runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -267,7 +267,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] runs-on: ubuntu-latest timeout-minutes: 60 steps: @@ -338,8 +338,8 @@ jobs: strategy: fail-fast: false matrix: - # As of 18 October 2025 a dependency is missing 3.14 wheels - python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] + # As of 2 November 2025 a dependency is missing 3.14 wheels + python-version: [ "3.10", "3.11", "3.12", "3.13" ] steps: - name: Install the latest version of uv uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 @@ -357,7 +357,7 @@ jobs: run: | # litestar's python-requires means uv won't let us add typing-extensions-latest # as a requirement unless we do this - sed -i 's/^requires-python = ">=3.8/requires-python = ">=3.9/' pyproject.toml + sed -i 's/^requires-python = ">=3.8/requires-python = ">=3.10/' pyproject.toml uv add --editable ../typing-extensions-latest uv sync From f684c9a36b8490985bf285404810a65ef6fdfc78 Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Tue, 11 Nov 2025 16:17:39 +0100 Subject: [PATCH 23/33] Fix Pydantic third-party test (#697) --- .github/workflows/third_party.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index c569c9f4..26ad56db 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -66,7 +66,7 @@ jobs: working-directory: pydantic run: | uv add --editable ../typing-extensions-latest - uv sync --group dev + uv sync --all-packages --group testing-extra --all-extras printf "\n\nINSTALLED DEPENDENCIES ARE:\n\n" uv pip list From 2638b86aad48a3d25704caac9d7e37368fd87815 Mon Sep 17 00:00:00 2001 From: Semyon Moroz Date: Tue, 18 Nov 2025 23:45:35 +0400 Subject: [PATCH 24/33] Remove `no_type_check_decorator` from `__all__` for Python >= 3.15 (#699) --- src/typing_extensions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index ca1705e4..9dd8eac4 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -148,7 +148,6 @@ 'ValuesView', 'cast', 'no_type_check', - 'no_type_check_decorator', ] # for backward compatibility @@ -4312,3 +4311,7 @@ def type_repr(value): Generic = typing.Generic ForwardRef = typing.ForwardRef Annotated = typing.Annotated + +# Breakpoint: https://github.com/python/cpython/pull/133602 +if sys.version_info < (3, 15, 0): + __all__.append("no_type_check_decorator") From 19cc3bc38603e284716cfd1047a0679ec3aefc5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 08:44:30 +0100 Subject: [PATCH 25/33] Update GitHub actions setup-uv and checkout (#703) --- .github/workflows/ci.yml | 2 +- .github/workflows/publish.yml | 8 +++---- .github/workflows/third_party.yml | 36 +++++++++++++++---------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94ffd5b9..9922826f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9aa8efff..898c3e7c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Set up Python @@ -55,7 +55,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Set up Python @@ -84,7 +84,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Set up Python @@ -112,7 +112,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false - name: Set up Python diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index 26ad56db..573aaef8 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -52,13 +52,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 with: python-version: ${{ matrix.python-version }} - name: Checkout pydantic run: git clone --depth=1 https://github.com/pydantic/pydantic.git || git clone --depth=1 https://github.com/pydantic/pydantic.git - name: Checkout typing_extensions - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: typing-extensions-latest persist-credentials: false @@ -85,13 +85,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 with: python-version: ${{ matrix.python-version }} - name: Checkout typing_inspect run: git clone --depth=1 https://github.com/ilevkivskyi/typing_inspect.git || git clone --depth=1 https://github.com/ilevkivskyi/typing_inspect.git - name: Checkout typing_extensions - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: typing-extensions-latest persist-credentials: false @@ -120,13 +120,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 with: python-version: ${{ matrix.python-version }} - name: Check out pycroscope run: git clone --depth=1 https://github.com/JelleZijlstra/pycroscope.git || git clone --depth=1 https://github.com/JelleZijlstra/pycroscope.git - name: Checkout typing_extensions - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: typing-extensions-latest persist-credentials: false @@ -155,13 +155,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 with: python-version: ${{ matrix.python-version }} - name: Check out typeguard run: git clone --depth=1 https://github.com/agronholm/typeguard.git || git clone --depth=1 https://github.com/agronholm/typeguard.git - name: Checkout typing_extensions - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: typing-extensions-latest persist-credentials: false @@ -193,13 +193,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 with: python-version: ${{ matrix.python-version }} - name: Check out typed-argument-parser run: git clone --depth=1 https://github.com/swansonk14/typed-argument-parser.git || git clone --depth=1 https://github.com/swansonk14/typed-argument-parser.git - name: Checkout typing_extensions - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: typing-extensions-latest persist-credentials: false @@ -236,13 +236,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 with: python-version: ${{ matrix.python-version }} - name: Checkout mypy for stubtest and mypyc tests run: git clone --depth=1 https://github.com/python/mypy.git || git clone --depth=1 https://github.com/python/mypy.git - name: Checkout typing_extensions - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: typing-extensions-latest persist-credentials: false @@ -272,13 +272,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 with: python-version: ${{ matrix.python-version }} - name: Checkout cattrs run: git clone --depth=1 https://github.com/python-attrs/cattrs.git || git clone --depth=1 https://github.com/python-attrs/cattrs.git - name: Checkout typing_extensions - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: typing-extensions-latest persist-credentials: false @@ -310,13 +310,13 @@ jobs: timeout-minutes: 60 steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 with: python-version: ${{ matrix.python-version }} - name: Checkout sqlalchemy run: git clone -b ${{ matrix.checkout-ref }} --depth=1 https://github.com/sqlalchemy/sqlalchemy.git || git clone -b ${{ matrix.checkout-ref }} --depth=1 https://github.com/sqlalchemy/sqlalchemy.git - name: Checkout typing_extensions - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: typing-extensions-latest persist-credentials: false @@ -342,13 +342,13 @@ jobs: python-version: [ "3.10", "3.11", "3.12", "3.13" ] steps: - name: Install the latest version of uv - uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2 + uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 with: python-version: ${{ matrix.python-version }} - name: Checkout litestar run: git clone --depth=1 https://github.com/litestar-org/litestar.git || git clone --depth=1 https://github.com/litestar-org/litestar.git - name: Checkout typing_extensions - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: path: typing-extensions-latest persist-credentials: false From 9cb611b865bf4b74114df63b3963fb398302de69 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Mon, 29 Dec 2025 22:12:13 +0000 Subject: [PATCH 26/33] Bump shellcheck pin (#713) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40ab76d7..cdf86f30 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,7 @@ repos: # actionlint has a shellcheck integration which extracts shell scripts in `run:` steps from GitHub Actions # and checks these with shellcheck. This is arguably its most useful feature, # but the integration only works if shellcheck is installed - - "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0" + - "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.1" - repo: https://github.com/woodruffw/zizmor-pre-commit rev: v1.14.2 hooks: From 3f266a4f80581f9da46e46d31cbf4a5fa443a725 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:01:55 +0000 Subject: [PATCH 27/33] [pre-commit.ci] pre-commit autoupdate (#716) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Alex Waygood --- .github/dependabot.yml | 2 ++ .pre-commit-config.yaml | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5c563144..2e4b9645 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,3 +8,5 @@ updates: actions: patterns: - "*" + cooldown: + default-days: 7 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cdf86f30..2a3aedee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.3 + rev: v0.14.10 hooks: - id: ruff - repo: https://github.com/pre-commit/pre-commit-hooks @@ -17,11 +17,11 @@ repos: - id: mixed-line-ending args: [--fix=lf] - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v1.0.0 + rev: v1.0.2 hooks: - id: sphinx-lint - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.34.0 + rev: 0.36.0 hooks: - id: check-dependabot - id: check-github-workflows @@ -32,7 +32,7 @@ repos: - id: validate-pyproject additional_dependencies: ["validate-pyproject-schema-store[all]"] - repo: https://github.com/rhysd/actionlint - rev: v1.7.7 + rev: v1.7.10 hooks: - id: actionlint additional_dependencies: @@ -41,7 +41,7 @@ repos: # but the integration only works if shellcheck is installed - "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.1" - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.14.2 + rev: v1.19.0 hooks: - id: zizmor - repo: meta From a7610ef567132cac2b5319fa193c830b655364c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:09:07 +0000 Subject: [PATCH 28/33] Bump the actions group across 1 directory with 3 updates (#719) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/publish.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9922826f..199f3563 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,7 +93,7 @@ jobs: python -c 'import typing_extensions; import test.__main__' test_typing -v - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') }} with: token: ${{ secrets.CODECOV_ORG_TOKEN }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 898c3e7c..d0f5d464 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,7 +43,7 @@ jobs: - name: Build a binary wheel and a source tarball run: python -m build - name: Store the distribution packages - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: python-package-distributions path: dist/ @@ -63,7 +63,7 @@ jobs: with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: python-package-distributions path: dist/ @@ -92,7 +92,7 @@ jobs: with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: python-package-distributions path: dist/ @@ -120,7 +120,7 @@ jobs: with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: python-package-distributions path: dist/ @@ -152,7 +152,7 @@ jobs: steps: - name: Download all the dists - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: python-package-distributions path: dist/ From 2f9df178ebe137f6a1d817c026ac50a2be1c5ed3 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 27 Jan 2026 21:37:47 -0800 Subject: [PATCH 29/33] docs: add security implications of annotations (#727) --- doc/index.rst | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/doc/index.rst b/doc/index.rst index e6ceb360..66577ef0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -815,6 +815,11 @@ Functions *format* specifies the format of the annotation and is a member of the :class:`Format` enum, defaulting to :attr:`Format.VALUE`. + .. caution:: + + This function may execute arbitrary code contained in annotations. + See :ref:`annotations-security` for more information. + .. versionadded:: 4.13.0 .. function:: get_annotations(obj, *, globals=None, locals=None, eval_str=False, format=Format.VALUE) @@ -834,6 +839,11 @@ Functions typing_extensions.get_annotations(obj, format=Format.FORWARDREF) + .. caution:: + + This function may execute arbitrary code contained in annotations. + See :ref:`annotations-security` for more information. + .. versionadded:: 4.13.0 .. function:: get_args(tp) @@ -901,6 +911,11 @@ Functions :py:data:`typing.Required` and :py:data:`typing.NotRequired`. ``typing_extensions`` backports these fixes. + .. caution:: + + This function may execute arbitrary code contained in annotations. + See :ref:`annotations-security` for more information. + .. versionchanged:: 4.1.0 Interaction with :data:`Required` and :data:`NotRequired`. @@ -1415,3 +1430,25 @@ If you have any feedback on our security process, please `open an issue `__. To report an issue privately, use `GitHub's private reporting feature `__. + +.. _annotations-security: + +Introspection of annotations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some functions in this module are designed to introspect annotations at +runtime. These functions may therefore execute code contained in annotations, +which can then perform arbitrary operations. For example, +:func:`get_annotations` may call an arbitrary :term:`annotate function`, and +:meth:`evaluate_forward_ref` may call :func:`eval` on an arbitrary string. Code contained +in an annotation might make arbitrary system calls, enter an infinite loop, or perform any +other operation. This is also true for any access of the :attr:`~object.__annotations__` attribute +(as of Python 3.14), +and for various functions in the :mod:`typing` module that work with annotations, such as +:func:`typing.get_type_hints`. + +Any security issue arising from this also applies immediately after importing +code that may contain untrusted annotations: importing code can always cause arbitrary operations +to be performed. However, it is unsafe to accept strings or other input from an untrusted source and +pass them to any of the APIs for introspecting annotations, for example by editing an +``__annotations__`` dictionary or directly creating a :class:`ForwardRef` object. From 442d8484f845f8863643ef490d81b82ec91d7963 Mon Sep 17 00:00:00 2001 From: Daniel Sperber Date: Mon, 9 Feb 2026 22:15:38 +0100 Subject: [PATCH 30/33] Remove no_type_check_decorator from _typing_names (#723) Co-authored-by: Alex Waygood --- src/typing_extensions.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 9dd8eac4..20c331ee 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -4298,11 +4298,16 @@ def type_repr(value): "ValuesView", "cast", "no_type_check", - "no_type_check_decorator", # This is private, but it was defined by typing_extensions for a long time # and some users rely on it. "_AnnotatedAlias", ] + +# Breakpoint: https://github.com/python/cpython/pull/133602 +if sys.version_info < (3, 15, 0): + _typing_names.append("no_type_check_decorator") + __all__.append("no_type_check_decorator") + globals().update( {name: getattr(typing, name) for name in _typing_names if hasattr(typing, name)} ) @@ -4311,7 +4316,3 @@ def type_repr(value): Generic = typing.Generic ForwardRef = typing.ForwardRef Annotated = typing.Annotated - -# Breakpoint: https://github.com/python/cpython/pull/133602 -if sys.version_info < (3, 15, 0): - __all__.append("no_type_check_decorator") From aa25c96a7c604fe20995cd6c0d28be829a570c9c Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Mon, 23 Mar 2026 05:38:19 -0400 Subject: [PATCH 31/33] Skip Codecov upload step on forks (#731) --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 199f3563..690f8c7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,9 @@ jobs: - name: Upload coverage reports to Codecov uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de - if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') }} + if: >- + github.repository == 'python/typing_extensions' + && (github.event_name == 'push' || github.event_name == 'pull_request') with: token: ${{ secrets.CODECOV_ORG_TOKEN }} flags: ${{ matrix.python-version }} From 58ad6e347e313773cd3dc4ba02d1d2563db775f0 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Sat, 28 Mar 2026 01:45:30 -0700 Subject: [PATCH 32/33] Fix litestar third party test (#737) Looks like it dropped support for 3.10 Fixes #736 --- .github/workflows/third_party.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/third_party.yml b/.github/workflows/third_party.yml index 573aaef8..0894c2d1 100644 --- a/.github/workflows/third_party.yml +++ b/.github/workflows/third_party.yml @@ -339,7 +339,7 @@ jobs: fail-fast: false matrix: # As of 2 November 2025 a dependency is missing 3.14 wheels - python-version: [ "3.10", "3.11", "3.12", "3.13" ] + python-version: [ "3.11", "3.12", "3.13" ] steps: - name: Install the latest version of uv uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 From 83caa5908b408560b7e30d60052c2e4da31b0556 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 07:58:27 +0200 Subject: [PATCH 33/33] Bump the actions group with 2 updates (#738) --- .github/workflows/publish.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d0f5d464..3d5fdcef 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,7 +43,7 @@ jobs: - name: Build a binary wheel and a source tarball run: python -m build - name: Store the distribution packages - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: python-package-distributions path: dist/ @@ -63,7 +63,7 @@ jobs: with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: python-package-distributions path: dist/ @@ -92,7 +92,7 @@ jobs: with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: python-package-distributions path: dist/ @@ -120,7 +120,7 @@ jobs: with: python-version: "3.x" - name: Download all the dists - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: python-package-distributions path: dist/ @@ -152,7 +152,7 @@ jobs: steps: - name: Download all the dists - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: python-package-distributions path: dist/