gh-118803: Make ByteString deprecations louder; remove ByteString from typing.__all__ and collections.abc.__all__ (#139127)

This commit is contained in:
Alex Waygood
2025-09-18 19:58:16 +01:00
committed by GitHub
parent 4305cc3ef3
commit 293b05c09b
7 changed files with 150 additions and 27 deletions

View File

@@ -289,6 +289,25 @@ New modules
Improved modules
================
collections.abc
---------------
* :class:`collections.abc.ByteString` has been removed from
``collections.abc.__all__``. :class:`!collections.abc.ByteString` has been
deprecated since Python 3.12, and is scheduled for removal in Python 3.17.
* The following statements now cause ``DeprecationWarning``\ s to be emitted at
runtime:
* ``from collections.abc import ByteString``
* ``import collections.abc; collections.abc.ByteString``.
``DeprecationWarning``\ s were already emitted if
:class:`collections.abc.ByteString` was subclassed or used as the second
argument to :func:`isinstance` or :func:`issubclass`, but warnings were not
previously emitted if it was merely imported or accessed from the
:mod:`!collections.abc` module.
dbm
---
@@ -671,6 +690,21 @@ typing
as it was incorrectly infered in runtime before.
(Contributed by Nikita Sobolev in :gh:`137191`.)
* :class:`typing.ByteString` has been removed from ``typing.__all__``.
:class:`!typing.ByteString` has been deprecated since Python 3.9, and is
scheduled for removal in Python 3.17.
* The following statements now cause ``DeprecationWarning``\ s to be emitted at
runtime:
* ``from typing import ByteString``
* ``import typing; typing.ByteString``.
``DeprecationWarning``\ s were already emitted if :class:`typing.ByteString`
was subclassed or used as the second argument to :func:`isinstance` or
:func:`issubclass`, but warnings were not previously emitted if it was merely
imported or accessed from the :mod:`!typing` module.
unicodedata
-----------

View File

@@ -49,7 +49,7 @@ __all__ = ["Awaitable", "Coroutine",
"Mapping", "MutableMapping",
"MappingView", "KeysView", "ItemsView", "ValuesView",
"Sequence", "MutableSequence",
"ByteString", "Buffer",
"Buffer",
]
# This module has been renamed from collections.abc to _collections_abc to
@@ -1165,3 +1165,13 @@ class MutableSequence(Sequence):
MutableSequence.register(list)
MutableSequence.register(bytearray)
_deprecated_ByteString = globals().pop("ByteString")
def __getattr__(attr):
if attr == "ByteString":
import warnings
warnings._deprecated("collections.abc.ByteString", remove=(3, 17))
globals()["ByteString"] = _deprecated_ByteString
return _deprecated_ByteString
raise AttributeError(f"module 'collections.abc' has no attribute {attr!r}")

View File

@@ -93,6 +93,13 @@ def runtest_refleak(test_name, test_func,
for obj in abc.__subclasses__() + [abc]:
abcs[obj] = _get_dump(obj)[0]
# `ByteString` is not included in `collections.abc.__all__`
with warnings.catch_warnings(action='ignore', category=DeprecationWarning):
ByteString = collections.abc.ByteString
# Mypy doesn't even think `ByteString` is a class, hence the `type: ignore`
for obj in ByteString.__subclasses__() + [ByteString]: # type: ignore[attr-defined]
abcs[obj] = _get_dump(obj)[0]
# bpo-31217: Integer pool to get a single integer object for the same
# value. The pool is used to prevent false alarm when checking for memory
# block leaks. Fill the pool with values in -1000..1000 which are the most
@@ -254,6 +261,8 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs, linecache_data):
# Clear ABC registries, restoring previously saved ABC registries.
abs_classes = [getattr(collections.abc, a) for a in collections.abc.__all__]
with warnings.catch_warnings(action='ignore', category=DeprecationWarning):
abs_classes.append(collections.abc.ByteString)
abs_classes = filter(isabstract, abs_classes)
for abc in abs_classes:
for obj in abc.__subclasses__() + [abc]:

View File

@@ -12,6 +12,7 @@ from itertools import product, chain, combinations
import string
import sys
from test import support
from test.support.import_helper import import_fresh_module
import types
import unittest
@@ -26,7 +27,7 @@ from collections.abc import Sized, Container, Callable, Collection
from collections.abc import Set, MutableSet
from collections.abc import Mapping, MutableMapping, KeysView, ItemsView, ValuesView
from collections.abc import Sequence, MutableSequence
from collections.abc import ByteString, Buffer
from collections.abc import Buffer
class TestUserObjects(unittest.TestCase):
@@ -1935,6 +1936,14 @@ class TestCollectionABCs(ABCTestCase):
nativeseq, seqseq, (letter, start, stop))
def test_ByteString(self):
previous_sys_modules = sys.modules.copy()
self.addCleanup(sys.modules.update, previous_sys_modules)
for module in "collections", "_collections_abc", "collections.abc":
sys.modules.pop(module, None)
with self.assertWarns(DeprecationWarning):
from collections.abc import ByteString
for sample in [bytes, bytearray]:
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(sample(), ByteString)
@@ -1956,6 +1965,14 @@ class TestCollectionABCs(ABCTestCase):
# No metaclass conflict
class Z(ByteString, Awaitable): pass
def test_ByteString_attribute_access(self):
collections_abc = import_fresh_module(
"collections.abc",
fresh=("collections", "_collections_abc")
)
with self.assertWarns(DeprecationWarning):
collections_abc.ByteString
def test_Buffer(self):
for sample in [bytes, bytearray, memoryview]:
self.assertIsInstance(sample(b"x"), Buffer)

