From 92580dc9922c82c7e690864b00073dd73847f045 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Thu, 21 Oct 2021 07:57:55 -0500 Subject: [PATCH 01/15] node changes --- astroid/nodes/node_classes.py | 35 +++++++++----- astroid/nodes/scoped_nodes.py | 87 +++++++++++++++++++++++++++++++++-- astroid/objects.py | 2 + tests/unittest_nodes.py | 7 +++ 4 files changed, 114 insertions(+), 17 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index c27b768926..6ae186b5cf 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -752,6 +752,8 @@ def __init__( vararg: Optional[str] = None, kwarg: Optional[str] = None, parent: Optional[NodeNG] = None, + lineno: Optional[int] = None, + col_offset: Optional[int] = None, ) -> None: """ :param vararg: The name of the variable length arguments. @@ -760,7 +762,7 @@ def __init__( :param parent: The parent node in the syntax tree. """ - super().__init__(parent=parent) + super().__init__(lineno=lineno, col_offset=col_offset, parent=parent) self.vararg: Optional[str] = vararg # can be None """The name of the variable length arguments.""" @@ -1284,6 +1286,7 @@ def __init__( lineno: Optional[int] = None, col_offset: Optional[int] = None, parent: Optional[NodeNG] = None, + simple: Optional[int] = None, ) -> None: """ :param lineno: The line that this node appears on in the source code. @@ -1302,7 +1305,7 @@ def __init__( self.value: Optional[NodeNG] = None # can be None """The value being assigned to the variables.""" - self.simple: Optional[int] = None + self.simple: Optional[int] = simple """Whether :attr:`target` is a pure name or a complex statement.""" super().__init__(lineno=lineno, col_offset=col_offset, parent=parent) @@ -1311,7 +1314,7 @@ def postinit( self, target: NodeNG, annotation: NodeNG, - simple: int, + simple: int = None, value: Optional[NodeNG] = None, ) -> None: """Do some setup after initialisation. @@ -1328,7 +1331,7 @@ def postinit( self.target = target self.annotation = annotation self.value = value - self.simple = simple + self.simple = simple or self.simple def get_children(self): yield self.target @@ -1759,7 +1762,13 @@ class Comprehension(NodeNG): optional_assign = True """Whether this node optionally assigns a variable.""" - def __init__(self, parent: Optional[NodeNG] = None) -> None: + def __init__( + self, + parent: Optional[NodeNG] = None, + lineno: Optional[int] = None, + col_offset: Optional[int] = None, + is_async: Optional[bool] = None, + ) -> None: """ :param parent: The parent node in the syntax tree. """ @@ -1772,10 +1781,10 @@ def __init__(self, parent: Optional[NodeNG] = None) -> None: self.ifs: typing.List[NodeNG] = [] """The contents of any if statements that filter the comprehension.""" - self.is_async: Optional[bool] = None + self.is_async: Optional[bool] = is_async """Whether this is an asynchronous comprehension or not.""" - super().__init__(parent=parent) + super().__init__(lineno=lineno, col_offset=col_offset, parent=parent) # pylint: disable=redefined-builtin; same name as builtin ast module. def postinit( @@ -1783,7 +1792,7 @@ def postinit( target: Optional[NodeNG] = None, iter: Optional[NodeNG] = None, ifs: Optional[typing.List[NodeNG]] = None, - is_async: Optional[bool] = None, + is_async: Optional[bool] = None, # @TODO: Deprecate and remove ) -> None: """Do some setup after initialisation. @@ -1800,7 +1809,7 @@ def postinit( self.iter = iter if ifs is not None: self.ifs = ifs - self.is_async = is_async + self.is_async = is_async or self.is_async def assign_type(self): """The type of assignment that this node performs. @@ -1846,7 +1855,7 @@ class Const(mixins.NoChildrenMixin, NodeNG, Instance): ] """ - _other_fields = ("value",) + _other_fields = ("value", "kind") def __init__( self, @@ -2630,7 +2639,7 @@ class ImportFrom(mixins.NoChildrenMixin, mixins.ImportFromMixin, Statement): """ - _other_fields = ("modname", "names", "level") + _other_fields = ("fromname", "names", "level") def __init__( self, @@ -2655,7 +2664,8 @@ def __init__( :param parent: The parent node in the syntax tree. """ - self.modname: Optional[str] = fromname # can be None + self.fromname: Optional[str] = fromname # can be None + self.modname = self.fromname # For backwards """The module that is being imported from. This is ``None`` for relative imports. @@ -4076,6 +4086,7 @@ class FormattedValue(NodeNG): """ _astroid_fields = ("value", "format_spec") + _other_other_fields = ("conversion",) def __init__( self, diff --git a/astroid/nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes.py index e9ccd4a2e1..724822a24b 100644 --- a/astroid/nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes.py @@ -43,6 +43,7 @@ import builtins import io import itertools +import json import os import typing from typing import List, Optional, TypeVar @@ -366,6 +367,27 @@ def __contains__(self, name): """ return name in self.locals + def __load__(self, data, loader): + """Load the node from the data dict.""" + self.__init__( + **{ + key: loader(data[key]) + for key in {"lineno", "col_offset", "parent"} + if key in data + }, + **{key: loader(data[key]) for key in self._other_fields}, + ) + + postinit_fields = set(self._astroid_fields + self._other_other_fields) - { + "locals", + "globals", + } + self.postinit( + **{key: loader(data[key]) for key in postinit_fields if key in data} + ) + # Use update so Module's globals get updated too + self.locals.update(loader(data["locals"])) + class Module(LocalsDictNodeNG): """Class representing an :class:`ast.Module` node. @@ -385,7 +407,7 @@ class Module(LocalsDictNodeNG): :type: int or None """ - lineno = 0 + lineno = None """The line that this node appears on in the source code. :type: int or None @@ -474,6 +496,7 @@ def __init__( package=None, parent=None, pure_python=True, + future_imports=None, ): """ :param name: The name of the module. @@ -496,6 +519,9 @@ def __init__( :param pure_python: Whether the ast was built from source. :type pure_python: bool or None + + :param future_imports: The imports from ``__future__``. + :type future_imports: Optional[Set[str]] """ self.name = name self.doc = doc @@ -514,7 +540,7 @@ def __init__( :type: list(NodeNG) or None """ - self.future_imports = set() + self.future_imports = future_imports or set() # pylint: enable=redefined-builtin @@ -826,6 +852,28 @@ def bool_value(self, context=None): def get_children(self): yield from self.body + def __dump__(self, dumper): + """Dump the node as a JSON-serializable dict.""" + # Don't serialize globals since globals is locals + assert self.locals is self.globals + data = super().__dump__(dumper) + data.pop("globals") + return data + + def dump(self): + """Dump the node as a JSON-serializable dict.""" + import astroid._persistence + + refmap = {} + data = astroid._persistence.dump(self, refmap) + return json.dumps(dict(refmap=refmap, data=data)) + + @classmethod + def load(cls, data): + import astroid._persistence + + return astroid._persistence.load(**json.loads(data)) + class ComprehensionScope(LocalsDictNodeNG): """Scoping for different types of comprehensions.""" @@ -1393,7 +1441,7 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): _other_fields = ("name", "doc") _other_other_fields = ( "locals", - "_type", + # "_type", "type_comment_returns", "type_comment_args", ) @@ -1839,6 +1887,15 @@ def scope_lookup(self, node, name, offset=0): return self, [frame] return super().scope_lookup(node, name, offset) + def __dump__(self, dumper): + data = super().__dump__(dumper) + data["instance_attrs"] = dumper(self.instance_attrs) + return data + + def __load__(self, data, loader): + super().__load__(data, loader) + self.instance_attrs = loader(data["instance_attrs"]) + class AsyncFunctionDef(FunctionDef): """Class representing an :class:`ast.FunctionDef` node. @@ -2054,6 +2111,14 @@ def __init__(self, name=None, doc=None, lineno=None, col_offset=None, parent=Non :type name: str or None """ + if not isinstance(doc, (str, type(None))): + # This can happen for C-implemented types, like `wrapper_descriptor` + # `type(str.__dict__['__add__'])` is a `wrapper_descriptor` + # `__doc__` on that object is a `getset_descriptor`, I assume + # because `wrapper_descriptor` has no doc. + # https://github.com/python/cpython/blob/f25f2e2e8c8e48490d22b0cdf67f575608701f6f/Objects/descrobject.c#L865 + doc = None + self.doc = doc """The class' docstring. @@ -2105,8 +2170,7 @@ def postinit( :param keywords: The keywords given to the class definition. :type keywords: list(Keyword) or None """ - if keywords is not None: - self.keywords = keywords + self.keywords = keywords self.bases = bases self.body = body self.decorators = decorators @@ -3054,3 +3118,16 @@ def _get_assign_nodes(self): child_node._get_assign_nodes() for child_node in self.body ) return list(itertools.chain.from_iterable(children_assign_nodes)) + + def __dump__(self, dumper): + data = super().__dump__(dumper) + data["instance_attrs"] = dumper(self.instance_attrs) + data["metaclass"] = dumper(self._metaclass) + return data + + def __load__(self, data, loader): + newstyle = data.pop("_newstyle") + super().__load__(data, loader) + self.instance_attrs = loader(data["instance_attrs"]) + self._metaclass = loader(data["metaclass"]) + self._newstyle = newstyle diff --git a/astroid/objects.py b/astroid/objects.py index 2ad3b558e3..834ea33471 100644 --- a/astroid/objects.py +++ b/astroid/objects.py @@ -306,6 +306,8 @@ def qname(self): class Property(scoped_nodes.FunctionDef): """Class representing a Python property""" + _other_fields = scoped_nodes.FunctionDef._other_fields + ("function",) + def __init__( self, function, name=None, doc=None, lineno=None, col_offset=None, parent=None ): diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 81c8379f45..7b47a0f9c4 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -1704,6 +1704,13 @@ def test_match_class(): *case1.pattern.kwd_patterns, ] +@pytest.mark.parametrize( + "node_class", nodes.ALL_NODE_CLASSES +) +def test_fields_declaration(node_class): + if not isinstance(node_class, nodes.NodeNG): + pass + ... if __name__ == "__main__": unittest.main() From 89a5a98f1356b40aca24bd3f6dac72e7c3fb7493 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Thu, 21 Oct 2021 09:26:21 -0500 Subject: [PATCH 02/15] init test --- astroid/nodes/node_classes.py | 20 ++++++++++++++------ astroid/rebuilder.py | 8 ++++++-- tests/unittest_nodes.py | 23 ++++++++++++++++++----- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 6ae186b5cf..d36db40a78 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -4263,7 +4263,7 @@ class EvaluatedObject(NodeNG): name = "EvaluatedObject" _astroid_fields = ("original",) - _other_fields = ("value",) + _other_other_fields = ("value",) def __init__( self, original: NodeNG, value: typing.Union[NodeNG, util.Uninferable] @@ -4344,11 +4344,17 @@ class MatchCase(mixins.MultiLineBlockMixin, NodeNG): _astroid_fields = ("pattern", "guard", "body") _multi_line_block_fields = ("body",) - def __init__(self, *, parent: Optional[NodeNG] = None) -> None: + def __init__( + self, + *, + lineno: Optional[int] = None, + col_offset: Optional[int] = None, + parent: Optional[NodeNG] = None + ) -> None: self.pattern: Pattern self.guard: Optional[NodeNG] self.body: typing.List[NodeNG] - super().__init__(parent=parent) + super().__init__(lineno=lineno, col_offset=col_offset, parent=parent) def postinit( self, @@ -4530,10 +4536,12 @@ def __init__( lineno: Optional[int] = None, col_offset: Optional[int] = None, parent: Optional[NodeNG] = None, + *, + kwd_attrs: typing.List[str] = [], ) -> None: self.cls: NodeNG self.patterns: typing.List[Pattern] - self.kwd_attrs: typing.List[str] + self.kwd_attrs = kwd_attrs self.kwd_patterns: typing.List[Pattern] super().__init__(lineno=lineno, col_offset=col_offset, parent=parent) @@ -4542,12 +4550,12 @@ def postinit( *, cls: NodeNG, patterns: typing.List[Pattern], - kwd_attrs: typing.List[str], kwd_patterns: typing.List[Pattern], + kwd_attrs: typing.List[str] = [], # @TODO: Handle deprecation (moved to __init__) ) -> None: self.cls = cls self.patterns = patterns - self.kwd_attrs = kwd_attrs + self.kwd_attrs = kwd_attrs or self.kwd_attrs self.kwd_patterns = kwd_patterns diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index b67a9c9460..0fb6e6b80f 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -1788,11 +1788,15 @@ def visit_matchmapping( def visit_matchclass( self, node: "ast.MatchClass", parent: NodeNG ) -> nodes.MatchClass: - newnode = nodes.MatchClass(node.lineno, node.col_offset, parent) + newnode = nodes.MatchClass( + node.lineno, + node.col_offset, + parent, + kwd_attrs=node.kwd_attrs, + ) newnode.postinit( cls=self.visit(node.cls, newnode), patterns=[self.visit(pattern, newnode) for pattern in node.patterns], - kwd_attrs=node.kwd_attrs, kwd_patterns=[ self.visit(pattern, newnode) for pattern in node.kwd_patterns ], diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 7b47a0f9c4..917466ec9f 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -29,6 +29,7 @@ """tests for specific behaviour of astroid nodes """ import copy +import inspect import os import platform import sys @@ -56,6 +57,7 @@ AstroidSyntaxError, AttributeInferenceError, ) +from astroid.nodes import node_classes from astroid.nodes.node_classes import ( AssignAttr, AssignName, @@ -1705,12 +1707,23 @@ def test_match_class(): ] @pytest.mark.parametrize( - "node_class", nodes.ALL_NODE_CLASSES -) + "node_class", [ + node_class for node_class in nodes.ALL_NODE_CLASSES + if isinstance(node_class, type) and issubclass(node_class, nodes.NodeNG) and node_class is not nodes.EvaluatedObject + ]) def test_fields_declaration(node_class): - if not isinstance(node_class, nodes.NodeNG): - pass - ... + expected_init_fields = set(node_class._other_fields) + expected_postinit_fields = node_class._astroid_fields + node_class._other_other_fields + args = set(inspect.signature(node_class.__init__).parameters.keys()) + args -= {"self"} + + if node_class not in {nodes.Module, nodes.EvaluatedObject}: + expected_init_fields |= {"lineno", "col_offset"} + + assert "parent" not in expected_init_fields + expected_init_fields |= {"parent"} + + assert args == expected_init_fields if __name__ == "__main__": unittest.main() From 5c91e846cf7358df9fd497da0e7300947b00c2d2 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 22 Oct 2021 16:06:39 -0500 Subject: [PATCH 03/15] Ensure the _fields attributes and __init__/postinit are cohesive --- astroid/decorators.py | 41 ++++++++++++++++- astroid/nodes/node_classes.py | 9 ++-- astroid/nodes/node_ng.py | 2 + astroid/nodes/scoped_nodes.py | 2 +- astroid/rebuilder.py | 6 +-- tests/unittest_decorators.py | 84 +++++++++++++++++++++++++++++++++-- tests/unittest_nodes.py | 41 +++++++++++++---- 7 files changed, 164 insertions(+), 21 deletions(-) diff --git a/astroid/decorators.py b/astroid/decorators.py index 66d9cb0926..4591dae762 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -197,7 +197,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: ) ): warnings.warn( - f"'{arg}' will be a required attribute for " + f"'{arg}' will be a required argument for " f"'{args[0].__class__.__qualname__}.{func.__name__}' in astroid {astroid_version} " f"('{arg}' should be of type: '{type_annotation}')", DeprecationWarning, @@ -207,3 +207,42 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: return wrapper return deco + +def deprecate_arguments(*arguments: str, hint: str) -> Callable[[Callable[P, R]], Callable[P, R]]: + """Decorator which emitts a DeprecationWarning if any arguments specified + are provided. + """ + def deco(func: Callable[P, R]) -> Callable[P, R]: + """Decorator function.""" + + parameter_names = inspect.signature(func).parameters.keys() + for arg in arguments: + if arg not in parameter_names: + raise Exception( + f"Can't find argument '{arg}'" + ) + params = list(parameter_names) + func.deprecated_args = arguments + + @functools.wraps(func) + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + """Emit DeprecationWarnings if conditions are met.""" + + for arg in arguments: + index = params.index(arg) + if ( + arg in kwargs + or len(args) > index + ): + warnings.warn( + f"'{arg}' is a deprecated argument for " + f"'{args[0].__class__.__qualname__}.{func.__name__}' " + "and will be removed in a future version of astroid." + f"({hint})", + DeprecationWarning, + ) + return func(*args, **kwargs) + + return wrapper + + return deco \ No newline at end of file diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index d36db40a78..7216bc2142 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -1310,6 +1310,7 @@ def __init__( super().__init__(lineno=lineno, col_offset=col_offset, parent=parent) + @decorators.deprecate_arguments("simple", hint="Pass it to __init__ instead") def postinit( self, target: NodeNG, @@ -1787,6 +1788,7 @@ def __init__( super().__init__(lineno=lineno, col_offset=col_offset, parent=parent) # pylint: disable=redefined-builtin; same name as builtin ast module. + @decorators.deprecate_arguments("is_async", hint="Pass to __init__ instead") def postinit( self, target: Optional[NodeNG] = None, @@ -4537,21 +4539,22 @@ def __init__( col_offset: Optional[int] = None, parent: Optional[NodeNG] = None, *, - kwd_attrs: typing.List[str] = [], + kwd_attrs: typing.List[str] = None, ) -> None: self.cls: NodeNG self.patterns: typing.List[Pattern] - self.kwd_attrs = kwd_attrs + self.kwd_attrs = kwd_attrs or [] self.kwd_patterns: typing.List[Pattern] super().__init__(lineno=lineno, col_offset=col_offset, parent=parent) + @decorators.deprecate_arguments("kwd_attrs", hint="Pass it to __init__ instead") def postinit( self, *, cls: NodeNG, patterns: typing.List[Pattern], kwd_patterns: typing.List[Pattern], - kwd_attrs: typing.List[str] = [], # @TODO: Handle deprecation (moved to __init__) + kwd_attrs: typing.List[str] = [], ) -> None: self.cls = cls self.patterns = patterns diff --git a/astroid/nodes/node_ng.py b/astroid/nodes/node_ng.py index 147e692210..34fcd37417 100644 --- a/astroid/nodes/node_ng.py +++ b/astroid/nodes/node_ng.py @@ -171,6 +171,8 @@ def __str__(self): result = [] for field in self._other_fields + self._astroid_fields: value = getattr(self, field) + if value is None: + continue width = 80 - len(field) - alignment lines = pprint.pformat(value, indent=2, width=width).splitlines(True) diff --git a/astroid/nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes.py index 724822a24b..ef14055633 100644 --- a/astroid/nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes.py @@ -2057,7 +2057,7 @@ def my_meth(self, arg): ), ) _other_fields = ("name", "doc") - _other_other_fields = ("locals", "_newstyle") + _other_other_fields = ("locals", "_newstyle", "_metaclass") _newstyle = None def __init__(self, name=None, doc=None, lineno=None, col_offset=None, parent=None): diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 0fb6e6b80f..2e0d5ed92d 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -973,11 +973,10 @@ def visit_assign(self, node: "ast.Assign", parent: NodeNG) -> nodes.Assign: def visit_annassign(self, node: "ast.AnnAssign", parent: NodeNG) -> nodes.AnnAssign: """visit an AnnAssign node by returning a fresh instance of it""" - newnode = nodes.AnnAssign(node.lineno, node.col_offset, parent) + newnode = nodes.AnnAssign(node.lineno, node.col_offset, parent, simple=node.simple) newnode.postinit( target=self.visit(node.target, newnode), annotation=self.visit(node.annotation, newnode), - simple=node.simple, value=self.visit(node.value, newnode), ) return newnode @@ -1112,12 +1111,11 @@ def visit_comprehension( self, node: "ast.comprehension", parent: NodeNG ) -> nodes.Comprehension: """visit a Comprehension node by returning a fresh instance of it""" - newnode = nodes.Comprehension(parent) + newnode = nodes.Comprehension(parent, is_async=bool(node.is_async)) newnode.postinit( self.visit(node.target, newnode), self.visit(node.iter, newnode), [self.visit(child, newnode) for child in node.ifs], - bool(node.is_async), ) return newnode diff --git a/tests/unittest_decorators.py b/tests/unittest_decorators.py index 4672f870a6..00f31226aa 100644 --- a/tests/unittest_decorators.py +++ b/tests/unittest_decorators.py @@ -1,18 +1,22 @@ import pytest from _pytest.recwarn import WarningsRecorder -from astroid.decorators import deprecate_default_argument_values +import astroid.decorators class SomeClass: - @deprecate_default_argument_values(name="str") + @astroid.decorators.deprecate_default_argument_values(name="str") def __init__(self, name=None, lineno=None): ... - @deprecate_default_argument_values("3.2", name="str", var="int") + @astroid.decorators.deprecate_default_argument_values("3.2", name="str", var="int") def func(self, name=None, var=None, type_annotation=None): ... +class SomeOtherClass: + @astroid.decorators.deprecate_arguments("foo", "bar", hint="pass to `func2` instead") + def func(self, foo=None, bar=None, baz=None): + ... class TestDeprecationDecorators: @staticmethod @@ -97,3 +101,77 @@ def test_deprecated_default_argument_values_ok(recwarn: WarningsRecorder) -> Non instance = SomeClass(name="some_name") instance.func(name="", var=42) assert len(recwarn) == 0 + + @staticmethod + def test_deprecated_argument_pass_by_position() -> None: + with pytest.warns(DeprecationWarning) as records: + SomeOtherClass().func(1, 2, 3) + assert len(records) == 2 + assert "foo" in records[0].message.args[0] + assert "'SomeOtherClass.func'" in records[0].message.args[0] + assert "bar" in records[1].message.args[0] + assert "'SomeOtherClass.func'" in records[1].message.args[0] + + with pytest.warns(DeprecationWarning) as records: + SomeOtherClass().func(1, 2) + assert len(records) == 2 + assert "foo" in records[0].message.args[0] + assert "'SomeOtherClass.func'" in records[0].message.args[0] + assert "bar" in records[1].message.args[0] + assert "'SomeOtherClass.func'" in records[1].message.args[0] + + with pytest.warns(DeprecationWarning) as records: + SomeOtherClass().func(1) + assert len(records) == 1 + assert "foo" in records[0].message.args[0] + assert "'SomeOtherClass.func'" in records[0].message.args[0] + + with pytest.warns(DeprecationWarning) as records: + SomeOtherClass().func(1, baz=3) + assert len(records) == 1 + assert "foo" in records[0].message.args[0] + assert "'SomeOtherClass.func'" in records[0].message.args[0] + + with pytest.warns(None) as records: + SomeOtherClass().func(baz=3) + assert len(records) == 0 + + @staticmethod + def test_deprecated_argument_pass_by_name() -> None: + with pytest.warns(DeprecationWarning) as records: + SomeOtherClass().func(foo=1, bar=2, baz=3) + assert len(records) == 2 + assert "foo" in records[0].message.args[0] + assert "'SomeOtherClass.func'" in records[0].message.args[0] + assert "bar" in records[1].message.args[0] + assert "'SomeOtherClass.func'" in records[1].message.args[0] + + with pytest.warns(DeprecationWarning) as records: + SomeOtherClass().func(foo=1, bar=2) + assert len(records) == 2 + assert "foo" in records[0].message.args[0] + assert "'SomeOtherClass.func'" in records[0].message.args[0] + assert "bar" in records[1].message.args[0] + assert "'SomeOtherClass.func'" in records[1].message.args[0] + + with pytest.warns(DeprecationWarning) as records: + SomeOtherClass().func(foo=1) + assert len(records) == 1 + assert "foo" in records[0].message.args[0] + assert "'SomeOtherClass.func'" in records[0].message.args[0] + + with pytest.warns(DeprecationWarning) as records: + SomeOtherClass().func(bar=1) + assert len(records) == 1 + assert "bar" in records[0].message.args[0] + assert "'SomeOtherClass.func'" in records[0].message.args[0] + + with pytest.warns(DeprecationWarning) as records: + SomeOtherClass().func(1, baz=3) + assert len(records) == 1 + assert "foo" in records[0].message.args[0] + assert "'SomeOtherClass.func'" in records[0].message.args[0] + + with pytest.warns(None) as records: + SomeOtherClass().func(baz=3) + assert len(records) == 0 diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 917466ec9f..030689435e 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -52,6 +52,7 @@ ) from astroid.const import PY38_PLUS, PY310_PLUS, Context from astroid.context import InferenceContext +from astroid.decorators import deprecate_arguments from astroid.exceptions import ( AstroidBuildingError, AstroidSyntaxError, @@ -1711,19 +1712,41 @@ def test_match_class(): node_class for node_class in nodes.ALL_NODE_CLASSES if isinstance(node_class, type) and issubclass(node_class, nodes.NodeNG) and node_class is not nodes.EvaluatedObject ]) -def test_fields_declaration(node_class): - expected_init_fields = set(node_class._other_fields) - expected_postinit_fields = node_class._astroid_fields + node_class._other_other_fields - args = set(inspect.signature(node_class.__init__).parameters.keys()) - args -= {"self"} +def test_init_fields_declaration(node_class): + expected_args = set(node_class._other_fields) + actual_args = set(inspect.signature(node_class.__init__).parameters.keys()) + actual_args -= {"self"} if node_class not in {nodes.Module, nodes.EvaluatedObject}: - expected_init_fields |= {"lineno", "col_offset"} + expected_args |= {"lineno", "col_offset"} - assert "parent" not in expected_init_fields - expected_init_fields |= {"parent"} + assert "parent" not in expected_args + expected_args |= {"parent"} - assert args == expected_init_fields + assert actual_args == expected_args + +@pytest.mark.parametrize( + "node_class", [ + node_class for node_class in nodes.ALL_NODE_CLASSES + if isinstance(node_class, type) and issubclass(node_class, nodes.NodeNG) and node_class is not nodes.EvaluatedObject + ]) +def test_postinit_fields_declaration(node_class): + expected_args = set(node_class._astroid_fields + node_class._other_other_fields) + + if not expected_args: + assert not hasattr(node_class, "postinit") + return + + expected_args = {arg.lstrip("_") for arg in expected_args} + actual_args = set(inspect.signature(node_class.postinit).parameters.keys()) + deprecated_args = getattr(node_class.postinit, "deprecated_args", []) + actual_args -= {"self", *deprecated_args} + if issubclass(node_class, nodes.LocalsDictNodeNG): + expected_args -= {"locals"} + if node_class is nodes.Module: + expected_args -= {"globals"} + + assert actual_args == expected_args if __name__ == "__main__": unittest.main() From 21d3496c46a9fbe6bfb05826a9999d5b40da2d1e Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 22 Oct 2021 16:13:03 -0500 Subject: [PATCH 04/15] changelog --- ChangeLog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 98b014a5a0..b8ff162731 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,10 @@ What's New in astroid 2.8.4? ============================ Release date: TBA +* Fix incosistency between ``NodeNG`` subclass ``*_field`` definitions and + ``__init__``/``postinit``. A ``DeprecationWarning`` will be emitted for code + which provides an argument to the now-incorrect method (generally the + parameters have been moved from ``postinit`` to ``__init__``). What's New in astroid 2.8.3? From ac264e40f34f6185f1e4f6c08a553cdfb4814c63 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 22 Oct 2021 16:19:00 -0500 Subject: [PATCH 05/15] Fixes from self-review --- astroid/nodes/node_classes.py | 2 +- astroid/nodes/scoped_nodes.py | 79 +---------------------------------- 2 files changed, 3 insertions(+), 78 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 7216bc2142..58fec44a25 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -2667,7 +2667,7 @@ def __init__( :param parent: The parent node in the syntax tree. """ self.fromname: Optional[str] = fromname # can be None - self.modname = self.fromname # For backwards + self.modname = self.fromname # For backwards compatibility """The module that is being imported from. This is ``None`` for relative imports. diff --git a/astroid/nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes.py index ef14055633..a4e689edb5 100644 --- a/astroid/nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes.py @@ -43,7 +43,6 @@ import builtins import io import itertools -import json import os import typing from typing import List, Optional, TypeVar @@ -367,27 +366,6 @@ def __contains__(self, name): """ return name in self.locals - def __load__(self, data, loader): - """Load the node from the data dict.""" - self.__init__( - **{ - key: loader(data[key]) - for key in {"lineno", "col_offset", "parent"} - if key in data - }, - **{key: loader(data[key]) for key in self._other_fields}, - ) - - postinit_fields = set(self._astroid_fields + self._other_other_fields) - { - "locals", - "globals", - } - self.postinit( - **{key: loader(data[key]) for key in postinit_fields if key in data} - ) - # Use update so Module's globals get updated too - self.locals.update(loader(data["locals"])) - class Module(LocalsDictNodeNG): """Class representing an :class:`ast.Module` node. @@ -410,8 +388,8 @@ class Module(LocalsDictNodeNG): lineno = None """The line that this node appears on in the source code. - :type: int or None - """ + :type: int or None" + """" # attributes below are set by the builder module or by raw factories @@ -852,28 +830,6 @@ def bool_value(self, context=None): def get_children(self): yield from self.body - def __dump__(self, dumper): - """Dump the node as a JSON-serializable dict.""" - # Don't serialize globals since globals is locals - assert self.locals is self.globals - data = super().__dump__(dumper) - data.pop("globals") - return data - - def dump(self): - """Dump the node as a JSON-serializable dict.""" - import astroid._persistence - - refmap = {} - data = astroid._persistence.dump(self, refmap) - return json.dumps(dict(refmap=refmap, data=data)) - - @classmethod - def load(cls, data): - import astroid._persistence - - return astroid._persistence.load(**json.loads(data)) - class ComprehensionScope(LocalsDictNodeNG): """Scoping for different types of comprehensions.""" @@ -1441,7 +1397,6 @@ class FunctionDef(mixins.MultiLineBlockMixin, node_classes.Statement, Lambda): _other_fields = ("name", "doc") _other_other_fields = ( "locals", - # "_type", "type_comment_returns", "type_comment_args", ) @@ -1887,15 +1842,6 @@ def scope_lookup(self, node, name, offset=0): return self, [frame] return super().scope_lookup(node, name, offset) - def __dump__(self, dumper): - data = super().__dump__(dumper) - data["instance_attrs"] = dumper(self.instance_attrs) - return data - - def __load__(self, data, loader): - super().__load__(data, loader) - self.instance_attrs = loader(data["instance_attrs"]) - class AsyncFunctionDef(FunctionDef): """Class representing an :class:`ast.FunctionDef` node. @@ -2111,14 +2057,6 @@ def __init__(self, name=None, doc=None, lineno=None, col_offset=None, parent=Non :type name: str or None """ - if not isinstance(doc, (str, type(None))): - # This can happen for C-implemented types, like `wrapper_descriptor` - # `type(str.__dict__['__add__'])` is a `wrapper_descriptor` - # `__doc__` on that object is a `getset_descriptor`, I assume - # because `wrapper_descriptor` has no doc. - # https://github.com/python/cpython/blob/f25f2e2e8c8e48490d22b0cdf67f575608701f6f/Objects/descrobject.c#L865 - doc = None - self.doc = doc """The class' docstring. @@ -3118,16 +3056,3 @@ def _get_assign_nodes(self): child_node._get_assign_nodes() for child_node in self.body ) return list(itertools.chain.from_iterable(children_assign_nodes)) - - def __dump__(self, dumper): - data = super().__dump__(dumper) - data["instance_attrs"] = dumper(self.instance_attrs) - data["metaclass"] = dumper(self._metaclass) - return data - - def __load__(self, data, loader): - newstyle = data.pop("_newstyle") - super().__load__(data, loader) - self.instance_attrs = loader(data["instance_attrs"]) - self._metaclass = loader(data["metaclass"]) - self._newstyle = newstyle From 1eb489caaf4fca410450a07de8ab779a365174ec Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 Oct 2021 21:19:45 +0000 Subject: [PATCH 06/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- astroid/decorators.py | 17 ++++++++--------- astroid/nodes/node_classes.py | 2 +- astroid/rebuilder.py | 4 +++- tests/unittest_decorators.py | 6 +++++- tests/unittest_nodes.py | 31 +++++++++++++++++++++---------- 5 files changed, 38 insertions(+), 22 deletions(-) diff --git a/astroid/decorators.py b/astroid/decorators.py index 4591dae762..5b25402e3a 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -208,19 +208,21 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: return deco -def deprecate_arguments(*arguments: str, hint: str) -> Callable[[Callable[P, R]], Callable[P, R]]: + +def deprecate_arguments( + *arguments: str, hint: str +) -> Callable[[Callable[P, R]], Callable[P, R]]: """Decorator which emitts a DeprecationWarning if any arguments specified are provided. """ + def deco(func: Callable[P, R]) -> Callable[P, R]: """Decorator function.""" parameter_names = inspect.signature(func).parameters.keys() for arg in arguments: if arg not in parameter_names: - raise Exception( - f"Can't find argument '{arg}'" - ) + raise Exception(f"Can't find argument '{arg}'") params = list(parameter_names) func.deprecated_args = arguments @@ -230,10 +232,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: for arg in arguments: index = params.index(arg) - if ( - arg in kwargs - or len(args) > index - ): + if arg in kwargs or len(args) > index: warnings.warn( f"'{arg}' is a deprecated argument for " f"'{args[0].__class__.__qualname__}.{func.__name__}' " @@ -245,4 +244,4 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: return wrapper - return deco \ No newline at end of file + return deco diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 58fec44a25..3310f97af9 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -4351,7 +4351,7 @@ def __init__( *, lineno: Optional[int] = None, col_offset: Optional[int] = None, - parent: Optional[NodeNG] = None + parent: Optional[NodeNG] = None, ) -> None: self.pattern: Pattern self.guard: Optional[NodeNG] diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 2e0d5ed92d..f9be757d83 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -973,7 +973,9 @@ def visit_assign(self, node: "ast.Assign", parent: NodeNG) -> nodes.Assign: def visit_annassign(self, node: "ast.AnnAssign", parent: NodeNG) -> nodes.AnnAssign: """visit an AnnAssign node by returning a fresh instance of it""" - newnode = nodes.AnnAssign(node.lineno, node.col_offset, parent, simple=node.simple) + newnode = nodes.AnnAssign( + node.lineno, node.col_offset, parent, simple=node.simple + ) newnode.postinit( target=self.visit(node.target, newnode), annotation=self.visit(node.annotation, newnode), diff --git a/tests/unittest_decorators.py b/tests/unittest_decorators.py index 00f31226aa..64e096301e 100644 --- a/tests/unittest_decorators.py +++ b/tests/unittest_decorators.py @@ -13,11 +13,15 @@ def __init__(self, name=None, lineno=None): def func(self, name=None, var=None, type_annotation=None): ... + class SomeOtherClass: - @astroid.decorators.deprecate_arguments("foo", "bar", hint="pass to `func2` instead") + @astroid.decorators.deprecate_arguments( + "foo", "bar", hint="pass to `func2` instead" + ) def func(self, foo=None, bar=None, baz=None): ... + class TestDeprecationDecorators: @staticmethod def test_deprecated_default_argument_values_one_arg() -> None: diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 030689435e..d7ba9bb4fd 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -52,13 +52,11 @@ ) from astroid.const import PY38_PLUS, PY310_PLUS, Context from astroid.context import InferenceContext -from astroid.decorators import deprecate_arguments from astroid.exceptions import ( AstroidBuildingError, AstroidSyntaxError, AttributeInferenceError, ) -from astroid.nodes import node_classes from astroid.nodes.node_classes import ( AssignAttr, AssignName, @@ -1707,11 +1705,17 @@ def test_match_class(): *case1.pattern.kwd_patterns, ] + @pytest.mark.parametrize( - "node_class", [ - node_class for node_class in nodes.ALL_NODE_CLASSES - if isinstance(node_class, type) and issubclass(node_class, nodes.NodeNG) and node_class is not nodes.EvaluatedObject - ]) + "node_class", + [ + node_class + for node_class in nodes.ALL_NODE_CLASSES + if isinstance(node_class, type) + and issubclass(node_class, nodes.NodeNG) + and node_class is not nodes.EvaluatedObject + ], +) def test_init_fields_declaration(node_class): expected_args = set(node_class._other_fields) actual_args = set(inspect.signature(node_class.__init__).parameters.keys()) @@ -1725,11 +1729,17 @@ def test_init_fields_declaration(node_class): assert actual_args == expected_args + @pytest.mark.parametrize( - "node_class", [ - node_class for node_class in nodes.ALL_NODE_CLASSES - if isinstance(node_class, type) and issubclass(node_class, nodes.NodeNG) and node_class is not nodes.EvaluatedObject - ]) + "node_class", + [ + node_class + for node_class in nodes.ALL_NODE_CLASSES + if isinstance(node_class, type) + and issubclass(node_class, nodes.NodeNG) + and node_class is not nodes.EvaluatedObject + ], +) def test_postinit_fields_declaration(node_class): expected_args = set(node_class._astroid_fields + node_class._other_other_fields) @@ -1748,5 +1758,6 @@ def test_postinit_fields_declaration(node_class): assert actual_args == expected_args + if __name__ == "__main__": unittest.main() From 445220cedeeecca1b21f5124f4ffbbe1231ec996 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 22 Oct 2021 16:23:47 -0500 Subject: [PATCH 07/15] Fix syntax typo --- astroid/nodes/scoped_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes.py index a4e689edb5..9957dafba3 100644 --- a/astroid/nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes.py @@ -389,7 +389,7 @@ class Module(LocalsDictNodeNG): """The line that this node appears on in the source code. :type: int or None" - """" + """ # attributes below are set by the builder module or by raw factories From 720b7d865c00fa21bb4eb98e03d1cc163ebceb72 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 22 Oct 2021 16:32:57 -0500 Subject: [PATCH 08/15] flake8 --- astroid/nodes/node_classes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 3310f97af9..9407a0b990 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -4539,7 +4539,7 @@ def __init__( col_offset: Optional[int] = None, parent: Optional[NodeNG] = None, *, - kwd_attrs: typing.List[str] = None, + kwd_attrs: typing.Optional[typing.List[str]] = None, ) -> None: self.cls: NodeNG self.patterns: typing.List[Pattern] @@ -4554,11 +4554,11 @@ def postinit( cls: NodeNG, patterns: typing.List[Pattern], kwd_patterns: typing.List[Pattern], - kwd_attrs: typing.List[str] = [], + kwd_attrs: typing.Optional[typing.List[str]] = None, ) -> None: self.cls = cls self.patterns = patterns - self.kwd_attrs = kwd_attrs or self.kwd_attrs + self.kwd_attrs = kwd_attrs or self.kwd_attrs or [] self.kwd_patterns = kwd_patterns From 1bcdb307b4873c232fa2aa42ad446e189f9707a2 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 22 Oct 2021 16:51:11 -0500 Subject: [PATCH 09/15] fix lineno --- astroid/nodes/scoped_nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/astroid/nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes.py index 9957dafba3..f447b211aa 100644 --- a/astroid/nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes.py @@ -385,10 +385,10 @@ class Module(LocalsDictNodeNG): :type: int or None """ - lineno = None + lineno = 0 """The line that this node appears on in the source code. - :type: int or None" + :type: int or None """ # attributes below are set by the builder module or by raw factories From 607cc430b5d64afac481ce2db7f51eae1cf7a66b Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 22 Oct 2021 17:34:07 -0500 Subject: [PATCH 10/15] no more foo/bar --- tests/unittest_decorators.py | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/unittest_decorators.py b/tests/unittest_decorators.py index 64e096301e..475fbed02f 100644 --- a/tests/unittest_decorators.py +++ b/tests/unittest_decorators.py @@ -16,9 +16,9 @@ def func(self, name=None, var=None, type_annotation=None): class SomeOtherClass: @astroid.decorators.deprecate_arguments( - "foo", "bar", hint="pass to `func2` instead" + "arg1", "arg2", hint="pass to `func2` instead" ) - def func(self, foo=None, bar=None, baz=None): + def func(self, arg1=None, arg2=None, arg3=None): ... @@ -111,29 +111,29 @@ def test_deprecated_argument_pass_by_position() -> None: with pytest.warns(DeprecationWarning) as records: SomeOtherClass().func(1, 2, 3) assert len(records) == 2 - assert "foo" in records[0].message.args[0] + assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] - assert "bar" in records[1].message.args[0] + assert "arg2" in records[1].message.args[0] assert "'SomeOtherClass.func'" in records[1].message.args[0] with pytest.warns(DeprecationWarning) as records: SomeOtherClass().func(1, 2) assert len(records) == 2 - assert "foo" in records[0].message.args[0] + assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] - assert "bar" in records[1].message.args[0] + assert "arg2" in records[1].message.args[0] assert "'SomeOtherClass.func'" in records[1].message.args[0] with pytest.warns(DeprecationWarning) as records: SomeOtherClass().func(1) assert len(records) == 1 - assert "foo" in records[0].message.args[0] + assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] with pytest.warns(DeprecationWarning) as records: SomeOtherClass().func(1, baz=3) assert len(records) == 1 - assert "foo" in records[0].message.args[0] + assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] with pytest.warns(None) as records: @@ -143,37 +143,37 @@ def test_deprecated_argument_pass_by_position() -> None: @staticmethod def test_deprecated_argument_pass_by_name() -> None: with pytest.warns(DeprecationWarning) as records: - SomeOtherClass().func(foo=1, bar=2, baz=3) + SomeOtherClass().func(arg1=1, arg2=2, baz=3) assert len(records) == 2 - assert "foo" in records[0].message.args[0] + assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] - assert "bar" in records[1].message.args[0] + assert "arg2" in records[1].message.args[0] assert "'SomeOtherClass.func'" in records[1].message.args[0] with pytest.warns(DeprecationWarning) as records: - SomeOtherClass().func(foo=1, bar=2) + SomeOtherClass().func(arg1=1, arg2=2) assert len(records) == 2 - assert "foo" in records[0].message.args[0] + assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] - assert "bar" in records[1].message.args[0] + assert "arg2" in records[1].message.args[0] assert "'SomeOtherClass.func'" in records[1].message.args[0] with pytest.warns(DeprecationWarning) as records: - SomeOtherClass().func(foo=1) + SomeOtherClass().func(arg1=1) assert len(records) == 1 - assert "foo" in records[0].message.args[0] + assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] with pytest.warns(DeprecationWarning) as records: - SomeOtherClass().func(bar=1) + SomeOtherClass().func(arg2=1) assert len(records) == 1 - assert "bar" in records[0].message.args[0] + assert "arg2" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] with pytest.warns(DeprecationWarning) as records: SomeOtherClass().func(1, baz=3) assert len(records) == 1 - assert "foo" in records[0].message.args[0] + assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] with pytest.warns(None) as records: From 3dd7c7255a9c222e21123407ac5aa0a3f3d49a36 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Fri, 22 Oct 2021 17:35:23 -0500 Subject: [PATCH 11/15] baz --- tests/unittest_decorators.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/unittest_decorators.py b/tests/unittest_decorators.py index 475fbed02f..cf4c70374c 100644 --- a/tests/unittest_decorators.py +++ b/tests/unittest_decorators.py @@ -131,19 +131,19 @@ def test_deprecated_argument_pass_by_position() -> None: assert "'SomeOtherClass.func'" in records[0].message.args[0] with pytest.warns(DeprecationWarning) as records: - SomeOtherClass().func(1, baz=3) + SomeOtherClass().func(1, arg3=3) assert len(records) == 1 assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] with pytest.warns(None) as records: - SomeOtherClass().func(baz=3) + SomeOtherClass().func(arg3=3) assert len(records) == 0 @staticmethod def test_deprecated_argument_pass_by_name() -> None: with pytest.warns(DeprecationWarning) as records: - SomeOtherClass().func(arg1=1, arg2=2, baz=3) + SomeOtherClass().func(arg1=1, arg2=2, arg3=3) assert len(records) == 2 assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] @@ -171,11 +171,11 @@ def test_deprecated_argument_pass_by_name() -> None: assert "'SomeOtherClass.func'" in records[0].message.args[0] with pytest.warns(DeprecationWarning) as records: - SomeOtherClass().func(1, baz=3) + SomeOtherClass().func(1, arg3=3) assert len(records) == 1 assert "arg1" in records[0].message.args[0] assert "'SomeOtherClass.func'" in records[0].message.args[0] with pytest.warns(None) as records: - SomeOtherClass().func(baz=3) + SomeOtherClass().func(arg3=3) assert len(records) == 0 From 1fbfe2b9d9793f4cc46cbe2615d4a7ef2be463f7 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sat, 23 Oct 2021 15:53:36 -0500 Subject: [PATCH 12/15] Update astroid/decorators.py Co-authored-by: Pierre Sassoulas --- astroid/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/decorators.py b/astroid/decorators.py index 5b25402e3a..e70af384b6 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -212,7 +212,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R: def deprecate_arguments( *arguments: str, hint: str ) -> Callable[[Callable[P, R]], Callable[P, R]]: - """Decorator which emitts a DeprecationWarning if any arguments specified + """Decorator which emits a DeprecationWarning if any arguments specified are provided. """ From 481805c0eab1a58e47e42f463b41ce53c09f06aa Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sat, 23 Oct 2021 15:53:43 -0500 Subject: [PATCH 13/15] Update astroid/decorators.py Co-authored-by: Pierre Sassoulas --- astroid/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/decorators.py b/astroid/decorators.py index e70af384b6..23246047f9 100644 --- a/astroid/decorators.py +++ b/astroid/decorators.py @@ -222,7 +222,7 @@ def deco(func: Callable[P, R]) -> Callable[P, R]: parameter_names = inspect.signature(func).parameters.keys() for arg in arguments: if arg not in parameter_names: - raise Exception(f"Can't find argument '{arg}'") + raise ValueError(f"Can't find argument '{arg}'") params = list(parameter_names) func.deprecated_args = arguments From b5af638e51cb66ce690c4d5df5ced9d369212e85 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sat, 23 Oct 2021 15:53:49 -0500 Subject: [PATCH 14/15] Update astroid/nodes/node_classes.py Co-authored-by: Pierre Sassoulas --- astroid/nodes/node_classes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 9407a0b990..870f9ec6cf 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -1315,7 +1315,7 @@ def postinit( self, target: NodeNG, annotation: NodeNG, - simple: int = None, + simple: Optional[int] = None, value: Optional[NodeNG] = None, ) -> None: """Do some setup after initialisation. From db0ea1c60d5aa779b1afdba1da61806ec61a1294 Mon Sep 17 00:00:00 2001 From: Joshua Cannon Date: Sat, 23 Oct 2021 15:54:06 -0500 Subject: [PATCH 15/15] Update tests/unittest_nodes.py Co-authored-by: Pierre Sassoulas --- tests/unittest_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index d7ba9bb4fd..2605da1a79 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -1740,7 +1740,7 @@ def test_init_fields_declaration(node_class): and node_class is not nodes.EvaluatedObject ], ) -def test_postinit_fields_declaration(node_class): +def test_postinit_fields_declaration(node_class: nodes.NodeNG) -> None: expected_args = set(node_class._astroid_fields + node_class._other_other_fields) if not expected_args: