From 8de6eba6d7bcbe9bce0264edacae00675430a535 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 19 Feb 2022 14:20:14 +0200 Subject: [PATCH 01/20] bpo-46797: Emit deprecation warnings for deprecated ast features --- Lib/ast.py | 68 ++- Lib/test/test_ast.py | 467 +++++++++++++----- .../2022-02-19-14-19-34.bpo-46797.6BXZX4.rst | 2 + 3 files changed, 402 insertions(+), 135 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst diff --git a/Lib/ast.py b/Lib/ast.py index 625738ad681af4..070552576e44ad 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -284,9 +284,7 @@ def get_docstring(node, clean=True): if not(node.body and isinstance(node.body[0], Expr)): return None node = node.body[0].value - if isinstance(node, Str): - text = node.s - elif isinstance(node, Constant) and isinstance(node.value, str): + if isinstance(node, Constant) and isinstance(node.value, str): text = node.value else: return None @@ -505,15 +503,34 @@ def generic_visit(self, node): # The following code is for backward compatibility. # It will be removed in future. - def _getter(self): + def _n_getter(self): """Deprecated. Use value instead.""" + import warnings + warnings.warn("Attribute n is deprecated; use value instead", + DeprecationWarning, stacklevel=2) return self.value - def _setter(self, value): + def _n_setter(self, value): + import warnings + warnings.warn("Attribute n is deprecated; use value instead", + DeprecationWarning, stacklevel=2) self.value = value - Constant.n = property(_getter, _setter) - Constant.s = property(_getter, _setter) + def _s_getter(self): + """Deprecated. Use value instead.""" + import warnings + warnings.warn("Attribute s is deprecated; use value instead", + DeprecationWarning, stacklevel=2) + return self.value + + def _s_setter(self, value): + import warnings + warnings.warn("Attribute s is deprecated; use value instead", + DeprecationWarning, stacklevel=2) + self.value = value + + Constant.n = property(_n_getter, _n_setter) + Constant.s = property(_s_getter, _s_setter) class _ABC(type): @@ -544,6 +561,9 @@ def _new(cls, *args, **kwargs): if pos < len(args): raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}") if cls in _const_types: + import warnings + warnings.warn(f"ast.{cls.__qualname__} is deprecated; use ast.Constant instead", + DeprecationWarning, stacklevel=2) return Constant(*args, **kwargs) return Constant.__new__(cls, *args, **kwargs) @@ -566,10 +586,15 @@ class Ellipsis(Constant, metaclass=_ABC): _fields = () def __new__(cls, *args, **kwargs): - if cls is Ellipsis: + if cls is _ast_Ellipsis: + import warnings + warnings.warn("ast.Ellipsis is deprecated; use ast.Constant instead", + DeprecationWarning, stacklevel=2) return Constant(..., *args, **kwargs) return Constant.__new__(cls, *args, **kwargs) +_ast_Ellipsis = Ellipsis + _const_types = { Num: (int, float, complex), Str: (str,), @@ -603,6 +628,9 @@ def __new__(cls, value, **kwargs): class ExtSlice(slice): """Deprecated AST node class. Use ast.Tuple instead.""" def __new__(cls, dims=(), **kwargs): + import warnings + warnings.warn("ast.ExtSlice is deprecated; use ast.Tuple instead", + DeprecationWarning, stacklevel=2) return Tuple(list(dims), Load(), **kwargs) # If the ast module is loaded more than once, only add deprecated methods once @@ -612,9 +640,14 @@ def __new__(cls, dims=(), **kwargs): def _dims_getter(self): """Deprecated. Use elts instead.""" + import warnings + warnings.warn("Attribute dims is deprecated; use elts instead", + DeprecationWarning, stacklevel=2) return self.elts def _dims_setter(self, value): + warnings.warn("Attribute dims is deprecated, use elts instead", + DeprecationWarning, stacklevel=2) self.elts = value Tuple.dims = property(_dims_getter, _dims_setter) @@ -1699,6 +1732,25 @@ def unparse(ast_obj): return unparser.visit(ast_obj) +_deprecated_globals = { + name: (globals().pop(name), '; use ast.Constant instead') + for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis') +} +_deprecated_globals['slice'] = globals().pop('slice'), '' +_deprecated_globals['Index'] = globals().pop('Index'), '' +_deprecated_globals['ExtSlice'] = globals().pop('ExtSlice'), '; use ast.Tuple instead' + +def __getattr__(name): + if name in _deprecated_globals: + value, details = _deprecated_globals[name] + globals()[name] = value + import warnings + warnings.warn(f"ast.{name} is deprecated{details}", + DeprecationWarning, stacklevel=2) + return value + raise AttributeError(f"module 'ast' has no attribute '{name}'") + + def main(): import argparse diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 039d1c1010b6d1..f50e55afc30f82 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -11,6 +11,7 @@ from textwrap import dedent from test import support +from test.support.import_helper import import_fresh_module def to_tuple(t): if t is None or isinstance(t, (str, int, complex)): @@ -251,6 +252,7 @@ def to_tuple(t): # excepthandler, arguments, keywords, alias class AST_Tests(unittest.TestCase): + maxDiff = None def _is_ast_node(self, name, node): if not isinstance(node, type): @@ -366,13 +368,48 @@ def test_base_classes(self): self.assertTrue(issubclass(ast.comprehension, ast.AST)) self.assertTrue(issubclass(ast.Gt, ast.AST)) - def test_field_attr_existence(self): - for name, item in ast.__dict__.items(): + def test_import_deprecated(self): + ast = import_fresh_module('ast') + for name in 'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis': + with self.assertWarnsRegex(DeprecationWarning, + fr'ast\.{name} is deprecated; use ast\.Constant instead'): + getattr(ast, name) + with self.assertWarnsRegex(DeprecationWarning, + r'ast\.slice is deprecated'): + ast.slice + with self.assertWarnsRegex(DeprecationWarning, + r'ast\.Index is deprecated'): + ast.Index + with self.assertWarnsRegex(DeprecationWarning, + r'ast\.ExtSlice is deprecated; use ast\.Tuple instead'): + ast.ExtSlice + + def test_field_attr_existence_deprecated(self): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num, Str, Bytes, NameConstant, Ellipsis + from ast import slice, Index, ExtSlice + + for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', + 'slice', 'Index', 'ExtSlice'): + item = getattr(ast, name) if self._is_ast_node(name, item): if name == 'Index': # Index(value) just returns value now. # The argument is required. continue + with self.subTest(item): + with self.assertWarns(DeprecationWarning): + x = item() + if isinstance(x, ast.AST): + self.assertEqual(type(x._fields), tuple) + + def test_field_attr_existence(self): + for name, item in ast.__dict__.items(): + if name in {'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', + 'slice', 'Index', 'ExtSlice'}: + continue + if self._is_ast_node(name, item): x = item() if isinstance(x, ast.AST): self.assertEqual(type(x._fields), tuple) @@ -390,25 +427,108 @@ def test_arguments(self): self.assertEqual(x.args, 2) self.assertEqual(x.vararg, 3) + def test_field_attr_writable_deprecated(self): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + x = ast.Num() + # We can assign to _fields + x._fields = 666 + self.assertEqual(x._fields, 666) + def test_field_attr_writable(self): - x = ast.Num() + x = ast.Constant() # We can assign to _fields x._fields = 666 self.assertEqual(x._fields, 666) + def test_classattrs_deprecated(self): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num, Str, Bytes, NameConstant, Ellipsis + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + x = ast.Num() + self.assertEqual(x._fields, ('value', 'kind')) + + with self.assertRaises(AttributeError): + x.value + + with self.assertRaises(AttributeError): + x.n + + x = ast.Num(42) + self.assertEqual(x.value, 42) + self.assertEqual(x.n, 42) + + with self.assertRaises(AttributeError): + x.lineno + + with self.assertRaises(AttributeError): + x.foobar + + x = ast.Num(lineno=2) + self.assertEqual(x.lineno, 2) + + x = ast.Num(42, lineno=0) + self.assertEqual(x.lineno, 0) + self.assertEqual(x._fields, ('value', 'kind')) + self.assertEqual(x.value, 42) + self.assertEqual(x.n, 42) + + self.assertRaises(TypeError, ast.Num, 1, None, 2) + self.assertRaises(TypeError, ast.Num, 1, None, 2, lineno=0) + + # Arbitrary keyword arguments are supported + self.assertEqual(ast.Num(1, foo='bar').foo, 'bar') + + with self.assertRaisesRegex(TypeError, "Num got multiple values for argument 'n'"): + ast.Num(1, n=2) + + self.assertEqual(ast.Num(42).n, 42) + self.assertEqual(ast.Num(4.25).n, 4.25) + self.assertEqual(ast.Num(4.25j).n, 4.25j) + self.assertEqual(ast.Str('42').s, '42') + self.assertEqual(ast.Bytes(b'42').s, b'42') + self.assertIs(ast.NameConstant(True).value, True) + self.assertIs(ast.NameConstant(False).value, False) + self.assertIs(ast.NameConstant(None).value, None) + + self.assertEqual([str(w.message) for w in wlog], [ + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'ast.Str is deprecated; use ast.Constant instead', + 'Attribute s is deprecated; use value instead', + 'ast.Bytes is deprecated; use ast.Constant instead', + 'Attribute s is deprecated; use value instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + ]) + def test_classattrs(self): - x = ast.Num() + x = ast.Constant() self.assertEqual(x._fields, ('value', 'kind')) with self.assertRaises(AttributeError): x.value - with self.assertRaises(AttributeError): - x.n - - x = ast.Num(42) + x = ast.Constant(42) self.assertEqual(x.value, 42) - self.assertEqual(x.n, 42) with self.assertRaises(AttributeError): x.lineno @@ -416,36 +536,23 @@ def test_classattrs(self): with self.assertRaises(AttributeError): x.foobar - x = ast.Num(lineno=2) + x = ast.Constant(lineno=2) self.assertEqual(x.lineno, 2) - x = ast.Num(42, lineno=0) + x = ast.Constant(42, lineno=0) self.assertEqual(x.lineno, 0) self.assertEqual(x._fields, ('value', 'kind')) self.assertEqual(x.value, 42) - self.assertEqual(x.n, 42) - self.assertRaises(TypeError, ast.Num, 1, None, 2) - self.assertRaises(TypeError, ast.Num, 1, None, 2, lineno=0) + self.assertRaises(TypeError, ast.Constant, 1, None, 2) + self.assertRaises(TypeError, ast.Constant, 1, None, 2, lineno=0) # Arbitrary keyword arguments are supported self.assertEqual(ast.Constant(1, foo='bar').foo, 'bar') - self.assertEqual(ast.Num(1, foo='bar').foo, 'bar') - with self.assertRaisesRegex(TypeError, "Num got multiple values for argument 'n'"): - ast.Num(1, n=2) with self.assertRaisesRegex(TypeError, "Constant got multiple values for argument 'value'"): ast.Constant(1, value=2) - self.assertEqual(ast.Num(42).n, 42) - self.assertEqual(ast.Num(4.25).n, 4.25) - self.assertEqual(ast.Num(4.25j).n, 4.25j) - self.assertEqual(ast.Str('42').s, '42') - self.assertEqual(ast.Bytes(b'42').s, b'42') - self.assertIs(ast.NameConstant(True).value, True) - self.assertIs(ast.NameConstant(False).value, False) - self.assertIs(ast.NameConstant(None).value, None) - self.assertEqual(ast.Constant(42).value, 42) self.assertEqual(ast.Constant(4.25).value, 4.25) self.assertEqual(ast.Constant(4.25j).value, 4.25j) @@ -457,85 +564,162 @@ def test_classattrs(self): self.assertIs(ast.Constant(...).value, ...) def test_realtype(self): - self.assertEqual(type(ast.Num(42)), ast.Constant) - self.assertEqual(type(ast.Num(4.25)), ast.Constant) - self.assertEqual(type(ast.Num(4.25j)), ast.Constant) - self.assertEqual(type(ast.Str('42')), ast.Constant) - self.assertEqual(type(ast.Bytes(b'42')), ast.Constant) - self.assertEqual(type(ast.NameConstant(True)), ast.Constant) - self.assertEqual(type(ast.NameConstant(False)), ast.Constant) - self.assertEqual(type(ast.NameConstant(None)), ast.Constant) - self.assertEqual(type(ast.Ellipsis()), ast.Constant) + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num, Str, Bytes, NameConstant, Ellipsis + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + self.assertEqual(type(ast.Num(42)), ast.Constant) + self.assertEqual(type(ast.Num(4.25)), ast.Constant) + self.assertEqual(type(ast.Num(4.25j)), ast.Constant) + self.assertEqual(type(ast.Str('42')), ast.Constant) + self.assertEqual(type(ast.Bytes(b'42')), ast.Constant) + self.assertEqual(type(ast.NameConstant(True)), ast.Constant) + self.assertEqual(type(ast.NameConstant(False)), ast.Constant) + self.assertEqual(type(ast.NameConstant(None)), ast.Constant) + self.assertEqual(type(ast.Ellipsis()), ast.Constant) + + self.assertEqual([str(w.message) for w in wlog], [ + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Str is deprecated; use ast.Constant instead', + 'ast.Bytes is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.Ellipsis is deprecated; use ast.Constant instead', + ]) def test_isinstance(self): - self.assertTrue(isinstance(ast.Num(42), ast.Num)) - self.assertTrue(isinstance(ast.Num(4.2), ast.Num)) - self.assertTrue(isinstance(ast.Num(4.2j), ast.Num)) - self.assertTrue(isinstance(ast.Str('42'), ast.Str)) - self.assertTrue(isinstance(ast.Bytes(b'42'), ast.Bytes)) - self.assertTrue(isinstance(ast.NameConstant(True), ast.NameConstant)) - self.assertTrue(isinstance(ast.NameConstant(False), ast.NameConstant)) - self.assertTrue(isinstance(ast.NameConstant(None), ast.NameConstant)) - self.assertTrue(isinstance(ast.Ellipsis(), ast.Ellipsis)) - - self.assertTrue(isinstance(ast.Constant(42), ast.Num)) - self.assertTrue(isinstance(ast.Constant(4.2), ast.Num)) - self.assertTrue(isinstance(ast.Constant(4.2j), ast.Num)) - self.assertTrue(isinstance(ast.Constant('42'), ast.Str)) - self.assertTrue(isinstance(ast.Constant(b'42'), ast.Bytes)) - self.assertTrue(isinstance(ast.Constant(True), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(False), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(None), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(...), ast.Ellipsis)) - - self.assertFalse(isinstance(ast.Str('42'), ast.Num)) - self.assertFalse(isinstance(ast.Num(42), ast.Str)) - self.assertFalse(isinstance(ast.Str('42'), ast.Bytes)) - self.assertFalse(isinstance(ast.Num(42), ast.NameConstant)) - self.assertFalse(isinstance(ast.Num(42), ast.Ellipsis)) - self.assertFalse(isinstance(ast.NameConstant(True), ast.Num)) - self.assertFalse(isinstance(ast.NameConstant(False), ast.Num)) - - self.assertFalse(isinstance(ast.Constant('42'), ast.Num)) - self.assertFalse(isinstance(ast.Constant(42), ast.Str)) - self.assertFalse(isinstance(ast.Constant('42'), ast.Bytes)) - self.assertFalse(isinstance(ast.Constant(42), ast.NameConstant)) - self.assertFalse(isinstance(ast.Constant(42), ast.Ellipsis)) - self.assertFalse(isinstance(ast.Constant(True), ast.Num)) - self.assertFalse(isinstance(ast.Constant(False), ast.Num)) - - self.assertFalse(isinstance(ast.Constant(), ast.Num)) - self.assertFalse(isinstance(ast.Constant(), ast.Str)) - self.assertFalse(isinstance(ast.Constant(), ast.Bytes)) - self.assertFalse(isinstance(ast.Constant(), ast.NameConstant)) - self.assertFalse(isinstance(ast.Constant(), ast.Ellipsis)) - - class S(str): pass - self.assertTrue(isinstance(ast.Constant(S('42')), ast.Str)) - self.assertFalse(isinstance(ast.Constant(S('42')), ast.Num)) - - def test_subclasses(self): - class N(ast.Num): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num, Str, Bytes, NameConstant, Ellipsis + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + self.assertTrue(isinstance(ast.Num(42), ast.Num)) + self.assertTrue(isinstance(ast.Num(4.2), ast.Num)) + self.assertTrue(isinstance(ast.Num(4.2j), ast.Num)) + self.assertTrue(isinstance(ast.Str('42'), ast.Str)) + self.assertTrue(isinstance(ast.Bytes(b'42'), ast.Bytes)) + self.assertTrue(isinstance(ast.NameConstant(True), ast.NameConstant)) + self.assertTrue(isinstance(ast.NameConstant(False), ast.NameConstant)) + self.assertTrue(isinstance(ast.NameConstant(None), ast.NameConstant)) + self.assertTrue(isinstance(ast.Ellipsis(), ast.Ellipsis)) + + self.assertTrue(isinstance(ast.Constant(42), ast.Num)) + self.assertTrue(isinstance(ast.Constant(4.2), ast.Num)) + self.assertTrue(isinstance(ast.Constant(4.2j), ast.Num)) + self.assertTrue(isinstance(ast.Constant('42'), ast.Str)) + self.assertTrue(isinstance(ast.Constant(b'42'), ast.Bytes)) + self.assertTrue(isinstance(ast.Constant(True), ast.NameConstant)) + self.assertTrue(isinstance(ast.Constant(False), ast.NameConstant)) + self.assertTrue(isinstance(ast.Constant(None), ast.NameConstant)) + self.assertTrue(isinstance(ast.Constant(...), ast.Ellipsis)) + + self.assertFalse(isinstance(ast.Str('42'), ast.Num)) + self.assertFalse(isinstance(ast.Num(42), ast.Str)) + self.assertFalse(isinstance(ast.Str('42'), ast.Bytes)) + self.assertFalse(isinstance(ast.Num(42), ast.NameConstant)) + self.assertFalse(isinstance(ast.Num(42), ast.Ellipsis)) + self.assertFalse(isinstance(ast.NameConstant(True), ast.Num)) + self.assertFalse(isinstance(ast.NameConstant(False), ast.Num)) + + self.assertFalse(isinstance(ast.Constant('42'), ast.Num)) + self.assertFalse(isinstance(ast.Constant(42), ast.Str)) + self.assertFalse(isinstance(ast.Constant('42'), ast.Bytes)) + self.assertFalse(isinstance(ast.Constant(42), ast.NameConstant)) + self.assertFalse(isinstance(ast.Constant(42), ast.Ellipsis)) + self.assertFalse(isinstance(ast.Constant(True), ast.Num)) + self.assertFalse(isinstance(ast.Constant(False), ast.Num)) + + self.assertFalse(isinstance(ast.Constant(), ast.Num)) + self.assertFalse(isinstance(ast.Constant(), ast.Str)) + self.assertFalse(isinstance(ast.Constant(), ast.Bytes)) + self.assertFalse(isinstance(ast.Constant(), ast.NameConstant)) + self.assertFalse(isinstance(ast.Constant(), ast.Ellipsis)) + + class S(str): pass + self.assertTrue(isinstance(ast.Constant(S('42')), ast.Str)) + self.assertFalse(isinstance(ast.Constant(S('42')), ast.Num)) + + self.assertEqual([str(w.message) for w in wlog], [ + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Str is deprecated; use ast.Constant instead', + 'ast.Bytes is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.Ellipsis is deprecated; use ast.Constant instead', + 'ast.Str is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Str is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated; use ast.Constant instead', + ]) + + def test_constant_subclasses_deprecated(self): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + class N(ast.Num): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.z = 'spam' + class N2(ast.Num): + pass + + n = N(42) + self.assertEqual(n.n, 42) + self.assertEqual(n.z, 'spam') + self.assertEqual(type(n), N) + self.assertTrue(isinstance(n, N)) + self.assertTrue(isinstance(n, ast.Num)) + self.assertFalse(isinstance(n, N2)) + self.assertFalse(isinstance(ast.Num(42), N)) + n = N(n=42) + self.assertEqual(n.n, 42) + self.assertEqual(type(n), N) + + self.assertEqual([str(w.message) for w in wlog], [ + 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated; use value instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated; use value instead', + ]) + + def test_constant_subclasses(self): + class N(ast.Constant): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.z = 'spam' - class N2(ast.Num): + class N2(ast.Constant): pass n = N(42) - self.assertEqual(n.n, 42) + self.assertEqual(n.value, 42) self.assertEqual(n.z, 'spam') self.assertEqual(type(n), N) self.assertTrue(isinstance(n, N)) - self.assertTrue(isinstance(n, ast.Num)) + self.assertTrue(isinstance(n, ast.Constant)) self.assertFalse(isinstance(n, N2)) - self.assertFalse(isinstance(ast.Num(42), N)) - n = N(n=42) - self.assertEqual(n.n, 42) + self.assertFalse(isinstance(ast.Constant(42), N)) + n = N(value=42) + self.assertEqual(n.value, 42) self.assertEqual(type(n), N) def test_module(self): - body = [ast.Num(42)] + body = [ast.Constant(42)] x = ast.Module(body, []) self.assertEqual(x.body, body) @@ -548,8 +732,8 @@ def test_nodeclasses(self): x.foobarbaz = 5 self.assertEqual(x.foobarbaz, 5) - n1 = ast.Num(1) - n3 = ast.Num(3) + n1 = ast.Constant(1) + n3 = ast.Constant(3) addop = ast.Add() x = ast.BinOp(n1, addop, n3) self.assertEqual(x.left, n1) @@ -861,7 +1045,7 @@ def test_dump_incomplete(self): def test_copy_location(self): src = ast.parse('1 + 1', mode='eval') - src.body.right = ast.copy_location(ast.Num(2), src.body.right) + src.body.right = ast.copy_location(ast.Constant(2), src.body.right) self.assertEqual(ast.dump(src, include_attributes=True), 'Expression(body=BinOp(left=Constant(value=1, lineno=1, col_offset=0, ' 'end_lineno=1, end_col_offset=1), op=Add(), right=Constant(value=2, ' @@ -878,7 +1062,7 @@ def test_copy_location(self): def test_fix_missing_locations(self): src = ast.parse('write("spam")') src.body.append(ast.Expr(ast.Call(ast.Name('spam', ast.Load()), - [ast.Str('eggs')], []))) + [ast.Constant('eggs')], []))) self.assertEqual(src, ast.fix_missing_locations(src)) self.maxDiff = None self.assertEqual(ast.dump(src, include_attributes=True), @@ -1171,9 +1355,9 @@ def arguments(args=None, posonlyargs=None, vararg=None, check(arguments(args=args), "must have Load context") check(arguments(posonlyargs=args), "must have Load context") check(arguments(kwonlyargs=args), "must have Load context") - check(arguments(defaults=[ast.Num(3)]), + check(arguments(defaults=[ast.Constant(3)]), "more positional defaults than args") - check(arguments(kw_defaults=[ast.Num(4)]), + check(arguments(kw_defaults=[ast.Constant(4)]), "length of kwonlyargs is not the same as kw_defaults") args = [ast.arg("x", ast.Name("x", ast.Load()))] check(arguments(args=args, defaults=[ast.Name("x", ast.Store())]), @@ -1226,9 +1410,9 @@ def test_delete(self): "must have Del context") def test_assign(self): - self.stmt(ast.Assign([], ast.Num(3)), "empty targets on Assign") - self.stmt(ast.Assign([None], ast.Num(3)), "None disallowed") - self.stmt(ast.Assign([ast.Name("x", ast.Load())], ast.Num(3)), + self.stmt(ast.Assign([], ast.Constant(3)), "empty targets on Assign") + self.stmt(ast.Assign([None], ast.Constant(3)), "None disallowed") + self.stmt(ast.Assign([ast.Name("x", ast.Load())], ast.Constant(3)), "must have Store context") self.stmt(ast.Assign([ast.Name("x", ast.Store())], ast.Name("y", ast.Store())), @@ -1256,39 +1440,39 @@ def test_for(self): self.stmt(ast.For(x, y, [p], [e]), "must have Load context") def test_while(self): - self.stmt(ast.While(ast.Num(3), [], []), "empty body on While") + self.stmt(ast.While(ast.Constant(3), [], []), "empty body on While") self.stmt(ast.While(ast.Name("x", ast.Store()), [ast.Pass()], []), "must have Load context") - self.stmt(ast.While(ast.Num(3), [ast.Pass()], + self.stmt(ast.While(ast.Constant(3), [ast.Pass()], [ast.Expr(ast.Name("x", ast.Store()))]), "must have Load context") def test_if(self): - self.stmt(ast.If(ast.Num(3), [], []), "empty body on If") + self.stmt(ast.If(ast.Constant(3), [], []), "empty body on If") i = ast.If(ast.Name("x", ast.Store()), [ast.Pass()], []) self.stmt(i, "must have Load context") - i = ast.If(ast.Num(3), [ast.Expr(ast.Name("x", ast.Store()))], []) + i = ast.If(ast.Constant(3), [ast.Expr(ast.Name("x", ast.Store()))], []) self.stmt(i, "must have Load context") - i = ast.If(ast.Num(3), [ast.Pass()], + i = ast.If(ast.Constant(3), [ast.Pass()], [ast.Expr(ast.Name("x", ast.Store()))]) self.stmt(i, "must have Load context") def test_with(self): p = ast.Pass() self.stmt(ast.With([], [p]), "empty items on With") - i = ast.withitem(ast.Num(3), None) + i = ast.withitem(ast.Constant(3), None) self.stmt(ast.With([i], []), "empty body on With") i = ast.withitem(ast.Name("x", ast.Store()), None) self.stmt(ast.With([i], [p]), "must have Load context") - i = ast.withitem(ast.Num(3), ast.Name("x", ast.Load())) + i = ast.withitem(ast.Constant(3), ast.Name("x", ast.Load())) self.stmt(ast.With([i], [p]), "must have Store context") def test_raise(self): - r = ast.Raise(None, ast.Num(3)) + r = ast.Raise(None, ast.Constant(3)) self.stmt(r, "Raise with cause but no exception") r = ast.Raise(ast.Name("x", ast.Store()), None) self.stmt(r, "must have Load context") - r = ast.Raise(ast.Num(4), ast.Name("x", ast.Store())) + r = ast.Raise(ast.Constant(4), ast.Name("x", ast.Store())) self.stmt(r, "must have Load context") def test_try(self): @@ -1359,11 +1543,11 @@ def test_expr(self): def test_boolop(self): b = ast.BoolOp(ast.And(), []) self.expr(b, "less than 2 values") - b = ast.BoolOp(ast.And(), [ast.Num(3)]) + b = ast.BoolOp(ast.And(), [ast.Constant(3)]) self.expr(b, "less than 2 values") - b = ast.BoolOp(ast.And(), [ast.Num(4), None]) + b = ast.BoolOp(ast.And(), [ast.Constant(4), None]) self.expr(b, "None disallowed") - b = ast.BoolOp(ast.And(), [ast.Num(4), ast.Name("x", ast.Store())]) + b = ast.BoolOp(ast.And(), [ast.Constant(4), ast.Name("x", ast.Store())]) self.expr(b, "must have Load context") def test_unaryop(self): @@ -1451,11 +1635,11 @@ def test_compare(self): left = ast.Name("x", ast.Load()) comp = ast.Compare(left, [ast.In()], []) self.expr(comp, "no comparators") - comp = ast.Compare(left, [ast.In()], [ast.Num(4), ast.Num(5)]) + comp = ast.Compare(left, [ast.In()], [ast.Constant(4), ast.Constant(5)]) self.expr(comp, "different number of comparators and operands") - comp = ast.Compare(ast.Num("blah"), [ast.In()], [left]) + comp = ast.Compare(ast.Constant("blah"), [ast.In()], [left]) self.expr(comp) - comp = ast.Compare(left, [ast.In()], [ast.Num("blah")]) + comp = ast.Compare(left, [ast.In()], [ast.Constant("blah")]) self.expr(comp) def test_call(self): @@ -1471,23 +1655,37 @@ def test_call(self): self.expr(call, "must have Load context") def test_num(self): - class subint(int): - pass - class subfloat(float): - pass - class subcomplex(complex): - pass - for obj in "0", "hello": - self.expr(ast.Num(obj)) - for obj in subint(), subfloat(), subcomplex(): - self.expr(ast.Num(obj), "invalid type", exc=TypeError) + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import Num + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + class subint(int): + pass + class subfloat(float): + pass + class subcomplex(complex): + pass + for obj in "0", "hello": + self.expr(ast.Num(obj)) + for obj in subint(), subfloat(), subcomplex(): + self.expr(ast.Num(obj), "invalid type", exc=TypeError) + + self.assertEqual([str(w.message) for w in wlog], [ + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', + ]) def test_attribute(self): attr = ast.Attribute(ast.Name("x", ast.Store()), "y", ast.Load()) self.expr(attr, "must have Load context") def test_subscript(self): - sub = ast.Subscript(ast.Name("x", ast.Store()), ast.Num(3), + sub = ast.Subscript(ast.Name("x", ast.Store()), ast.Constant(3), ast.Load()) self.expr(sub, "must have Load context") x = ast.Name("x", ast.Load()) @@ -1507,7 +1705,7 @@ def test_subscript(self): def test_starred(self): left = ast.List([ast.Starred(ast.Name("x", ast.Load()), ast.Store())], ast.Store()) - assign = ast.Assign([left], ast.Num(4)) + assign = ast.Assign([left], ast.Constant(4)) self.stmt(assign, "must have Store context") def _sequence(self, fac): @@ -1522,7 +1720,17 @@ def test_tuple(self): self._sequence(ast.Tuple) def test_nameconstant(self): - self.expr(ast.NameConstant(4)) + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('ignore', '', DeprecationWarning) + from ast import NameConstant + + with warnings.catch_warnings(record=True) as wlog: + warnings.filterwarnings('always', '', DeprecationWarning) + self.expr(ast.NameConstant(4)) + + self.assertEqual([str(w.message) for w in wlog], [ + 'ast.NameConstant is deprecated; use ast.Constant instead', + ]) def test_stdlib_validates(self): stdlib = os.path.dirname(ast.__file__) @@ -2199,10 +2407,15 @@ def visit_Ellipsis(self, node): ]) self.assertEqual([str(w.message) for w in wlog], [ 'visit_Num is deprecated; add visit_Constant', + 'Attribute n is deprecated; use value instead', 'visit_Num is deprecated; add visit_Constant', + 'Attribute n is deprecated; use value instead', 'visit_Num is deprecated; add visit_Constant', + 'Attribute n is deprecated; use value instead', 'visit_Str is deprecated; add visit_Constant', + 'Attribute s is deprecated; use value instead', 'visit_Bytes is deprecated; add visit_Constant', + 'Attribute s is deprecated; use value instead', 'visit_NameConstant is deprecated; add visit_Constant', 'visit_NameConstant is deprecated; add visit_Constant', 'visit_Ellipsis is deprecated; add visit_Constant', diff --git a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst new file mode 100644 index 00000000000000..48675dfd477ed8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst @@ -0,0 +1,2 @@ +Deprecation warnings are now emitted for deprecated features in the +:mod:`ast` module which were deprecated in documentation only. From fdefe87709bd774b9d02ac584cd52884a5da9ecb Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 4 May 2023 13:12:59 +0100 Subject: [PATCH 02/20] fix patchcheck Co-authored-by: Hugo van Kemenade --- Lib/test/test_ast.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 9fb0fa1e83d053..03f7f5c5e32270 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -468,8 +468,8 @@ def test_field_attr_existence_deprecated(self): # The argument is required. continue with self.subTest(item): - with self.assertWarns(DeprecationWarning): - x = item() + with self.assertWarns(DeprecationWarning): + x = item() if isinstance(x, ast.AST): self.assertEqual(type(x._fields), tuple) From 60ab0c400f1ee88d498c1d1a146742557f7c02e0 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Thu, 4 May 2023 15:20:33 +0100 Subject: [PATCH 03/20] Remove `DeprecationWarning`s for `Index` and `ExtSlice` --- Lib/ast.py | 5 ----- Lib/test/test_ast.py | 20 +++++++------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 2bb62d561eda5a..826739554a324b 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -627,9 +627,6 @@ def __new__(cls, value, **kwargs): class ExtSlice(slice): """Deprecated AST node class. Use ast.Tuple instead.""" def __new__(cls, dims=(), **kwargs): - import warnings - warnings.warn("ast.ExtSlice is deprecated; use ast.Tuple instead", - DeprecationWarning, stacklevel=2) return Tuple(list(dims), Load(), **kwargs) # If the ast module is loaded more than once, only add deprecated methods once @@ -1737,8 +1734,6 @@ def unparse(ast_obj): for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis') } _deprecated_globals['slice'] = globals().pop('slice'), '' -_deprecated_globals['Index'] = globals().pop('Index'), '' -_deprecated_globals['ExtSlice'] = globals().pop('ExtSlice'), '; use ast.Tuple instead' def __getattr__(name): if name in _deprecated_globals: diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 03f7f5c5e32270..e452b3d94bef59 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -446,27 +446,17 @@ def test_import_deprecated(self): with self.assertWarnsRegex(DeprecationWarning, r'ast\.slice is deprecated'): ast.slice - with self.assertWarnsRegex(DeprecationWarning, - r'ast\.Index is deprecated'): - ast.Index - with self.assertWarnsRegex(DeprecationWarning, - r'ast\.ExtSlice is deprecated; use ast\.Tuple instead'): - ast.ExtSlice def test_field_attr_existence_deprecated(self): with warnings.catch_warnings(): warnings.filterwarnings('ignore', '', DeprecationWarning) from ast import Num, Str, Bytes, NameConstant, Ellipsis - from ast import slice, Index, ExtSlice + from ast import slice for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', - 'slice', 'Index', 'ExtSlice'): + 'slice'): item = getattr(ast, name) if self._is_ast_node(name, item): - if name == 'Index': - # Index(value) just returns value now. - # The argument is required. - continue with self.subTest(item): with self.assertWarns(DeprecationWarning): x = item() @@ -475,8 +465,12 @@ def test_field_attr_existence_deprecated(self): def test_field_attr_existence(self): for name, item in ast.__dict__.items(): + # These emit DeprecationWarnings if name in {'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', - 'slice', 'Index', 'ExtSlice'}: + 'slice'}: + continue + # constructor has a different signature + if name == 'Index': continue if self._is_ast_node(name, item): x = item() From 951c5487f575fb52eac7bc569ec6d59a0abeabdd Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 12:48:43 +0100 Subject: [PATCH 04/20] Also emit DeprecationWarning during `isinstance()` checks --- Lib/ast.py | 4 + Lib/test/test_ast.py | 183 ++++++++++++++++++++++++++++--------------- 2 files changed, 122 insertions(+), 65 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 826739554a324b..9cb1928b009365 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -537,6 +537,10 @@ def __init__(cls, *args): cls.__doc__ = """Deprecated AST node class. Use ast.Constant instead""" def __instancecheck__(cls, inst): + if cls in _const_types: + import warnings + warnings.warn(f"ast.{cls.__qualname__} is deprecated; use ast.Constant instead", + DeprecationWarning, stacklevel=2) if not isinstance(inst, Constant): return False if cls in _const_types: diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index e452b3d94bef59..e979d58bf009e2 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -8,6 +8,7 @@ import unittest import warnings import weakref +from functools import partial from textwrap import dedent from test import support @@ -656,76 +657,127 @@ def test_realtype(self): ]) def test_isinstance(self): + from ast import Constant + with warnings.catch_warnings(): warnings.filterwarnings('ignore', '', DeprecationWarning) from ast import Num, Str, Bytes, NameConstant, Ellipsis - with warnings.catch_warnings(record=True) as wlog: - warnings.filterwarnings('always', '', DeprecationWarning) - self.assertTrue(isinstance(ast.Num(42), ast.Num)) - self.assertTrue(isinstance(ast.Num(4.2), ast.Num)) - self.assertTrue(isinstance(ast.Num(4.2j), ast.Num)) - self.assertTrue(isinstance(ast.Str('42'), ast.Str)) - self.assertTrue(isinstance(ast.Bytes(b'42'), ast.Bytes)) - self.assertTrue(isinstance(ast.NameConstant(True), ast.NameConstant)) - self.assertTrue(isinstance(ast.NameConstant(False), ast.NameConstant)) - self.assertTrue(isinstance(ast.NameConstant(None), ast.NameConstant)) - self.assertTrue(isinstance(ast.Ellipsis(), ast.Ellipsis)) - - self.assertTrue(isinstance(ast.Constant(42), ast.Num)) - self.assertTrue(isinstance(ast.Constant(4.2), ast.Num)) - self.assertTrue(isinstance(ast.Constant(4.2j), ast.Num)) - self.assertTrue(isinstance(ast.Constant('42'), ast.Str)) - self.assertTrue(isinstance(ast.Constant(b'42'), ast.Bytes)) - self.assertTrue(isinstance(ast.Constant(True), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(False), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(None), ast.NameConstant)) - self.assertTrue(isinstance(ast.Constant(...), ast.Ellipsis)) - - self.assertFalse(isinstance(ast.Str('42'), ast.Num)) - self.assertFalse(isinstance(ast.Num(42), ast.Str)) - self.assertFalse(isinstance(ast.Str('42'), ast.Bytes)) - self.assertFalse(isinstance(ast.Num(42), ast.NameConstant)) - self.assertFalse(isinstance(ast.Num(42), ast.Ellipsis)) - self.assertFalse(isinstance(ast.NameConstant(True), ast.Num)) - self.assertFalse(isinstance(ast.NameConstant(False), ast.Num)) - - self.assertFalse(isinstance(ast.Constant('42'), ast.Num)) - self.assertFalse(isinstance(ast.Constant(42), ast.Str)) - self.assertFalse(isinstance(ast.Constant('42'), ast.Bytes)) - self.assertFalse(isinstance(ast.Constant(42), ast.NameConstant)) - self.assertFalse(isinstance(ast.Constant(42), ast.Ellipsis)) - self.assertFalse(isinstance(ast.Constant(True), ast.Num)) - self.assertFalse(isinstance(ast.Constant(False), ast.Num)) - - self.assertFalse(isinstance(ast.Constant(), ast.Num)) - self.assertFalse(isinstance(ast.Constant(), ast.Str)) - self.assertFalse(isinstance(ast.Constant(), ast.Bytes)) - self.assertFalse(isinstance(ast.Constant(), ast.NameConstant)) - self.assertFalse(isinstance(ast.Constant(), ast.Ellipsis)) - - class S(str): pass - self.assertTrue(isinstance(ast.Constant(S('42')), ast.Str)) - self.assertFalse(isinstance(ast.Constant(S('42')), ast.Num)) + assertNumDeprecated = partial( + self.assertWarnsRegex, + DeprecationWarning, + 'ast.Num is deprecated; use ast.Constant instead' + ) + assertStrDeprecated = partial( + self.assertWarnsRegex, + DeprecationWarning, + 'ast.Str is deprecated; use ast.Constant instead' + ) + assertBytesDeprecated = partial( + self.assertWarnsRegex, + DeprecationWarning, + 'ast.Bytes is deprecated; use ast.Constant instead' + ) + assertNameConstantDeprecated = partial( + self.assertWarnsRegex, + DeprecationWarning, + 'ast.NameConstant is deprecated; use ast.Constant instead' + ) + assertEllipsisDeprecated = partial( + self.assertWarnsRegex, + DeprecationWarning, + 'ast.Ellipsis is deprecated; use ast.Constant instead' + ) - self.assertEqual([str(w.message) for w in wlog], [ - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Str is deprecated; use ast.Constant instead', - 'ast.Bytes is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.Ellipsis is deprecated; use ast.Constant instead', - 'ast.Str is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Str is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - ]) + for arg in 42, 4.2, 4.2j: + with self.subTest(arg=arg): + with assertNumDeprecated(): + n = Num(arg) + with assertNumDeprecated(): + self.assertIsInstance(n, Num) + + with assertStrDeprecated(): + s = Str('42') + with assertStrDeprecated(): + self.assertIsInstance(s, Str) + + with assertBytesDeprecated(): + b = Bytes(b'42') + with assertBytesDeprecated(): + self.assertIsInstance(b, Bytes) + + for arg in True, False, None: + with self.subTest(arg=arg): + with assertNameConstantDeprecated(): + n = NameConstant(arg) + with assertNameConstantDeprecated(): + self.assertIsInstance(n, NameConstant) + + with assertEllipsisDeprecated(): + e = Ellipsis() + with assertEllipsisDeprecated(): + self.assertIsInstance(e, Ellipsis) + + for arg in 42, 4.2, 4.2j: + with self.subTest(arg=arg): + with assertNumDeprecated(): + self.assertIsInstance(Constant(arg), Num) + + with assertStrDeprecated(): + self.assertIsInstance(Constant('42'), Str) + + with assertBytesDeprecated(): + self.assertIsInstance(Constant(b'42'), Bytes) + + for arg in True, False, None: + with self.subTest(arg=arg): + with assertNameConstantDeprecated(): + self.assertIsInstance(Constant(arg), NameConstant) + + with assertEllipsisDeprecated(): + self.assertIsInstance(Constant(...), Ellipsis) + + with assertStrDeprecated(): + s = Str('42') + assertNumDeprecated(self.assertNotIsInstance, s, Num) + assertBytesDeprecated(self.assertNotIsInstance, s, Bytes) + + with assertNumDeprecated(): + n = Num(42) + assertStrDeprecated(self.assertNotIsInstance, n, Str) + assertNameConstantDeprecated(self.assertNotIsInstance, n, NameConstant) + assertEllipsisDeprecated(self.assertNotIsInstance, n, Ellipsis) + + with assertNameConstantDeprecated(): + n = NameConstant(True) + with assertNumDeprecated(): + self.assertNotIsInstance(n, Num) + + with assertNameConstantDeprecated(): + n = NameConstant(False) + with assertNumDeprecated(): + self.assertNotIsInstance(n, Num) + + for arg in '42', True, False: + with self.subTest(arg=arg): + with assertNumDeprecated(): + self.assertNotIsInstance(Constant(arg), Num) + + assertStrDeprecated(self.assertNotIsInstance, Constant(42), Str) + assertBytesDeprecated(self.assertNotIsInstance, Constant('42'), Bytes) + assertNameConstantDeprecated(self.assertNotIsInstance, Constant(42), NameConstant) + assertEllipsisDeprecated(self.assertNotIsInstance, Constant(42), Ellipsis) + assertNumDeprecated(self.assertNotIsInstance, Constant(), Num) + assertStrDeprecated(self.assertNotIsInstance, Constant(), Str) + assertBytesDeprecated(self.assertNotIsInstance, Constant(), Bytes) + assertNameConstantDeprecated(self.assertNotIsInstance, Constant(), NameConstant) + assertEllipsisDeprecated(self.assertNotIsInstance, Constant(), Ellipsis) + + class S(str): pass + with assertStrDeprecated(): + self.assertTrue(isinstance(Constant(S('42')), Str)) + with assertNumDeprecated(): + self.assertFalse(isinstance(Constant(S('42')), Num)) def test_constant_subclasses_deprecated(self): with warnings.catch_warnings(): @@ -757,6 +809,7 @@ class N2(ast.Num): 'Attribute n is deprecated; use value instead', 'Attribute n is deprecated; use value instead', 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated; use ast.Constant instead', 'Attribute n is deprecated; use value instead', 'Attribute n is deprecated; use value instead', ]) From 9025b7e8c2f21ba60e7aeb8ba17aa34b356af3d7 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 12:56:42 +0100 Subject: [PATCH 05/20] Add whatsnew --- Doc/whatsnew/3.12.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index edbf921467553c..a0728e9d7ea2fc 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -835,6 +835,19 @@ although there is currently no date scheduled for their removal. :keyword:`for`, :keyword:`if`, :keyword:`in`, :keyword:`is` and :keyword:`or`. In a future release it will be changed to a syntax error. (:gh:`87999`) +* The following :mod:`ast` features have been deprecated in documentation since + Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime + when they are accessed or used, and may be removed in a future version of Python: + + * :class:`!ast.Num` + * :class:`!ast.Str` + * :class:`!ast.Bytes` + * :class:`!ast.NameConstant` + * :class:`!ast.Ellipsis` + + Use :class:`ast.Constant` instead. + (Contributed by Serhiy Storchaka in :gh:`90953`.) + Removed ======= From 3d2de749eabc8d14b67f020c1b20804c744d8f8e Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 12:59:04 +0100 Subject: [PATCH 06/20] Tweak news entry --- .../next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst index 48675dfd477ed8..16847809855a78 100644 --- a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst +++ b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst @@ -1,2 +1,4 @@ -Deprecation warnings are now emitted for deprecated features in the -:mod:`ast` module which were deprecated in documentation only. +Deprecation warnings are now emitted for :class:`!ast.Num`, +:class:`!ast.Bytes`, :class:`!ast.Str`, :class:`!ast.NameConstant` and +:class:`!ast.Ellipsis`. These have been documented as deprecated since Python +3.8, and may be removed in a future version of Python. From 6e9f5f5fb0fc4c89bdc55ccd317e1852882b750e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 5 May 2023 13:48:04 +0100 Subject: [PATCH 07/20] Apply suggestions from code review Co-authored-by: Hugo van Kemenade --- Doc/whatsnew/3.12.rst | 2 +- .../next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 6a696988013df7..16b7a6ba1d4e33 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -858,7 +858,7 @@ although there is currently no date scheduled for their removal. * The following :mod:`ast` features have been deprecated in documentation since Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime - when they are accessed or used, and may be removed in a future version of Python: + when they are accessed or used, and will be removed in Python 3.14. * :class:`!ast.Num` * :class:`!ast.Str` diff --git a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst index 16847809855a78..6539efbc9d0eb0 100644 --- a/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst +++ b/Misc/NEWS.d/next/Library/2022-02-19-14-19-34.bpo-46797.6BXZX4.rst @@ -1,4 +1,4 @@ Deprecation warnings are now emitted for :class:`!ast.Num`, :class:`!ast.Bytes`, :class:`!ast.Str`, :class:`!ast.NameConstant` and :class:`!ast.Ellipsis`. These have been documented as deprecated since Python -3.8, and may be removed in a future version of Python. +3.8, and will be removed in Python 3.14. From 8f86b9a1e88c863253d09537edd64a9014a078ae Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 14:05:09 +0100 Subject: [PATCH 08/20] Use `warnings._deprecated --- Lib/ast.py | 51 +++++++++++------ Lib/test/test_ast.py | 128 +++++++++++++++++++++---------------------- 2 files changed, 99 insertions(+), 80 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 9cb1928b009365..8745cc8b94a71b 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -497,6 +497,15 @@ def generic_visit(self, node): return node +_DEPRECATED_VALUE_ALIAS_MESSAGE = ( + "{name} is deprecated and will be removed in Python {remove}; use value instead" +) +_DEPRECATED_CLASS_MESSAGE = ( + "{name} is deprecated and will be removed in Python {remove}; " + "use ast.Constant instead" +) + + # If the ast module is loaded more than once, only add deprecated methods once if not hasattr(Constant, 'n'): # The following code is for backward compatibility. @@ -505,27 +514,31 @@ def generic_visit(self, node): def _n_getter(self): """Deprecated. Use value instead.""" import warnings - warnings.warn("Attribute n is deprecated; use value instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + "Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14) + ) return self.value def _n_setter(self, value): import warnings - warnings.warn("Attribute n is deprecated; use value instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + "Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14) + ) self.value = value def _s_getter(self): """Deprecated. Use value instead.""" import warnings - warnings.warn("Attribute s is deprecated; use value instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + "Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14) + ) return self.value def _s_setter(self, value): import warnings - warnings.warn("Attribute s is deprecated; use value instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + "Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14) + ) self.value = value Constant.n = property(_n_getter, _n_setter) @@ -539,8 +552,11 @@ def __init__(cls, *args): def __instancecheck__(cls, inst): if cls in _const_types: import warnings - warnings.warn(f"ast.{cls.__qualname__} is deprecated; use ast.Constant instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + f"ast.{cls.__qualname__}", + message=_DEPRECATED_CLASS_MESSAGE, + remove=(3, 14) + ) if not isinstance(inst, Constant): return False if cls in _const_types: @@ -565,8 +581,9 @@ def _new(cls, *args, **kwargs): raise TypeError(f"{cls.__name__} got multiple values for argument {key!r}") if cls in _const_types: import warnings - warnings.warn(f"ast.{cls.__qualname__} is deprecated; use ast.Constant instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + f"ast.{cls.__qualname__}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) + ) return Constant(*args, **kwargs) return Constant.__new__(cls, *args, **kwargs) @@ -591,8 +608,9 @@ class Ellipsis(Constant, metaclass=_ABC): def __new__(cls, *args, **kwargs): if cls is _ast_Ellipsis: import warnings - warnings.warn("ast.Ellipsis is deprecated; use ast.Constant instead", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + f"ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) + ) return Constant(..., *args, **kwargs) return Constant.__new__(cls, *args, **kwargs) @@ -1744,8 +1762,9 @@ def __getattr__(name): value, details = _deprecated_globals[name] globals()[name] = value import warnings - warnings.warn(f"ast.{name} is deprecated{details}", - DeprecationWarning, stacklevel=2) + warnings._deprecated( + f"ast.{name}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) + ) return value raise AttributeError(f"module 'ast' has no attribute '{name}'") diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index e979d58bf009e2..fa701ce804e46e 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -440,9 +440,12 @@ def test_base_classes(self): def test_import_deprecated(self): ast = import_fresh_module('ast') + depr_regex = ( + r'ast\.{} is deprecated and will be removed in Python 3.14; ' + r'use ast\.Constant instead' + ) for name in 'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis': - with self.assertWarnsRegex(DeprecationWarning, - fr'ast\.{name} is deprecated; use ast\.Constant instead'): + with self.assertWarnsRegex(DeprecationWarning, depr_regex.format(name)): getattr(ast, name) with self.assertWarnsRegex(DeprecationWarning, r'ast\.slice is deprecated'): @@ -559,29 +562,29 @@ def test_classattrs_deprecated(self): self.assertIs(ast.NameConstant(None).value, None) self.assertEqual([str(w.message) for w in wlog], [ - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'ast.Str is deprecated; use ast.Constant instead', - 'Attribute s is deprecated; use value instead', - 'ast.Bytes is deprecated; use ast.Constant instead', - 'Attribute s is deprecated; use value instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Str is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute s is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Bytes is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute s is deprecated and will be removed in Python 3.14; use value instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', ]) def test_classattrs(self): @@ -645,15 +648,15 @@ def test_realtype(self): self.assertEqual(type(ast.Ellipsis()), ast.Constant) self.assertEqual([str(w.message) for w in wlog], [ - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Str is deprecated; use ast.Constant instead', - 'ast.Bytes is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.NameConstant is deprecated; use ast.Constant instead', - 'ast.Ellipsis is deprecated; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Str is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Bytes is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Ellipsis is deprecated and will be removed in Python 3.14; use ast.Constant instead', ]) def test_isinstance(self): @@ -663,30 +666,27 @@ def test_isinstance(self): warnings.filterwarnings('ignore', '', DeprecationWarning) from ast import Num, Str, Bytes, NameConstant, Ellipsis + cls_depr_msg = ( + 'ast.{} is deprecated and will be removed in Python 3.14; ' + 'use ast.Constant instead' + ) + assertNumDeprecated = partial( - self.assertWarnsRegex, - DeprecationWarning, - 'ast.Num is deprecated; use ast.Constant instead' + self.assertWarnsRegex, DeprecationWarning, cls_depr_msg.format("Num") ) assertStrDeprecated = partial( - self.assertWarnsRegex, - DeprecationWarning, - 'ast.Str is deprecated; use ast.Constant instead' + self.assertWarnsRegex, DeprecationWarning, cls_depr_msg.format("Str") ) assertBytesDeprecated = partial( - self.assertWarnsRegex, - DeprecationWarning, - 'ast.Bytes is deprecated; use ast.Constant instead' + self.assertWarnsRegex, DeprecationWarning, cls_depr_msg.format("Bytes") ) assertNameConstantDeprecated = partial( self.assertWarnsRegex, DeprecationWarning, - 'ast.NameConstant is deprecated; use ast.Constant instead' + cls_depr_msg.format("NameConstant") ) assertEllipsisDeprecated = partial( - self.assertWarnsRegex, - DeprecationWarning, - 'ast.Ellipsis is deprecated; use ast.Constant instead' + self.assertWarnsRegex, DeprecationWarning, cls_depr_msg.format("Ellipsis") ) for arg in 42, 4.2, 4.2j: @@ -806,12 +806,12 @@ class N2(ast.Num): self.assertEqual(type(n), N) self.assertEqual([str(w.message) for w in wlog], [ - 'Attribute n is deprecated; use value instead', - 'Attribute n is deprecated; use value instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'Attribute n is deprecated; use value instead', - 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', ]) def test_constant_subclasses(self): @@ -1866,11 +1866,11 @@ class subcomplex(complex): self.expr(ast.Num(obj), "invalid type", exc=TypeError) self.assertEqual([str(w.message) for w in wlog], [ - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', - 'ast.Num is deprecated; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', + 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', ]) def test_attribute(self): @@ -1922,7 +1922,7 @@ def test_nameconstant(self): self.expr(ast.NameConstant(4)) self.assertEqual([str(w.message) for w in wlog], [ - 'ast.NameConstant is deprecated; use ast.Constant instead', + 'ast.NameConstant is deprecated and will be removed in Python 3.14; use ast.Constant instead', ]) def test_stdlib_validates(self): @@ -2612,15 +2612,15 @@ def visit_Ellipsis(self, node): ]) self.assertEqual([str(w.message) for w in wlog], [ 'visit_Num is deprecated; add visit_Constant', - 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'visit_Num is deprecated; add visit_Constant', - 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'visit_Num is deprecated; add visit_Constant', - 'Attribute n is deprecated; use value instead', + 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', 'visit_Str is deprecated; add visit_Constant', - 'Attribute s is deprecated; use value instead', + 'Attribute s is deprecated and will be removed in Python 3.14; use value instead', 'visit_Bytes is deprecated; add visit_Constant', - 'Attribute s is deprecated; use value instead', + 'Attribute s is deprecated and will be removed in Python 3.14; use value instead', 'visit_NameConstant is deprecated; add visit_Constant', 'visit_NameConstant is deprecated; add visit_Constant', 'visit_Ellipsis is deprecated; add visit_Constant', From 8c949f37a9650273c9933ae5ad4190cabeabcebf Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 14:07:02 +0100 Subject: [PATCH 09/20] Move whatsnew entry to correct location --- Doc/whatsnew/3.12.rst | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 16b7a6ba1d4e33..eca499393f165b 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -839,6 +839,19 @@ Pending Removal in Python 3.14 use :func:`importlib.util.find_spec` instead. (Contributed by Nikita Sobolev in :gh:`97850`.) +* The following :mod:`ast` features have been deprecated in documentation since + Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime + when they are accessed or used, and will be removed in Python 3.14. + + * :class:`!ast.Num` + * :class:`!ast.Str` + * :class:`!ast.Bytes` + * :class:`!ast.NameConstant` + * :class:`!ast.Ellipsis` + + Use :class:`ast.Constant` instead. + (Contributed by Serhiy Storchaka in :gh:`90953`.) + Pending Removal in Future Versions ---------------------------------- @@ -856,20 +869,6 @@ although there is currently no date scheduled for their removal. :keyword:`for`, :keyword:`if`, :keyword:`in`, :keyword:`is` and :keyword:`or`. In a future release it will be changed to a syntax error. (:gh:`87999`) -* The following :mod:`ast` features have been deprecated in documentation since - Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime - when they are accessed or used, and will be removed in Python 3.14. - - * :class:`!ast.Num` - * :class:`!ast.Str` - * :class:`!ast.Bytes` - * :class:`!ast.NameConstant` - * :class:`!ast.Ellipsis` - - Use :class:`ast.Constant` instead. - (Contributed by Serhiy Storchaka in :gh:`90953`.) - - Removed ======= From 3d71e66cb4321b68ab822c631d72855298e1dd80 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 14:19:28 +0100 Subject: [PATCH 10/20] Revert spurious whitespace change --- Doc/whatsnew/3.12.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index eca499393f165b..b92dfbe07b0a0c 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -869,6 +869,7 @@ although there is currently no date scheduled for their removal. :keyword:`for`, :keyword:`if`, :keyword:`in`, :keyword:`is` and :keyword:`or`. In a future release it will be changed to a syntax error. (:gh:`87999`) + Removed ======= From 4261cc6551fea02f50dc33c4a406cc5e149c1683 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 14:26:59 +0100 Subject: [PATCH 11/20] Revert warning for `dims` attribute (only deprecated in 3.9) --- Lib/ast.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 8745cc8b94a71b..ce0c7e0c054141 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -658,14 +658,9 @@ def __new__(cls, dims=(), **kwargs): def _dims_getter(self): """Deprecated. Use elts instead.""" - import warnings - warnings.warn("Attribute dims is deprecated; use elts instead", - DeprecationWarning, stacklevel=2) return self.elts def _dims_setter(self, value): - warnings.warn("Attribute dims is deprecated, use elts instead", - DeprecationWarning, stacklevel=2) self.elts = value Tuple.dims = property(_dims_getter, _dims_setter) From a785a0c22c4c60d36b44588e63a152a564dab87a Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 15:23:08 +0100 Subject: [PATCH 12/20] Also remove deprecation for `ast.slice`, deprecated in 3.9 --- Lib/ast.py | 1 - Lib/test/test_ast.py | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index ce0c7e0c054141..494d4cac2238ba 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -1750,7 +1750,6 @@ def unparse(ast_obj): name: (globals().pop(name), '; use ast.Constant instead') for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis') } -_deprecated_globals['slice'] = globals().pop('slice'), '' def __getattr__(name): if name in _deprecated_globals: diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index fa701ce804e46e..84270328beaf6c 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -447,18 +447,13 @@ def test_import_deprecated(self): for name in 'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis': with self.assertWarnsRegex(DeprecationWarning, depr_regex.format(name)): getattr(ast, name) - with self.assertWarnsRegex(DeprecationWarning, - r'ast\.slice is deprecated'): - ast.slice def test_field_attr_existence_deprecated(self): with warnings.catch_warnings(): warnings.filterwarnings('ignore', '', DeprecationWarning) from ast import Num, Str, Bytes, NameConstant, Ellipsis - from ast import slice - for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', - 'slice'): + for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis'): item = getattr(ast, name) if self._is_ast_node(name, item): with self.subTest(item): @@ -470,8 +465,7 @@ def test_field_attr_existence_deprecated(self): def test_field_attr_existence(self): for name, item in ast.__dict__.items(): # These emit DeprecationWarnings - if name in {'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis', - 'slice'}: + if name in {'Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis'}: continue # constructor has a different signature if name == 'Index': From a3491eab5e5b8c0dbc2fff4c58e99626f1ba2ed9 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 15:35:18 +0100 Subject: [PATCH 13/20] Better unittest style --- Lib/test/test_ast.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 84270328beaf6c..a49c34d608c78f 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -769,9 +769,9 @@ def test_isinstance(self): class S(str): pass with assertStrDeprecated(): - self.assertTrue(isinstance(Constant(S('42')), Str)) + self.assertIsInstance(Constant(S('42')), Str) with assertNumDeprecated(): - self.assertFalse(isinstance(Constant(S('42')), Num)) + self.assertNotIsInstance(Constant(S('42')), Num) def test_constant_subclasses_deprecated(self): with warnings.catch_warnings(): @@ -790,14 +790,14 @@ class N2(ast.Num): n = N(42) self.assertEqual(n.n, 42) self.assertEqual(n.z, 'spam') - self.assertEqual(type(n), N) - self.assertTrue(isinstance(n, N)) - self.assertTrue(isinstance(n, ast.Num)) - self.assertFalse(isinstance(n, N2)) - self.assertFalse(isinstance(ast.Num(42), N)) + self.assertIs(type(n), N) + self.assertIsInstance(n, N) + self.assertIsinstance(n, ast.Num) + self.assertNotIsInstance(n, N2) + self.assertNotIsInstance(ast.Num(42), N) n = N(n=42) self.assertEqual(n.n, 42) - self.assertEqual(type(n), N) + self.assertIs(type(n), N) self.assertEqual([str(w.message) for w in wlog], [ 'Attribute n is deprecated and will be removed in Python 3.14; use value instead', From 3613908df40549c493dd56fca9dfcb62e42dd5ae Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 15:39:56 +0100 Subject: [PATCH 14/20] . --- Lib/test/test_ast.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index a49c34d608c78f..4126d9140c860c 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -631,15 +631,15 @@ def test_realtype(self): with warnings.catch_warnings(record=True) as wlog: warnings.filterwarnings('always', '', DeprecationWarning) - self.assertEqual(type(ast.Num(42)), ast.Constant) - self.assertEqual(type(ast.Num(4.25)), ast.Constant) - self.assertEqual(type(ast.Num(4.25j)), ast.Constant) - self.assertEqual(type(ast.Str('42')), ast.Constant) - self.assertEqual(type(ast.Bytes(b'42')), ast.Constant) - self.assertEqual(type(ast.NameConstant(True)), ast.Constant) - self.assertEqual(type(ast.NameConstant(False)), ast.Constant) - self.assertEqual(type(ast.NameConstant(None)), ast.Constant) - self.assertEqual(type(ast.Ellipsis()), ast.Constant) + self.assertIs(type(ast.Num(42)), ast.Constant) + self.assertIs(type(ast.Num(4.25)), ast.Constant) + self.assertIs(type(ast.Num(4.25j)), ast.Constant) + self.assertIs(type(ast.Str('42')), ast.Constant) + self.assertIs(type(ast.Bytes(b'42')), ast.Constant) + self.assertIs(type(ast.NameConstant(True)), ast.Constant) + self.assertIs(type(ast.NameConstant(False)), ast.Constant) + self.assertIs(type(ast.NameConstant(None)), ast.Constant) + self.assertIs(type(ast.Ellipsis()), ast.Constant) self.assertEqual([str(w.message) for w in wlog], [ 'ast.Num is deprecated and will be removed in Python 3.14; use ast.Constant instead', From 9bba5dd5d4332997e559003bcb35067936c73531 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 5 May 2023 15:49:42 +0100 Subject: [PATCH 15/20] Update 3.12.rst --- Doc/whatsnew/3.12.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index b92dfbe07b0a0c..32bfd5a106cdd4 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -841,7 +841,7 @@ Pending Removal in Python 3.14 * The following :mod:`ast` features have been deprecated in documentation since Python 3.8, now cause a :exc:`DeprecationWarning` to be emitted at runtime - when they are accessed or used, and will be removed in Python 3.14. + when they are accessed or used, and will be removed in Python 3.14: * :class:`!ast.Num` * :class:`!ast.Str` From 234243a3f26aa0790e29405c1a73f1c8471b4c19 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 5 May 2023 16:18:26 +0100 Subject: [PATCH 16/20] typo --- Lib/test/test_ast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 4126d9140c860c..810200f6306393 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -792,7 +792,7 @@ class N2(ast.Num): self.assertEqual(n.z, 'spam') self.assertIs(type(n), N) self.assertIsInstance(n, N) - self.assertIsinstance(n, ast.Num) + self.assertIsInstance(n, ast.Num) self.assertNotIsInstance(n, N2) self.assertNotIsInstance(ast.Num(42), N) n = N(n=42) From 915e12e363c3e6891ce9882be2bce7c7bb2cec8c Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 6 May 2023 15:09:52 +0100 Subject: [PATCH 17/20] Add comment explaining subtle code --- Lib/ast.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/ast.py b/Lib/ast.py index 494d4cac2238ba..4b876ea4404614 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -614,6 +614,9 @@ def __new__(cls, *args, **kwargs): return Constant(..., *args, **kwargs) return Constant.__new__(cls, *args, **kwargs) +# Keep another reference to Ellipsis in the global namespace +# so it can be referenced in Ellipsis.__new__ +# (The original "Ellipsis" name is removed from the global namespace later on) _ast_Ellipsis = Ellipsis _const_types = { From a7abed267655bcaad41ad14a16ab33ee93f7e1dc Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 6 May 2023 16:58:28 +0100 Subject: [PATCH 18/20] Simplify slightly --- Lib/ast.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/ast.py b/Lib/ast.py index 4b876ea4404614..a1870075a8164f 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -1750,14 +1750,13 @@ def unparse(ast_obj): _deprecated_globals = { - name: (globals().pop(name), '; use ast.Constant instead') + name: globals().pop(name) for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis') } def __getattr__(name): if name in _deprecated_globals: - value, details = _deprecated_globals[name] - globals()[name] = value + globals()[name] = value = _deprecated_globals[name] import warnings warnings._deprecated( f"ast.{name}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) From 7ceb9ccf9a10a2c7f405a516f938843996bda259 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 6 May 2023 17:12:26 +0100 Subject: [PATCH 19/20] Unnecessary f-string --- Lib/ast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ast.py b/Lib/ast.py index a1870075a8164f..65152047a22370 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -609,7 +609,7 @@ def __new__(cls, *args, **kwargs): if cls is _ast_Ellipsis: import warnings warnings._deprecated( - f"ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) + "ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14) ) return Constant(..., *args, **kwargs) return Constant.__new__(cls, *args, **kwargs) From ecf092fdc290d63676020c504f7a1303fe9f1a9d Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sat, 6 May 2023 17:18:47 +0100 Subject: [PATCH 20/20] One last nit --- Lib/test/test_ast.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 810200f6306393..fdd21aca06ffdd 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -460,7 +460,7 @@ def test_field_attr_existence_deprecated(self): with self.assertWarns(DeprecationWarning): x = item() if isinstance(x, ast.AST): - self.assertEqual(type(x._fields), tuple) + self.assertIs(type(x._fields), tuple) def test_field_attr_existence(self): for name, item in ast.__dict__.items(): @@ -473,7 +473,7 @@ def test_field_attr_existence(self): if self._is_ast_node(name, item): x = item() if isinstance(x, ast.AST): - self.assertEqual(type(x._fields), tuple) + self.assertIs(type(x._fields), tuple) def test_arguments(self): x = ast.arguments()