Skip to content

Commit 293b05c

Browse files
authored
gh-118803: Make ByteString deprecations louder; remove ByteString from typing.__all__ and collections.abc.__all__ (#139127)
1 parent 4305cc3 commit 293b05c

File tree

7 files changed

+150
-27
lines changed

7 files changed

+150
-27
lines changed

Doc/whatsnew/3.15.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,25 @@ New modules
289289
Improved modules
290290
================
291291

292+
collections.abc
293+
---------------
294+
295+
* :class:`collections.abc.ByteString` has been removed from
296+
``collections.abc.__all__``. :class:`!collections.abc.ByteString` has been
297+
deprecated since Python 3.12, and is scheduled for removal in Python 3.17.
298+
299+
* The following statements now cause ``DeprecationWarning``\ s to be emitted at
300+
runtime:
301+
302+
* ``from collections.abc import ByteString``
303+
* ``import collections.abc; collections.abc.ByteString``.
304+
305+
``DeprecationWarning``\ s were already emitted if
306+
:class:`collections.abc.ByteString` was subclassed or used as the second
307+
argument to :func:`isinstance` or :func:`issubclass`, but warnings were not
308+
previously emitted if it was merely imported or accessed from the
309+
:mod:`!collections.abc` module.
310+
292311
dbm
293312
---
294313

@@ -671,6 +690,21 @@ typing
671690
as it was incorrectly infered in runtime before.
672691
(Contributed by Nikita Sobolev in :gh:`137191`.)
673692

693+
* :class:`typing.ByteString` has been removed from ``typing.__all__``.
694+
:class:`!typing.ByteString` has been deprecated since Python 3.9, and is
695+
scheduled for removal in Python 3.17.
696+
697+
* The following statements now cause ``DeprecationWarning``\ s to be emitted at
698+
runtime:
699+
700+
* ``from typing import ByteString``
701+
* ``import typing; typing.ByteString``.
702+
703+
``DeprecationWarning``\ s were already emitted if :class:`typing.ByteString`
704+
was subclassed or used as the second argument to :func:`isinstance` or
705+
:func:`issubclass`, but warnings were not previously emitted if it was merely
706+
imported or accessed from the :mod:`!typing` module.
707+
674708

675709
unicodedata
676710
-----------

Lib/_collections_abc.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def _f(): pass
4949
"Mapping", "MutableMapping",
5050
"MappingView", "KeysView", "ItemsView", "ValuesView",
5151
"Sequence", "MutableSequence",
52-
"ByteString", "Buffer",
52+
"Buffer",
5353
]
5454

5555
# This module has been renamed from collections.abc to _collections_abc to
@@ -1165,3 +1165,13 @@ def __iadd__(self, values):
11651165

11661166
MutableSequence.register(list)
11671167
MutableSequence.register(bytearray)
1168+
1169+
_deprecated_ByteString = globals().pop("ByteString")
1170+
1171+
def __getattr__(attr):
1172+
if attr == "ByteString":
1173+
import warnings
1174+
warnings._deprecated("collections.abc.ByteString", remove=(3, 17))
1175+
globals()["ByteString"] = _deprecated_ByteString
1176+
return _deprecated_ByteString
1177+
raise AttributeError(f"module 'collections.abc' has no attribute {attr!r}")

Lib/test/libregrtest/refleak.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ def runtest_refleak(test_name, test_func,
9393
for obj in abc.__subclasses__() + [abc]:
9494
abcs[obj] = _get_dump(obj)[0]
9595

96+
# `ByteString` is not included in `collections.abc.__all__`
97+
with warnings.catch_warnings(action='ignore', category=DeprecationWarning):
98+
ByteString = collections.abc.ByteString
99+
# Mypy doesn't even think `ByteString` is a class, hence the `type: ignore`
100+
for obj in ByteString.__subclasses__() + [ByteString]: # type: ignore[attr-defined]
101+
abcs[obj] = _get_dump(obj)[0]
102+
96103
# bpo-31217: Integer pool to get a single integer object for the same
97104
# value. The pool is used to prevent false alarm when checking for memory
98105
# 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):
254261

255262
# Clear ABC registries, restoring previously saved ABC registries.
256263
abs_classes = [getattr(collections.abc, a) for a in collections.abc.__all__]
264+
with warnings.catch_warnings(action='ignore', category=DeprecationWarning):
265+
abs_classes.append(collections.abc.ByteString)
257266
abs_classes = filter(isabstract, abs_classes)
258267
for abc in abs_classes:
259268
for obj in abc.__subclasses__() + [abc]:

Lib/test/test_collections.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import string
1313
import sys
1414
from test import support
15+
from test.support.import_helper import import_fresh_module
1516
import types
1617
import unittest
1718

@@ -26,7 +27,7 @@
2627
from collections.abc import Set, MutableSet
2728
from collections.abc import Mapping, MutableMapping, KeysView, ItemsView, ValuesView
2829
from collections.abc import Sequence, MutableSequence
29-
from collections.abc import ByteString, Buffer
30+
from collections.abc import Buffer
3031

3132

3233
class TestUserObjects(unittest.TestCase):
@@ -1935,6 +1936,14 @@ def assert_index_same(seq1, seq2, index_args):
19351936
nativeseq, seqseq, (letter, start, stop))
19361937

