Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-91896: Improve visibility of ByteString deprecation warnings #104294

Merged
merged 3 commits into from
May 12, 2023
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
9 changes: 9 additions & 0 deletions Lib/collections/abc.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
from _collections_abc import *
from _collections_abc import __all__
from _collections_abc import _CallableGenericAlias

_deprecated_ByteString = globals().pop("ByteString")

def __getattr__(attr):
if attr == "ByteString":
import warnings
warnings._deprecated("collections.abc.ByteString", remove=(3, 14))
return _deprecated_ByteString
raise AttributeError(f"module 'collections.abc' has no attribute {attr!r}")
16 changes: 10 additions & 6 deletions Lib/test/libregrtest/refleak.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ def dash_R(ns, test_name, test_func):
else:
zdc = zipimport._zip_directory_cache.copy()
abcs = {}
for abc in [getattr(collections.abc, a) for a in collections.abc.__all__]:
if not isabstract(abc):
continue
for obj in abc.__subclasses__() + [abc]:
abcs[obj] = _get_dump(obj)[0]
# catch and ignore collections.abc.ByteString deprecation
with warnings.catch_warnings(action='ignore', category=DeprecationWarning):
for abc in [getattr(collections.abc, a) for a in collections.abc.__all__]:
if not isabstract(abc):
continue
for obj in abc.__subclasses__() + [abc]:
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
Expand Down Expand Up @@ -173,7 +175,9 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):
zipimport._zip_directory_cache.update(zdc)

# Clear ABC registries, restoring previously saved ABC registries.
abs_classes = [getattr(collections.abc, a) for a in collections.abc.__all__]
# ignore deprecation warning for collections.abc.ByteString
with warnings.catch_warnings(action='ignore', category=DeprecationWarning):
abs_classes = [getattr(collections.abc, a) for a in collections.abc.__all__]
abs_classes = filter(isabstract, abs_classes)
for abc in abs_classes:
for obj in abc.__subclasses__() + [abc]:
Expand Down
10 changes: 9 additions & 1 deletion Lib/test/test_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import string
import sys
from test import support
from test.support.import_helper import import_fresh_module
import types
import unittest

Expand All @@ -25,7 +26,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 @@ -1939,6 +1940,8 @@ def assert_index_same(seq1, seq2, index_args):
nativeseq, seqseq, (letter, start, stop))

def test_ByteString(self):
with self.assertWarns(DeprecationWarning):
from collections.abc import ByteString
for sample in [bytes, bytearray]:
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(sample(), ByteString)
Expand All @@ -1960,6 +1963,11 @@ 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")
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
30 changes: 26 additions & 4 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import re
import sys
import warnings
from test.support.import_helper import import_fresh_module
from unittest import TestCase, main, skipUnless, skip
from unittest.mock import patch
from copy import copy, deepcopy
Expand Down Expand Up @@ -3908,7 +3909,14 @@ class MyChain(typing.ChainMap[str, T]): ...
self.assertEqual(MyChain[int]().__orig_class__, MyChain[int])

def test_all_repr_eq_any(self):
objs = (getattr(typing, el) for el in typing.__all__)
typing = import_fresh_module("typing")
with warnings.catch_warnings(record=True) as wlog:
warnings.filterwarnings('always', '', DeprecationWarning)
objs = [getattr(typing, el) for el in typing.__all__]
self.assertEqual(
[str(w.message) for w in wlog],
["'typing.ByteString' is deprecated and slated for removal in Python 3.14"]
)
for obj in objs:
self.assertNotEqual(repr(obj), '')
self.assertEqual(obj, obj)
Expand Down Expand Up @@ -5996,8 +6004,16 @@ def test_mutablesequence(self):
self.assertNotIsInstance((), typing.MutableSequence)

def test_bytestring(self):
self.assertIsInstance(b'', typing.ByteString)
self.assertIsInstance(bytearray(b''), typing.ByteString)
with self.assertWarns(DeprecationWarning):
from typing import ByteString
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(b'', ByteString)
with self.assertWarns(DeprecationWarning):
self.assertIsInstance(bytearray(b''), 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)
Expand Down Expand Up @@ -8293,6 +8309,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 @@ -8301,7 +8321,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 @@ -8626,6 +8646,8 @@ def test_all_exported_names(self):
getattr(v, '__module__', None) == typing.__name__
)
}
# Deprecated; added dynamically via module __getattr__
computed_all.add("ByteString")
self.assertSetEqual(computed_all, actual_all)


Expand Down
32 changes: 31 additions & 1 deletion Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,22 @@ def __or__(self, right):
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 @@ -2763,7 +2779,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 = _alias(collections.abc.ByteString, 0) # Not generic
# Tuple accepts variable number of parameters.
Tuple = _TupleType(tuple, -1, inst=False, name='Tuple')
Tuple.__doc__ = \
Expand Down Expand Up @@ -3563,3 +3578,18 @@ def method(self) -> None:
# read-only property, TypeError if it's a builtin class.
pass
return method


def __getattr__(attr):
if attr == "ByteString":
import warnings
warnings._deprecated("typing.ByteString", remove=(3, 14))
with warnings.catch_warnings(
action="ignore", category=DeprecationWarning
):
# Not generic
ByteString = globals()["ByteString"] = _DeprecatedGenericAlias(
collections.abc.ByteString, 0, removal_version=(3, 14)
)
return ByteString
raise AttributeError(f"module 'typing' has no attribute {attr!r}")