From f0f5bb32043e1223d8c413e763cd93061d4f9fac Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 12 May 2023 07:01:31 +0100 Subject: [PATCH] gh-91896: Improve visibility of `ByteString` deprecation warnings (#104294) --- Lib/collections/abc.py | 9 +++++++++ Lib/test/libregrtest/refleak.py | 16 ++++++++++------ Lib/test/test_collections.py | 10 +++++++++- Lib/test/test_typing.py | 30 ++++++++++++++++++++++++++---- Lib/typing.py | 32 +++++++++++++++++++++++++++++++- 5 files changed, 85 insertions(+), 12 deletions(-) diff --git a/Lib/collections/abc.py b/Lib/collections/abc.py index 86ca8b8a8414b3..60b1eb60fa6ae1 100644 --- a/Lib/collections/abc.py +++ b/Lib/collections/abc.py @@ -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}") diff --git a/Lib/test/libregrtest/refleak.py b/Lib/test/libregrtest/refleak.py index 2de8c6cfbc61a1..776a9e9b587d32 100644 --- a/Lib/test/libregrtest/refleak.py +++ b/Lib/test/libregrtest/refleak.py @@ -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 @@ -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]: diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py index bb8b352518ef3e..f0736b8081feac 100644 --- a/Lib/test/test_collections.py +++ b/Lib/test/test_collections.py @@ -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 @@ -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): @@ -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) @@ -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) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index f162e587810ac0..3422dc1ed3f5f8 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -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 @@ -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) @@ -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) @@ -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', @@ -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', @@ -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) diff --git a/Lib/typing.py b/Lib/typing.py index 62c7dd31a6290a..bf7bd241972a6b 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1599,6 +1599,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' @@ -2756,7 +2772,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__ = \ @@ -3556,3 +3571,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}")