Skip to content

Commit

Permalink
gh-90953: Emit deprecation warnings for ast features deprecated in …
Browse files Browse the repository at this point in the history
…Python 3.8 (#104199)

`ast.Num`, `ast.Str`, `ast.Bytes`, `ast.Ellipsis` and `ast.NameConstant` now all emit deprecation warnings on import, access, instantation or `isinstance()` checks.

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
  • Loading branch information
AlexWaygood and serhiy-storchaka committed May 6, 2023
1 parent 263abd3 commit 376137f
Show file tree
Hide file tree
Showing 4 changed files with 472 additions and 135 deletions.
13 changes: 13 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,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
----------------------------------

Expand Down
82 changes: 74 additions & 8 deletions Lib/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,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
Expand Down Expand Up @@ -499,27 +497,66 @@ 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.
# It will be removed in future.

def _getter(self):
def _n_getter(self):
"""Deprecated. Use value instead."""
import warnings
warnings._deprecated(
"Attribute n", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
)
return self.value

def _n_setter(self, value):
import warnings
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._deprecated(
"Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
)
return self.value

def _setter(self, value):
def _s_setter(self, value):
import warnings
warnings._deprecated(
"Attribute s", message=_DEPRECATED_VALUE_ALIAS_MESSAGE, remove=(3, 14)
)
self.value = value

Constant.n = property(_getter, _setter)
Constant.s = property(_getter, _setter)
Constant.n = property(_n_getter, _n_setter)
Constant.s = property(_s_getter, _s_setter)

class _ABC(type):

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._deprecated(
f"ast.{cls.__qualname__}",
message=_DEPRECATED_CLASS_MESSAGE,
remove=(3, 14)
)
if not isinstance(inst, Constant):
return False
if cls in _const_types:
Expand All @@ -543,6 +580,10 @@ 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._deprecated(
f"ast.{cls.__qualname__}", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
)
return Constant(*args, **kwargs)
return Constant.__new__(cls, *args, **kwargs)

Expand All @@ -565,10 +606,19 @@ class Ellipsis(Constant, metaclass=_ABC):
_fields = ()

def __new__(cls, *args, **kwargs):
if cls is Ellipsis:
if cls is _ast_Ellipsis:
import warnings
warnings._deprecated(
"ast.Ellipsis", message=_DEPRECATED_CLASS_MESSAGE, remove=(3, 14)
)
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 = {
Num: (int, float, complex),
Str: (str,),
Expand Down Expand Up @@ -1699,6 +1749,22 @@ def unparse(ast_obj):
return unparser.visit(ast_obj)


_deprecated_globals = {
name: globals().pop(name)
for name in ('Num', 'Str', 'Bytes', 'NameConstant', 'Ellipsis')
}

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


def main():
import argparse

Expand Down

0 comments on commit 376137f

Please sign in to comment.