View File

@@ -13,6 +13,7 @@ import os
import pickle
import re
import sys
import warnings
from unittest import TestCase, main, skip
from unittest.mock import patch
from copy import copy, deepcopy
@@ -7500,14 +7501,23 @@ class CollectionsAbcTests(BaseTestCase):
self.assertNotIsInstance((), typing.MutableSequence)
def test_bytestring(self):
previous_typing_module = sys.modules.pop("typing", None)
self.addCleanup(sys.modules.__setitem__, "typing", previous_typing_module)
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(b'', typing.ByteString)
from typing import ByteString
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(bytearray(b''), typing.ByteString)
self.assertIsInstance(b'', ByteString)
with self.assertWarns(DeprecationWarning):
class Foo(typing.ByteString): ...
self.assertIsInstance(bytearray(b''), ByteString)
with self.assertWarns(DeprecationWarning):
class Bar(typing.ByteString, typing.Awaitable): ...
self.assertIsSubclass(bytes, ByteString)
with self.assertWarns(DeprecationWarning):
self.assertIsSubclass(bytearray, ByteString)
with self.assertWarns(DeprecationWarning):
class Foo(ByteString): ...
with self.assertWarns(DeprecationWarning):
class Bar(ByteString, typing.Awaitable): ...
def test_list(self):
self.assertIsSubclass(list, typing.List)
@@ -10455,6 +10465,10 @@ SpecialAttrsT = typing.TypeVar('SpecialAttrsT', int, float, complex)
class SpecialAttrsTests(BaseTestCase):
def test_special_attrs(self):
with warnings.catch_warnings(
action='ignore', category=DeprecationWarning
):
typing_ByteString = typing.ByteString
cls_to_check = {
# ABC classes
typing.AbstractSet: 'AbstractSet',
@@ -10463,7 +10477,7 @@ class SpecialAttrsTests(BaseTestCase):
typing.AsyncIterable: 'AsyncIterable',
typing.AsyncIterator: 'AsyncIterator',
typing.Awaitable: 'Awaitable',
typing.ByteString: 'ByteString',
typing_ByteString: 'ByteString',
typing.Callable: 'Callable',
typing.ChainMap: 'ChainMap',
typing.Collection: 'Collection',
@@ -10816,7 +10830,8 @@ class AllTests(BaseTestCase):
# there's a few types and metaclasses that aren't exported
not k.endswith(('Meta', '_contra', '_co')) and
not k.upper() == k and
# but export all things that have __module__ == 'typing'
k not in {"ByteString"} and
# but export all other things that have __module__ == 'typing'
getattr(v, '__module__', None) == typing.__name__
)
}

View File

@@ -65,7 +65,6 @@ __all__ = [
# ABCs (from collections.abc).
'AbstractSet', # collections.abc.Set.
'ByteString',
'Container',
'ContextManager',
'Hashable',
@@ -1603,21 +1602,6 @@ class _SpecialGenericAlias(_NotIterable, _BaseGenericAlias, _root=True):
return Union[left, self]
class _DeprecatedGenericAlias(_SpecialGenericAlias, _root=True):
def __init__(
self, origin, nparams, *, removal_version, inst=True, name=None
):
super().__init__(origin, nparams, inst=inst, name=name)
self._removal_version = removal_version
def __instancecheck__(self, inst):
import warnings
warnings._deprecated(
f"{self.__module__}.{self._name}", remove=self._removal_version
)
return super().__instancecheck__(inst)
class _CallableGenericAlias(_NotIterable, _GenericAlias, _root=True):
def __repr__(self):
assert self._name == 'Callable'
@@ -2805,9 +2789,6 @@ Mapping = _alias(collections.abc.Mapping, 2)
MutableMapping = _alias(collections.abc.MutableMapping, 2)
Sequence = _alias(collections.abc.Sequence, 1)
MutableSequence = _alias(collections.abc.MutableSequence, 1)
ByteString = _DeprecatedGenericAlias(
collections.abc.ByteString, 0, removal_version=(3, 17) # Not generic.
)
# Tuple accepts variable number of parameters.
Tuple = _TupleType(tuple, -1, inst=False, name='Tuple')
Tuple.__doc__ = \
@@ -3799,6 +3780,48 @@ def __getattr__(attr):
)
warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2)
obj = _collect_type_parameters
elif attr == "ByteString":
import warnings
warnings._deprecated(
"typing.ByteString",
message=(
"{name!r} and 'collections.abc.ByteString' are deprecated "
"and slated for removal in Python {remove}"
),
remove=(3, 17)
)
class _DeprecatedGenericAlias(_SpecialGenericAlias, _root=True):
def __init__(
self, origin, nparams, *, removal_version, inst=True, name=None
):
super().__init__(origin, nparams, inst=inst, name=name)
self._removal_version = removal_version
def __instancecheck__(self, inst):
import warnings
warnings._deprecated(
f"{self.__module__}.{self._name}", remove=self._removal_version
)
return super().__instancecheck__(inst)
def __subclasscheck__(self, cls):
import warnings
warnings._deprecated(
f"{self.__module__}.{self._name}", remove=self._removal_version
)
return super().__subclasscheck__(cls)
with warnings.catch_warnings(
action="ignore", category=DeprecationWarning
):
# Not generic
ByteString = globals()["ByteString"] = _DeprecatedGenericAlias(
collections.abc.ByteString, 0, removal_version=(3, 17)
)
return ByteString
else:
raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
globals()[attr] = obj

View File

@@ -0,0 +1,15 @@
:class:`collections.abc.ByteString` has been removed from
``collections.abc.__all__``, and :class:`typing.ByteString` has been removed
from ``typing.__all__``. The former has been deprecated since Python 3.12,
and the latter has been deprecated since Python 3.9. Both classes are
scheduled for removal in Python 3.17.
Additionally, the following statements now cause ``DeprecationWarning``\ s to
be emitted at runtime: ``from collections.abc import ByteString``, ``from
typing import ByteString``, ``import collections.abc;
collections.abc.ByteString`` and ``import typing; typing.ByteString``. Both
classes already caused ``DeprecationWarning``\ s to be emitted if they were
subclassed or used as the second argument to ``isinstance()`` or
``issubclass()``, but they did not previously lead to
``DeprecationWarning``\ s if they were merely imported or accessed from their
respective modules.