Skip to content

Commit

Permalink
Use absolute import qualified module name for deprecated module check (
Browse files Browse the repository at this point in the history
…#4678)

* Use absolute import qualified module name for deprecated module check
* Improved docstring of get_import_name
  • Loading branch information
matusvalo committed Jul 6, 2021
1 parent 70bd7d7 commit 0f8212f
Show file tree
Hide file tree
Showing 13 changed files with 53 additions and 22 deletions.
3 changes: 3 additions & 0 deletions ChangeLog
Expand Up @@ -17,6 +17,9 @@ Release date: TBA
..
Put bug fixes that should not wait for a new minor version here

* Fix false-positive ``deprecated-module`` when relative import uses deprecated module name.

Closes #4629

* Fix false-positive ``consider-using-with`` (R1732) if ``contextlib.ExitStack`` takes care of calling the ``__exit__`` method

Expand Down
4 changes: 2 additions & 2 deletions pylint/checkers/deprecated.py
Expand Up @@ -8,7 +8,7 @@
import astroid

from pylint.checkers import utils
from pylint.checkers.utils import safe_infer
from pylint.checkers.utils import get_import_name, safe_infer

ACCEPTABLE_NODES = (
astroid.BoundMethod,
Expand Down Expand Up @@ -109,6 +109,7 @@ def visit_decorators(self, node):
def visit_importfrom(self, node):
"""triggered when a from statement is seen"""
basename = node.modname
basename = get_import_name(node, basename)
self.check_deprecated_module(node, basename)
class_names = (name for name, _ in node.names)
self.check_deprecated_class(node, basename, class_names)
Expand Down Expand Up @@ -175,7 +176,6 @@ def deprecated_classes(self, module: str) -> Iterable:

def check_deprecated_module(self, node, mod_path):
"""Checks if the module is deprecated"""

for mod_name in self.deprecated_modules():
if mod_path == mod_name or mod_path.startswith(mod_name + "."):
self.add_message("deprecated-module", node=node, args=mod_path)
Expand Down
21 changes: 2 additions & 19 deletions pylint/checkers/imports.py
Expand Up @@ -56,6 +56,7 @@
from pylint.checkers import BaseChecker, DeprecatedMixin
from pylint.checkers.utils import (
check_messages,
get_import_name,
is_from_fallback_block,
node_ignores_exception,
)
Expand All @@ -78,24 +79,6 @@ def _qualified_names(modname):
return [".".join(names[0 : i + 1]) for i in range(len(names))]


def _get_import_name(importnode, modname):
"""Get a prepared module name from the given import node
In the case of relative imports, this will return the
absolute qualified module name, which might be useful
for debugging. Otherwise, the initial module name
is returned unchanged.
"""
if isinstance(importnode, astroid.ImportFrom):
if importnode.level:
root = importnode.root()
if isinstance(root, astroid.Module):
modname = root.relative_to_absolute_name(
modname, level=importnode.level
)
return modname


def _get_first_import(node, context, name, base, level, alias):
"""return the node where [base.]<name> is imported or None if not found"""
fullname = f"{base}.{name}" if base else name
Expand Down Expand Up @@ -826,7 +809,7 @@ def _get_imported_module(self, importnode, modname):
):
return None

dotted_modname = _get_import_name(importnode, modname)
dotted_modname = get_import_name(importnode, modname)
self.add_message("import-error", args=repr(dotted_modname), node=importnode)
return None

Expand Down
25 changes: 25 additions & 0 deletions pylint/checkers/utils.py
Expand Up @@ -1510,3 +1510,28 @@ def get_subscript_const_value(node: astroid.Subscript) -> astroid.Const:
)

return inferred


def get_import_name(
importnode: Union[astroid.Import, astroid.ImportFrom], modname: str
) -> str:
"""Get a prepared module name from the given import node
In the case of relative imports, this will return the
absolute qualified module name, which might be useful
for debugging. Otherwise, the initial module name
is returned unchanged.
:param importnode: node representing import statement.
:param modname: module name from import statement.
:returns: absolute qualified module name of the module
used in import.
"""
if isinstance(importnode, astroid.ImportFrom):
if importnode.level:
root = importnode.root()
if isinstance(root, astroid.Module):
modname = root.relative_to_absolute_name(
modname, level=importnode.level
)
return modname
2 changes: 1 addition & 1 deletion tests/checkers/unittest_deprecated.py
Expand Up @@ -452,7 +452,7 @@ def test_deprecated_class_import_from(self):
# Tests detecting deprecated class via import from
node = astroid.extract_node(
"""
from .deprecated import DeprecatedClass
from deprecated import DeprecatedClass
"""
)
with self.assertAddsMessages(
Expand Down
Empty file.
@@ -0,0 +1,7 @@
# pylint: disable=import-error, missing-module-docstring, unused-import

# from import of stdlib optparse which should yield deprecated-module error
from optparse import OptionParser # [deprecated-module]
# from import of module internal optparse module inside this package.
# This should not yield deprecated-module error
from .optparse import Bar
@@ -0,0 +1,2 @@
[testoptions]
min_pyver=3.2
@@ -0,0 +1 @@
deprecated-module:4:0::Uses of a deprecated module 'optparse':HIGH
Empty file.
@@ -0,0 +1,7 @@
# pylint: disable=import-error, unused-import, missing-module-docstring

# from import of stdlib optparse which should yield deprecated-module error
from optparse import OptionParser # [deprecated-module]
# from import of module internal optparse module inside this package.
# This should not yield deprecated-module error
from ..optparse import Bar
@@ -0,0 +1,2 @@
[testoptions]
min_pyver=3.2
@@ -0,0 +1 @@
deprecated-module:4:0::Uses of a deprecated module 'optparse':HIGH

0 comments on commit 0f8212f

Please sign in to comment.