diff --git a/Doc/deprecations/pending-removal-in-3.21.rst b/Doc/deprecations/pending-removal-in-3.21.rst index 9fd0cd15aac5476..5434b161a6374a2 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.13 + and is scheduled to be removed in Python 3.21. + Use :pep:`695` syntax instead. 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 `. 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 e342aa71c7924ea..2817f35bc206a36 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -1218,6 +1218,10 @@ 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. + .. 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.