From 8de6eba6d7bcbe9bce0264edacae00675430a535 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Sat, 19 Feb 2022 14:20:14 +0200 Subject: [PATCH 1/2] 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 <Alex.Waygood@Gmail.com> Date: Thu, 4 May 2023 13:12:59 +0100 Subject: [PATCH 2/2] fix patchcheck Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com> --- 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)