Skip to content

Move Mixins to _base_nodes #1635

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
185 changes: 26 additions & 159 deletions astroid/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: str | None = None) -> nodes.Module:
"""return the ast for a module whose name is <modname> imported by <self>"""
# 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,
)
168 changes: 168 additions & 0 deletions astroid/nodes/_base_nodes.py
Original file line number Diff line number Diff line change
@@ -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: str | None = None) -> nodes.Module:
"""return the ast for a module whose name is <modname> imported by <self>"""
# 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 ()