Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---

Expand Down Expand Up @@ -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
-----------
Expand Down
12 changes: 11 additions & 1 deletion Lib/_collections_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def _f(): pass
"Mapping", "MutableMapping",
"MappingView", "KeysView", "ItemsView", "ValuesView",
"Sequence", "MutableSequence",
"ByteString", "Buffer",
"Buffer",
]

# This module has been renamed from collections.abc to _collections_abc to
Expand Down Expand Up @@ -1161,3 +1161,13 @@ def __iadd__(self, values):

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means only the first time anyone does from collections.abc import ByteString in a program will trigger a warning, right? That seems potentially problematic: maybe the first time is in some dependency you don't control, so you ignore the warning, and then it happens again in your code and you miss it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. I did that deliberately so that the warning wouldn't be too noisy, and since it's what we previously did for other modules such as ast:

cpython/Lib/ast.py

Lines 1897 to 1905 in d065edf

def __getattr__(name):
if name in _deprecated_globals:
globals()[name] = value = _deprecated_globals[name]
import warnings
warnings._deprecated(
f"ast.{name}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
)
return value
raise AttributeError(f"module 'ast' has no attribute '{name}'")

But I'm happy to switch to it emitting the deprecation warning even on subsequent imports, if you prefer! It would simplify the implementation a little

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably OK to stick with the standard format here, hopefully it's enough to get people to notice...

return _deprecated_ByteString
raise AttributeError(f"module 'collections.abc' has no attribute {attr!r}")
9 changes: 9 additions & 0 deletions Lib/test/libregrtest/refleak.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]:
Expand Down
19 changes: 18 additions & 1 deletion Lib/test/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import string
import sys
from test import support
from test.support.import_helper import import_fresh_module
import types
import unittest

Expand All @@ -26,7 +27,7 @@
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):
Expand Down Expand Up @@ -1935,6 +1936,14 @@ def assert_index_same(seq1, seq2, index_args):
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)
Expand All @@ -1956,6 +1965,14 @@ class X(ByteString): pass
# 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)
Expand Down
27 changes: 21 additions & 6 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import pickle
import re
import sys
import warnings
from unittest import TestCase, main, skip
from unittest.mock import patch
from copy import copy, deepcopy
Expand Down Expand Up @@ -7500,14 +7501,23 @@ def test_mutablesequence(self):
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):
from typing import ByteString
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(b'', ByteString)
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(b'', typing.ByteString)
self.assertIsInstance(bytearray(b''), ByteString)
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(bytearray(b''), typing.ByteString)
self.assertIsSubclass(bytes, ByteString)
with self.assertWarns(DeprecationWarning):
class Foo(typing.ByteString): ...
self.assertIsSubclass(bytearray, ByteString)
with self.assertWarns(DeprecationWarning):
class Bar(typing.ByteString, typing.Awaitable): ...
class Foo(ByteString): ...
with self.assertWarns(DeprecationWarning):
class Bar(ByteString, typing.Awaitable): ...

def test_list(self):
self.assertIsSubclass(list, typing.List)
Expand Down Expand Up @@ -10455,6 +10465,10 @@ def test_no_isinstance(self):
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',
Expand All @@ -10463,7 +10477,7 @@ def test_special_attrs(self):
typing.AsyncIterable: 'AsyncIterable',
typing.AsyncIterator: 'AsyncIterator',
typing.Awaitable: 'Awaitable',
typing.ByteString: 'ByteString',
typing_ByteString: 'ByteString',
typing.Callable: 'Callable',
typing.ChainMap: 'ChainMap',
typing.Collection: 'Collection',
Expand Down Expand Up @@ -10816,7 +10830,8 @@ 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
# 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__
)
}
Expand Down
61 changes: 42 additions & 19 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@

# ABCs (from collections.abc).
'AbstractSet', # collections.abc.Set.
'ByteString',
'Container',
'ContextManager',
'Hashable',
Expand Down Expand Up @@ -1603,21 +1602,6 @@ def __ror__(self, left):
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'
Expand Down Expand Up @@ -2805,9 +2789,6 @@ class Other(Leaf): # Error reported by type checker
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__ = \
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading