From 9d9d3df8b70d3354923920755566157e08ab9f25 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 21 May 2026 16:24:54 +0300 Subject: [PATCH 1/6] gh-105578: Raise `DeprecationWarning` on `typing.AnyStr` usage --- Doc/deprecations/pending-removal-in-3.18.rst | 6 +++++ Doc/library/typing.rst | 4 ++-- Doc/whatsnew/3.16.rst | 4 ++++ Lib/test/test_typing.py | 24 +++++++++++++++---- Lib/typing.py | 17 ++++++------- ...-05-21-16-15-01.gh-issue-105578.oq-0pg.rst | 2 ++ 6 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-05-21-16-15-01.gh-issue-105578.oq-0pg.rst diff --git a/Doc/deprecations/pending-removal-in-3.18.rst b/Doc/deprecations/pending-removal-in-3.18.rst index 19113aab981bbc6..3dbcea42d3f69c9 100644 --- a/Doc/deprecations/pending-removal-in-3.18.rst +++ b/Doc/deprecations/pending-removal-in-3.18.rst @@ -11,6 +11,12 @@ Pending removal in Python 3.18 C implementation, has been deprecated since Python 3.13. (Contributed by Serhiy Storchaka in :gh:`89902`.) +* :mod:`typing`: + + * :data:`typing.AnyStr` has been discouraged + and deprecated since Python 3.16. + Use :pep:`695` syntax instead. + * Deprecations defined by :pep:`829`: * ``import`` lines in :file:`{name}.pth` files are silently ignored. diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 71b395c80166cc5..248bb8570a711ee 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -899,8 +899,8 @@ using ``[]``. Use ``class A[T: (str, bytes)]: ...`` instead of importing ``AnyStr``. See :pep:`695` for more details. - In Python 3.16, ``AnyStr`` will be removed from ``typing.__all__``, and - deprecation warnings will be emitted at runtime when it is accessed or + Since Python 3.16, ``AnyStr`` is removed from ``typing.__all__``, and + deprecation warnings is emitted at runtime when it is accessed or imported from ``typing``. ``AnyStr`` will be removed from ``typing`` in Python 3.18. diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 2e5342e4f020534..891722b129e3639 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -195,6 +195,10 @@ Deprecated 3.9, now issues a deprecation warning on use. This property is slated for removal in 3.21. Use ``ast.Tuple.elts`` instead. +* :mod:`typing`: + + * :data:`typing.AnyStr` now raises :exc:`DeprecationWarning` when used. + .. Add deprecations above alphabetically, not here at the end. diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index ad644bb31288098..3008bd138c8d8bc 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -20,7 +20,7 @@ from typing import Any, NoReturn, Never, assert_never from typing import overload, get_overloads, clear_overloads -from typing import TypeVar, TypeVarTuple, Unpack, AnyStr +from typing import TypeVar, TypeVarTuple, Unpack from typing import T, KT, VT # Not in __all__. from typing import Union, Optional, Literal from typing import Tuple, List, Dict, MutableMapping @@ -49,6 +49,11 @@ import weakref import types +with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + + from typing import AnyStr + from test.support import ( captured_stderr, cpython_only, requires_docstrings, import_helper, run_code, subTests, EqualToForwardRef, @@ -9551,12 +9556,16 @@ def test_no_isinstance(self): class IOTests(BaseTestCase): def test_io(self): - def stuff(a: IO) -> AnyStr: return a.readline() a = stuff.__annotations__['a'] - self.assertEqual(a.__parameters__, (AnyStr,)) + self.assertEqual(len(a.__parameters__), 1) + any_str = a.__parameters__[0] + self.assertEqual(repr(any_str), 'AnyStr') + self.assertEqual(any_str.__bound__, None) + self.assertEqual(any_str.__constraints__, (bytes, str)) + self.assertEqual(any_str.__constraints__, AnyStr.__constraints__) def test_textio(self): @@ -11176,13 +11185,20 @@ def test_all_exported_names(self): # there's a few types and metaclasses that aren't exported not k.endswith(('Meta', '_contra', '_co')) and not k.upper() == k and - k not in {"ByteString"} and + k not in {"ByteString", "AnyStr"} and # but export all other things that have __module__ == 'typing' getattr(v, '__module__', None) == typing.__name__ ) } self.assertSetEqual(computed_all, actual_all) + def test_any_str_deprecated(self): + # gh-105578 + fresh = import_helper.import_fresh_module('typing') + + with self.assertWarnsRegex(DeprecationWarning, r'typing\.AnyStr'): + fresh.AnyStr + class TypeIterationTests(BaseTestCase): _UNITERABLE_TYPES = ( diff --git a/Lib/typing.py b/Lib/typing.py index 130e09be4b91275..9d53b23c41459ea 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -121,7 +121,6 @@ 'TextIO', # One-off things. - 'AnyStr', 'assert_type', 'assert_never', 'cast', @@ -2815,11 +2814,6 @@ class Disjoint3(Disjoint1, Disjoint2): pass # Type checker error CT_co = TypeVar('CT_co', covariant=True, bound=type) -# A useful type variable with constraints. This represents string types. -# (This one *is* for export!) -AnyStr = TypeVar('AnyStr', bytes, str) - - # Various ABCs mimicking those in collections.abc. _alias = _SpecialGenericAlias @@ -3523,8 +3517,9 @@ def __ror__(self, other): # Constant that's True when type checking, but False here. TYPE_CHECKING = False - -class IO(Generic[AnyStr]): +# Since `AnyStr` public API is deprecated since 3.16 and will be removed +# in 3.18, we need to keep `IO` protocol the same as it used to be: +class IO[AnyStr: (bytes, str)]: """Generic base class for TextIO and BinaryIO. This is an abstract, generic version of the return of open(). @@ -3924,6 +3919,12 @@ def __subclasscheck__(self, cls): ) return ByteString + elif attr == "AnyStr": + import warnings + + warnings._deprecated("typing.AnyStr", remove=(3, 18)) + AnyStr = globals()["AnyStr"] = TypeVar('AnyStr', bytes, str) + return AnyStr else: raise AttributeError(f"module {__name__!r} has no attribute {attr!r}") globals()[attr] = obj diff --git a/Misc/NEWS.d/next/Library/2026-05-21-16-15-01.gh-issue-105578.oq-0pg.rst b/Misc/NEWS.d/next/Library/2026-05-21-16-15-01.gh-issue-105578.oq-0pg.rst new file mode 100644 index 000000000000000..52733512e96b912 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-21-16-15-01.gh-issue-105578.oq-0pg.rst @@ -0,0 +1,2 @@ +Deprecate :data:`typing.AnyStr` type variable and raise a warning, when it +is imported. From 1f3c6b45bec7ae9b90be035be5ae064f45687c52 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 21 May 2026 16:52:52 +0300 Subject: [PATCH 2/6] Address review --- Doc/library/typing.rst | 2 +- Lib/typing.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 248bb8570a711ee..c20aded3cc07899 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -900,7 +900,7 @@ using ``[]``. :pep:`695` for more details. Since Python 3.16, ``AnyStr`` is removed from ``typing.__all__``, and - deprecation warnings is emitted at runtime when it is accessed or + deprecation warnings are emitted at runtime when it is accessed or imported from ``typing``. ``AnyStr`` will be removed from ``typing`` in Python 3.18. diff --git a/Lib/typing.py b/Lib/typing.py index 9d53b23c41459ea..e2763a010e43f70 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3922,7 +3922,15 @@ def __subclasscheck__(self, cls): elif attr == "AnyStr": import warnings - warnings._deprecated("typing.AnyStr", remove=(3, 18)) + warnings._deprecated( + "typing.AnyStr", + message=( + "{name!r} is deprecated and slated for removal " + "in Python {remove}. Use PEP-695 type parameters " + "with constraints instead" + ), + remove=(3, 18), + ) AnyStr = globals()["AnyStr"] = TypeVar('AnyStr', bytes, str) return AnyStr else: From e5248b6712702364520a76f44f5011897628e8aa Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 28 Oct 2026 12:54:41 +0300 Subject: [PATCH 3/6] Apply suggestions from code review --- Doc/deprecations/pending-removal-in-3.18.rst | 3 ++- Lib/typing.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/deprecations/pending-removal-in-3.18.rst b/Doc/deprecations/pending-removal-in-3.18.rst index 3dbcea42d3f69c9..fa94e5f28dce8a5 100644 --- a/Doc/deprecations/pending-removal-in-3.18.rst +++ b/Doc/deprecations/pending-removal-in-3.18.rst @@ -14,7 +14,8 @@ Pending removal in Python 3.18 * :mod:`typing`: * :data:`typing.AnyStr` has been discouraged - and deprecated since Python 3.16. + and deprecated since Python 3.16 + and is scheduled to be removed in Python 3.21. Use :pep:`695` syntax instead. * Deprecations defined by :pep:`829`: diff --git a/Lib/typing.py b/Lib/typing.py index 656ced94769d317..a64e014e4fe78b4 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3954,7 +3954,7 @@ def __subclasscheck__(self, cls): "in Python {remove}. Use PEP-695 type parameters " "with constraints instead" ), - remove=(3, 18), + remove=(3, 21), ) AnyStr = globals()["AnyStr"] = TypeVar('AnyStr', bytes, str) return AnyStr From 2eb9c1b2f44b3b157e3096bee2986ecbbf3c6864 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 28 Oct 2026 13:59:31 +0300 Subject: [PATCH 4/6] Fix lazy imports CI --- Doc/deprecations/pending-removal-in-3.18.rst | 7 ------- Doc/deprecations/pending-removal-in-3.21.rst | 7 +++++++ Lib/test/test_typing.py | 18 ++++++++---------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Doc/deprecations/pending-removal-in-3.18.rst b/Doc/deprecations/pending-removal-in-3.18.rst index fa94e5f28dce8a5..19113aab981bbc6 100644 --- a/Doc/deprecations/pending-removal-in-3.18.rst +++ b/Doc/deprecations/pending-removal-in-3.18.rst @@ -11,13 +11,6 @@ Pending removal in Python 3.18 C implementation, has been deprecated since Python 3.13. (Contributed by Serhiy Storchaka in :gh:`89902`.) -* :mod:`typing`: - - * :data:`typing.AnyStr` has been discouraged - and deprecated since Python 3.16 - and is scheduled to be removed in Python 3.21. - Use :pep:`695` syntax instead. - * Deprecations defined by :pep:`829`: * ``import`` lines in :file:`{name}.pth` files are silently ignored. diff --git a/Doc/deprecations/pending-removal-in-3.21.rst b/Doc/deprecations/pending-removal-in-3.21.rst index 9fd0cd15aac5476..1a281b9cebec584 100644 --- a/Doc/deprecations/pending-removal-in-3.21.rst +++ b/Doc/deprecations/pending-removal-in-3.21.rst @@ -37,3 +37,10 @@ Pending removal in Python 3.21 * ``tempfile._TemporaryFileWrapper`` will be removed in Python 3.21. Use the public :class:`tempfile.TemporaryFileWrapper` instead. + +* :mod:`typing`: + + * :data:`typing.AnyStr` has been discouraged + and deprecated since Python 3.16 + and is scheduled to be removed in Python 3.21. + Use :pep:`695` syntax instead. diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 49f32dcc6bb88df..b1775ba7d81e521 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -50,11 +50,6 @@ import weakref import types -with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - - from typing import AnyStr - from test.support import ( captured_stderr, cpython_only, requires_docstrings, import_helper, run_code, subTests, EqualToForwardRef, @@ -494,7 +489,6 @@ def test_repr(self): self.assertEqual(repr(T), '~T') self.assertEqual(repr(KT), '~KT') self.assertEqual(repr(VT), '~VT') - self.assertEqual(repr(AnyStr), '~AnyStr') T_co = TypeVar('T_co', covariant=True) self.assertEqual(repr(T_co), '+T_co') T_contra = TypeVar('T_contra', contravariant=True) @@ -6393,13 +6387,15 @@ def on_bottom(self, a: int) -> int: class CastTests(BaseTestCase): def test_basics(self): + T = TypeVar("T") + self.assertEqual(cast(int, 42), 42) self.assertEqual(cast(float, 42), 42) self.assertIs(type(cast(float, 42)), int) self.assertEqual(cast(Any, 42), 42) self.assertEqual(cast(list, 42), 42) self.assertEqual(cast(Union[str, float], 42), 42) - self.assertEqual(cast(AnyStr, 42), 42) + self.assertEqual(cast(T, 42), 42) self.assertEqual(cast(None, 42), 42) def test_errors(self): @@ -6412,9 +6408,10 @@ class AssertTypeTests(BaseTestCase): def test_basics(self): arg = 42 + T = TypeVar("T") self.assertIs(assert_type(arg, int), arg) self.assertIs(assert_type(arg, str | float), arg) - self.assertIs(assert_type(arg, AnyStr), arg) + self.assertIs(assert_type(arg, T), arg) self.assertIs(assert_type(arg, None), arg) def test_errors(self): @@ -9642,7 +9639,7 @@ def test_no_isinstance(self): class IOTests(BaseTestCase): def test_io(self): - def stuff(a: IO) -> AnyStr: + def stuff(a: IO): return a.readline() a = stuff.__annotations__['a'] @@ -9651,7 +9648,6 @@ def stuff(a: IO) -> AnyStr: self.assertEqual(repr(any_str), 'AnyStr') self.assertEqual(any_str.__bound__, None) self.assertEqual(any_str.__constraints__, (bytes, str)) - self.assertEqual(any_str.__constraints__, AnyStr.__constraints__) def test_textio(self): @@ -11308,6 +11304,8 @@ def test_any_str_deprecated(self): with self.assertWarnsRegex(DeprecationWarning, r'typing\.AnyStr'): fresh.AnyStr + self.assertEqual(repr(fresh.AnyStr), '~AnyStr') + class TypeIterationTests(BaseTestCase): _UNITERABLE_TYPES = ( From 3e321d430ad9b5656a212d0d4bbb89cf9c412a3a Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 28 Oct 2026 14:51:28 +0300 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/deprecations/pending-removal-in-3.21.rst | 2 +- Lib/typing.py | 4 ++-- .../Library/2026-05-21-16-15-01.gh-issue-105578.oq-0pg.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/deprecations/pending-removal-in-3.21.rst b/Doc/deprecations/pending-removal-in-3.21.rst index 1a281b9cebec584..5434b161a6374a2 100644 --- a/Doc/deprecations/pending-removal-in-3.21.rst +++ b/Doc/deprecations/pending-removal-in-3.21.rst @@ -41,6 +41,6 @@ Pending removal in Python 3.21 * :mod:`typing`: * :data:`typing.AnyStr` has been discouraged - and deprecated since Python 3.16 + and deprecated since Python 3.13 and is scheduled to be removed in Python 3.21. Use :pep:`695` syntax instead. diff --git a/Lib/typing.py b/Lib/typing.py index a64e014e4fe78b4..b8545cae052e6f9 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3543,7 +3543,7 @@ def __ror__(self, other): TYPE_CHECKING = False # Since `AnyStr` public API is deprecated since 3.16 and will be removed -# in 3.18, we need to keep `IO` protocol the same as it used to be: +# in 3.21, we need to keep `IO` protocol the same as it used to be: class IO[AnyStr: (bytes, str)]: """Generic base class for TextIO and BinaryIO. @@ -3951,7 +3951,7 @@ def __subclasscheck__(self, cls): "typing.AnyStr", message=( "{name!r} is deprecated and slated for removal " - "in Python {remove}. Use PEP-695 type parameters " + "in Python {remove}. Use PEP 695 type parameters " "with constraints instead" ), remove=(3, 21), diff --git a/Misc/NEWS.d/next/Library/2026-05-21-16-15-01.gh-issue-105578.oq-0pg.rst b/Misc/NEWS.d/next/Library/2026-05-21-16-15-01.gh-issue-105578.oq-0pg.rst index 52733512e96b912..98b6f5abaeb94b5 100644 --- a/Misc/NEWS.d/next/Library/2026-05-21-16-15-01.gh-issue-105578.oq-0pg.rst +++ b/Misc/NEWS.d/next/Library/2026-05-21-16-15-01.gh-issue-105578.oq-0pg.rst @@ -1,2 +1,2 @@ -Deprecate :data:`typing.AnyStr` type variable and raise a warning, when it +Deprecate :data:`typing.AnyStr` type variable and raise a warning when it is imported. From adfaf3d62bf6e15e14ba256d76abd423bda62f80 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Wed, 28 Oct 2026 14:54:31 +0300 Subject: [PATCH 6/6] Fix removal date --- Doc/library/typing.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index e46b7bd23934011..77f2c1572c6c059 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -894,7 +894,7 @@ using ``[]``. def greet_proper(cond: bool) -> str | bytes: return "hi there!" if cond else b"greetings!" - .. deprecated-removed:: 3.13 3.18 + .. deprecated-removed:: 3.13 3.21 Deprecated in favor of the new :ref:`type parameter syntax `. Use ``class A[T: (str, bytes)]: ...`` instead of importing ``AnyStr``. See :pep:`695` for more details. @@ -902,7 +902,7 @@ using ``[]``. Since Python 3.16, ``AnyStr`` is removed from ``typing.__all__``, and deprecation warnings are emitted at runtime when it is accessed or imported from ``typing``. ``AnyStr`` will be removed from ``typing`` - in Python 3.18. + in Python 3.21. .. data:: LiteralString