19371938
def test_ByteString(self):
1939+
previous_sys_modules = sys.modules.copy()
1940+
self.addCleanup(sys.modules.update, previous_sys_modules)
1941+
1942+
for module in "collections", "_collections_abc", "collections.abc":
1943+
sys.modules.pop(module, None)
1944+
1945+
with self.assertWarns(DeprecationWarning):
1946+
from collections.abc import ByteString
19381947
for sample in [bytes, bytearray]:
19391948
with self.assertWarns(DeprecationWarning):
19401949
self.assertIsInstance(sample(), ByteString)
@@ -1956,6 +1965,14 @@ class X(ByteString): pass
19561965
# No metaclass conflict
19571966
class Z(ByteString, Awaitable): pass
19581967

1968+
def test_ByteString_attribute_access(self):
1969+
collections_abc = import_fresh_module(
1970+
"collections.abc",
1971+
fresh=("collections", "_collections_abc")
1972+
)
1973+
with self.assertWarns(DeprecationWarning):
1974+
collections_abc.ByteString
1975+
19591976
def test_Buffer(self):
19601977
for sample in [bytes, bytearray, memoryview]:
19611978
self.assertIsInstance(sample(b"x"), Buffer)

Lib/test/test_typing.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import pickle
1414
import re
1515
import sys
16+
import warnings
1617
from unittest import TestCase, main, skip
1718
from unittest.mock import patch
1819
from copy import copy, deepcopy
@@ -7500,14 +7501,23 @@ def test_mutablesequence(self):
75007501
self.assertNotIsInstance((), typing.MutableSequence)
75017502

75027503
def test_bytestring(self):
7504+
previous_typing_module = sys.modules.pop("typing", None)
7505+
self.addCleanup(sys.modules.__setitem__, "typing", previous_typing_module)
7506+
7507+
with self.assertWarns(DeprecationWarning):
7508+
from typing import ByteString
7509+
with self.assertWarns(DeprecationWarning):
7510+
self.assertIsInstance(b'', ByteString)
75037511
with self.assertWarns(DeprecationWarning):
7504-
self.assertIsInstance(b'', typing.ByteString)
7512+
self.assertIsInstance(bytearray(b''), ByteString)
75057513
with self.assertWarns(DeprecationWarning):
7506-
self.assertIsInstance(bytearray(b''), typing.ByteString)
7514+
self.assertIsSubclass(bytes, ByteString)
75077515
with self.assertWarns(DeprecationWarning):
7508-
class Foo(typing.ByteString): ...
7516+
self.assertIsSubclass(bytearray, ByteString)
75097517
with self.assertWarns(DeprecationWarning):
7510-
class Bar(typing.ByteString, typing.Awaitable): ...
7518+
class Foo(ByteString): ...
7519+
with self.assertWarns(DeprecationWarning):
7520+
class Bar(ByteString, typing.Awaitable): ...
75117521

75127522
def test_list(self):
75137523
self.assertIsSubclass(list, typing.List)
@@ -10455,6 +10465,10 @@ def test_no_isinstance(self):
1045510465
class SpecialAttrsTests(BaseTestCase):
1045610466

1045710467
def test_special_attrs(self):
10468+
with warnings.catch_warnings(
10469+
action='ignore', category=DeprecationWarning
10470+
):
10471+
typing_ByteString = typing.ByteString
1045810472
cls_to_check = {
1045910473
# ABC classes
1046010474
typing.AbstractSet: 'AbstractSet',
@@ -10463,7 +10477,7 @@ def test_special_attrs(self):
1046310477
typing.AsyncIterable: 'AsyncIterable',
1046410478
typing.AsyncIterator: 'AsyncIterator',
1046510479
typing.Awaitable: 'Awaitable',
10466-
typing.ByteString: 'ByteString',
10480+
typing_ByteString: 'ByteString',
1046710481
typing.Callable: 'Callable',
1046810482
typing.ChainMap: 'ChainMap',
1046910483
typing.Collection: 'Collection',
@@ -10816,7 +10830,8 @@ def test_all_exported_names(self):
1081610830
# there's a few types and metaclasses that aren't exported
1081710831
not k.endswith(('Meta', '_contra', '_co')) and
1081810832
not k.upper() == k and
10819-
# but export all things that have __module__ == 'typing'
10833+
k not in {"ByteString"} and
10834+
# but export all other things that have __module__ == 'typing'
1082010835
getattr(v, '__module__', None) == typing.__name__
1082110836
)
1082210837
}

Lib/typing.py

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@
6565

6666
# ABCs (from collections.abc).
6767
'AbstractSet', # collections.abc.Set.
68-
'ByteString',
6968
'Container',
7069
'ContextManager',
7170
'Hashable',
@@ -1603,21 +1602,6 @@ def __ror__(self, left):
16031602
return Union[left, self]
16041603

16051604

1606-
class _DeprecatedGenericAlias(_SpecialGenericAlias, _root=True):
1607-
def __init__(
1608-
self, origin, nparams, *, removal_version, inst=True, name=None
1609-
):
1610-
super().__init__(origin, nparams, inst=inst, name=name)
1611-
self._removal_version = removal_version
1612-
1613-
def __instancecheck__(self, inst):
1614-
import warnings
1615-
warnings._deprecated(
1616-
f"{self.__module__}.{self._name}", remove=self._removal_version
1617-
)
1618-
return super().__instancecheck__(inst)
1619-
1620-
16211605
class _CallableGenericAlias(_NotIterable, _GenericAlias, _root=True):
16221606
def __repr__(self):
16231607
assert self._name == 'Callable'
@@ -2805,9 +2789,6 @@ class Other(Leaf): # Error reported by type checker
28052789
MutableMapping = _alias(collections.abc.MutableMapping, 2)
28062790
Sequence = _alias(collections.abc.Sequence, 1)
28072791
MutableSequence = _alias(collections.abc.MutableSequence, 1)
2808-
ByteString = _DeprecatedGenericAlias(
2809-
collections.abc.ByteString, 0, removal_version=(3, 17) # Not generic.
2810-
)
28112792
# Tuple accepts variable number of parameters.
28122793
Tuple = _TupleType(tuple, -1, inst=False, name='Tuple')
28132794
Tuple.__doc__ = \
@@ -3799,6 +3780,48 @@ def __getattr__(attr):
37993780
)
38003781
warnings.warn(depr_message, category=DeprecationWarning, stacklevel=2)
38013782
obj = _collect_type_parameters
3783+
elif attr == "ByteString":
3784+
import warnings
3785+
3786+
warnings._deprecated(
3787+
"typing.ByteString",
3788+
message=(
3789+
"{name!r} and 'collections.abc.ByteString' are deprecated "
3790+
"and slated for removal in Python {remove}"
3791+
),
3792+
remove=(3, 17)
3793+
)
3794+
3795+
class _DeprecatedGenericAlias(_SpecialGenericAlias, _root=True):
3796+
def __init__(
3797+
self, origin, nparams, *, removal_version, inst=True, name=None
3798+
):
3799+
super().__init__(origin, nparams, inst=inst, name=name)
3800+
self._removal_version = removal_version
3801+
3802+
def __instancecheck__(self, inst):
3803+
import warnings
3804+
warnings._deprecated(
3805+
f"{self.__module__}.{self._name}", remove=self._removal_version
3806+
)
3807+
return super().__instancecheck__(inst)
3808+
3809+
def __subclasscheck__(self, cls):
3810+
import warnings
3811+
warnings._deprecated(
3812+
f"{self.__module__}.{self._name}", remove=self._removal_version
3813+
)
3814+
return super().__subclasscheck__(cls)
3815+
3816+
with warnings.catch_warnings(
3817+
action="ignore", category=DeprecationWarning
3818+
):
3819+
# Not generic
3820+
ByteString = globals()["ByteString"] = _DeprecatedGenericAlias(
3821+
collections.abc.ByteString, 0, removal_version=(3, 17)
3822+
)
3823+
3824+
return ByteString
38023825
else:
38033826
raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
38043827
globals()[attr] = obj
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
:class:`collections.abc.ByteString` has been removed from
2+
``collections.abc.__all__``, and :class:`typing.ByteString` has been removed
3+
from ``typing.__all__``. The former has been deprecated since Python 3.12,
4+
and the latter has been deprecated since Python 3.9. Both classes are
5+
scheduled for removal in Python 3.17.
6+
7+
Additionally, the following statements now cause ``DeprecationWarning``\ s to
8+
be emitted at runtime: ``from collections.abc import ByteString``, ``from
9+
typing import ByteString``, ``import collections.abc;
10+
collections.abc.ByteString`` and ``import typing; typing.ByteString``. Both
11+
classes already caused ``DeprecationWarning``\ s to be emitted if they were
12+
subclassed or used as the second argument to ``isinstance()`` or
13+
``issubclass()``, but they did not previously lead to
14+
``DeprecationWarning``\ s if they were merely imported or accessed from their
15+
respective modules.

0 commit comments

Comments
 (0)