https://github.com/python/cpython/commit/d7465429a2becbdf08561585b2acebc9de2ebf2d
commit: d7465429a2becbdf08561585b2acebc9de2ebf2d
branch: main
author: sobolevn <[email protected]>
committer: sobolevn <[email protected]>
date: 2026-11-05T22:13:44Z
summary:

gh-105578: Raise `DeprecationWarning` on `typing.AnyStr` usage (#150190)

Co-authored-by: Hugo van Kemenade <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-05-21-16-15-01.gh-issue-105578.oq-0pg.rst
M Doc/deprecations/pending-removal-in-3.21.rst
M Doc/library/typing.rst
M Doc/whatsnew/3.16.rst
M Lib/test/test_typing.py
M Lib/typing.py

diff --git a/Doc/deprecations/pending-removal-in-3.21.rst 
b/Doc/deprecations/pending-removal-in-3.21.rst
index 9fd0cd15aac5476..c85f170e3d3f948 100644
--- a/Doc/deprecations/pending-removal-in-3.21.rst
+++ b/Doc/deprecations/pending-removal-in-3.21.rst
@@ -37,3 +37,11 @@ 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.13
+     and is scheduled to be removed in Python 3.21.
+     Use :pep:`695` syntax instead.
+     (Contributed by Nikita Sobolev in :gh:`150190`.)
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
index c909b8bad6d726c..77f2c1572c6c059 100644
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -894,15 +894,15 @@ 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 
<type-params>`.
       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 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
 
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index ce622d37e3c3f48..4af5d3c0bd072e2 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -1251,6 +1251,13 @@ New deprecations
     open them one by one instead.
     (Contributed by Serhiy Storchaka in :gh:`152638`.)
 
+* :mod:`typing`:
+
+  * :data:`typing.AnyStr` now raises :exc:`DeprecationWarning`
+    when used and is scheduled to be removed in Python 3.21.
+    Use :pep:`695` syntax instead.
+    (Contributed by Nikita Sobolev in :gh:`150190`.)
+
 .. Add deprecations above alphabetically, not here at the end.
 
 .. include:: ../deprecations/pending-removal-in-3.17.rst
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index b0d468b34092b25..b1775ba7d81e521 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -21,7 +21,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
@@ -489,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)
@@ -6388,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):
@@ -6407,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):
@@ -9637,12 +9639,15 @@ 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']
-        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))
 
     def test_textio(self):
 
@@ -11285,13 +11290,22 @@ 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
+
+        self.assertEqual(repr(fresh.AnyStr), '~AnyStr')
+
 
 class TypeIterationTests(BaseTestCase):
     _UNITERABLE_TYPES = (
diff --git a/Lib/typing.py b/Lib/typing.py
index 99c467a8af07d89..b8545cae052e6f9 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -123,7 +123,6 @@
     'TextIO',
 
     # One-off things.
-    'AnyStr',
     'assert_type',
     'assert_never',
     'cast',
@@ -2840,11 +2839,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
 
@@ -3548,8 +3542,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.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.
 
     This is an abstract, generic version of the return of open().
@@ -3949,6 +3944,20 @@ def __subclasscheck__(self, cls):
             )
 
         return ByteString
+    elif attr == "AnyStr":
+        import warnings
+
+        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, 21),
+        )
+        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..98b6f5abaeb94b5
--- /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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to