From d0046c7167ddbaff2a74ff346ab47c7633174e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sun, 19 Jun 2022 12:40:12 +0200 Subject: [PATCH 1/2] Move ``Mixins`` to ``_base_nodes`` --- ChangeLog | 4 + astroid/mixins.py | 185 +++++------------------------------ astroid/nodes/_base_nodes.py | 168 +++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+), 159 deletions(-) create mode 100644 astroid/nodes/_base_nodes.py diff --git a/ChangeLog b/ChangeLog index b1f9af8f11..a7b40d61f9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,10 @@ Release date: TBA Closes #1512 +* The ``astroid.mixins`` module has been deprecated and marked for removal in 3.0.0. + + Closes #1633 + * Capture and log messages emitted by C extensions when importing them. This prevents contaminating programmatic output, e.g. pylint's JSON reporter. diff --git a/astroid/mixins.py b/astroid/mixins.py index 700df095e5..2166948dca 100644 --- a/astroid/mixins.py +++ b/astroid/mixins.py @@ -5,162 +5,29 @@ """This module contains some mixins for the different nodes. """ -from __future__ import annotations - -import itertools -import sys -from typing import TYPE_CHECKING - -from astroid import decorators -from astroid.exceptions import AttributeInferenceError - -if TYPE_CHECKING: - from astroid import nodes - -if sys.version_info >= (3, 8): - from functools import cached_property -else: - from astroid.decorators import cachedproperty as cached_property - - -class BlockRangeMixIn: - """override block range""" - - @cached_property - def blockstart_tolineno(self): - return self.lineno - - def _elsed_block_range(self, lineno, orelse, last=None): - """handle block line numbers range for try/finally, for, if and while - statements - """ - if lineno == self.fromlineno: - return lineno, lineno - if orelse: - if lineno >= orelse[0].fromlineno: - return lineno, orelse[-1].tolineno - return lineno, orelse[0].fromlineno - 1 - return lineno, last or self.tolineno - - -class FilterStmtsMixin: - """Mixin for statement filtering and assignment type""" - - def _get_filtered_stmts(self, _, node, _stmts, mystmt: nodes.Statement | None): - """method used in _filter_stmts to get statements and trigger break""" - if self.statement(future=True) is mystmt: - # original node's statement is the assignment, only keep - # current node (gen exp, list comp) - return [node], True - return _stmts, False - - def assign_type(self): - return self - - -class AssignTypeMixin: - def assign_type(self): - return self - - def _get_filtered_stmts( - self, lookup_node, node, _stmts, mystmt: nodes.Statement | None - ): - """method used in filter_stmts""" - if self is mystmt: - return _stmts, True - if self.statement(future=True) is mystmt: - # original node's statement is the assignment, only keep - # current node (gen exp, list comp) - return [node], True - return _stmts, False - - -class ParentAssignTypeMixin(AssignTypeMixin): - def assign_type(self): - return self.parent.assign_type() - - -class ImportFromMixin(FilterStmtsMixin): - """MixIn for From and Import Nodes""" - - def _infer_name(self, frame, name): - return name - - def do_import_module(self, modname=None): - """return the ast for a module whose name is imported by """ - # handle special case where we are on a package node importing a module - # using the same name as the package, which may end in an infinite loop - # on relative imports - # XXX: no more needed ? - mymodule = self.root() - level = getattr(self, "level", None) # Import as no level - if modname is None: - modname = self.modname - # XXX we should investigate deeper if we really want to check - # importing itself: modname and mymodule.name be relative or absolute - if mymodule.relative_to_absolute_name(modname, level) == mymodule.name: - # FIXME: we used to raise InferenceError here, but why ? - return mymodule - - return mymodule.import_module( - modname, level=level, relative_only=level and level >= 1 - ) - - def real_name(self, asname): - """get name from 'as' name""" - for name, _asname in self.names: - if name == "*": - return asname - if not _asname: - name = name.split(".", 1)[0] - _asname = name - if asname == _asname: - return name - raise AttributeInferenceError( - "Could not find original name for {attribute} in {target!r}", - target=self, - attribute=asname, - ) - - -class MultiLineBlockMixin: - """Mixin for nodes with multi-line blocks, e.g. For and FunctionDef. - Note that this does not apply to every node with a `body` field. - For instance, an If node has a multi-line body, but the body of an - IfExpr is not multi-line, and hence cannot contain Return nodes, - Assign nodes, etc. - """ - - @cached_property - def _multi_line_blocks(self): - return tuple(getattr(self, field) for field in self._multi_line_block_fields) - - def _get_return_nodes_skip_functions(self): - for block in self._multi_line_blocks: - for child_node in block: - if child_node.is_function: - continue - yield from child_node._get_return_nodes_skip_functions() - - def _get_yield_nodes_skip_lambdas(self): - for block in self._multi_line_blocks: - for child_node in block: - if child_node.is_lambda: - continue - yield from child_node._get_yield_nodes_skip_lambdas() - - @decorators.cached - def _get_assign_nodes(self): - children_assign_nodes = ( - child_node._get_assign_nodes() - for block in self._multi_line_blocks - for child_node in block - ) - return list(itertools.chain.from_iterable(children_assign_nodes)) - - -class NoChildrenMixin: - """Mixin for nodes with no children, e.g. Pass.""" - - def get_children(self): - yield from () +import warnings + +from astroid.nodes._base_nodes import ( + AssignTypeMixin, + BlockRangeMixIn, + FilterStmtsMixin, + ImportFromMixin, + MultiLineBlockMixin, + NoChildrenMixin, + ParentAssignTypeMixin, +) + +__all__ = ( + "AssignTypeMixin", + "BlockRangeMixIn", + "FilterStmtsMixin", + "ImportFromMixin", + "MultiLineBlockMixin", + "NoChildrenMixin", + "ParentAssignTypeMixin", +) + +warnings.warn( + "The 'astroid.mixins' module is deprecated and will become private in astroid 3.0.0", + DeprecationWarning, +) diff --git a/astroid/nodes/_base_nodes.py b/astroid/nodes/_base_nodes.py new file mode 100644 index 0000000000..32a41f05c0 --- /dev/null +++ b/astroid/nodes/_base_nodes.py @@ -0,0 +1,168 @@ +# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html +# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE +# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt + +"""This module contains some base nodes that can be inherited for the different nodes. + +Previously these were called Mixin nodes. +""" + +from __future__ import annotations + +import itertools +import sys +from typing import TYPE_CHECKING + +from astroid import decorators +from astroid.exceptions import AttributeInferenceError + +if TYPE_CHECKING: + from astroid import nodes + +if sys.version_info >= (3, 8): + from functools import cached_property +else: + from astroid.decorators import cachedproperty as cached_property + + +class BlockRangeMixIn: + """override block range""" + + @cached_property + def blockstart_tolineno(self): + return self.lineno + + def _elsed_block_range(self, lineno, orelse, last=None): + """handle block line numbers range for try/finally, for, if and while + statements + """ + if lineno == self.fromlineno: + return lineno, lineno + if orelse: + if lineno >= orelse[0].fromlineno: + return lineno, orelse[-1].tolineno + return lineno, orelse[0].fromlineno - 1 + return lineno, last or self.tolineno + + +class FilterStmtsMixin: + """Mixin for statement filtering and assignment type""" + + def _get_filtered_stmts(self, _, node, _stmts, mystmt: nodes.Statement | None): + """method used in _filter_stmts to get statements and trigger break""" + if self.statement(future=True) is mystmt: + # original node's statement is the assignment, only keep + # current node (gen exp, list comp) + return [node], True + return _stmts, False + + def assign_type(self): + return self + + +class AssignTypeMixin: + def assign_type(self): + return self + + def _get_filtered_stmts( + self, lookup_node, node, _stmts, mystmt: nodes.Statement | None + ): + """method used in filter_stmts""" + if self is mystmt: + return _stmts, True + if self.statement(future=True) is mystmt: + # original node's statement is the assignment, only keep + # current node (gen exp, list comp) + return [node], True + return _stmts, False + + +class ParentAssignTypeMixin(AssignTypeMixin): + def assign_type(self): + return self.parent.assign_type() + + +class ImportFromMixin(FilterStmtsMixin): + """MixIn for From and Import Nodes""" + + def _infer_name(self, frame, name): + return name + + def do_import_module(self, modname=None): + """return the ast for a module whose name is imported by """ + # handle special case where we are on a package node importing a module + # using the same name as the package, which may end in an infinite loop + # on relative imports + # XXX: no more needed ? + mymodule = self.root() + level = getattr(self, "level", None) # Import as no level + if modname is None: + modname = self.modname + # XXX we should investigate deeper if we really want to check + # importing itself: modname and mymodule.name be relative or absolute + if mymodule.relative_to_absolute_name(modname, level) == mymodule.name: + # FIXME: we used to raise InferenceError here, but why ? + return mymodule + + return mymodule.import_module( + modname, level=level, relative_only=level and level >= 1 + ) + + def real_name(self, asname): + """get name from 'as' name""" + for name, _asname in self.names: + if name == "*": + return asname + if not _asname: + name = name.split(".", 1)[0] + _asname = name + if asname == _asname: + return name + raise AttributeInferenceError( + "Could not find original name for {attribute} in {target!r}", + target=self, + attribute=asname, + ) + + +class MultiLineBlockMixin: + """Mixin for nodes with multi-line blocks, e.g. For and FunctionDef. + Note that this does not apply to every node with a `body` field. + For instance, an If node has a multi-line body, but the body of an + IfExpr is not multi-line, and hence cannot contain Return nodes, + Assign nodes, etc. + """ + + @cached_property + def _multi_line_blocks(self): + return tuple(getattr(self, field) for field in self._multi_line_block_fields) + + def _get_return_nodes_skip_functions(self): + for block in self._multi_line_blocks: + for child_node in block: + if child_node.is_function: + continue + yield from child_node._get_return_nodes_skip_functions() + + def _get_yield_nodes_skip_lambdas(self): + for block in self._multi_line_blocks: + for child_node in block: + if child_node.is_lambda: + continue + yield from child_node._get_yield_nodes_skip_lambdas() + + @decorators.cached + def _get_assign_nodes(self): + children_assign_nodes = ( + child_node._get_assign_nodes() + for block in self._multi_line_blocks + for child_node in block + ) + return list(itertools.chain.from_iterable(children_assign_nodes)) + + +class NoChildrenMixin: + """Mixin for nodes with no children, e.g. Pass.""" + + def get_children(self): + yield from () From 9a2a4be0d2c925b29c5b431bcccdf138e321704c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:37:52 +0200 Subject: [PATCH 2/2] Update --- astroid/nodes/_base_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/nodes/_base_nodes.py b/astroid/nodes/_base_nodes.py index 32a41f05c0..83f49b520a 100644 --- a/astroid/nodes/_base_nodes.py +++ b/astroid/nodes/_base_nodes.py @@ -88,7 +88,7 @@ class ImportFromMixin(FilterStmtsMixin): def _infer_name(self, frame, name): return name - def do_import_module(self, modname=None): + def do_import_module(self, modname: str | None = None) -> nodes.Module: """return the ast for a module whose name is imported by """ # handle special case where we are on a package node importing a module # using the same name as the package, which may end in an infinite loop