Don't emit import-self and cyclic-import for relative imports of modules with the same name as the package itself.

The problem was partially the fault of astroid.modutils.get_module_part, in combination with a given context file. The function returned 'dummy' as the module part for the string dummy.dummy.Dummy, which is in fact true, since the first dummy is the package and the second dummy is the module from where Dummy gets loaded. But get_module_part has no way to know this semantic inference, that the second dummy is a relative import inside the first one. As such, it's better to just skip the check if the condition of being relative inside a __init__.py file is found, since there's no way to load itself in that case.

Closes issues #708 and #706.

authorClaudiu Popa <pcmanticore@gmail.com>
changesete7040ed411e1
branchdefault
phasepublic
hiddenno
parent revision#fd86093b48c1 Refactor things through the imports checker
child revision#2547bf6bf2e5 Prepare a small bug fix release.
files modified by this revision
ChangeLog
pylint/checkers/imports.py
pylint/test/regrtest_data/dummy/__init__.py
pylint/test/regrtest_data/dummy/another.py
pylint/test/regrtest_data/dummy/dummy.py
pylint/test/test_self.py
# HG changeset patch
# User Claudiu Popa <pcmanticore@gmail.com>
# Date 1448974739 -7200
# Tue Dec 01 14:58:59 2015 +0200
# Node ID e7040ed411e115ed99d1c693549b003215be0407
# Parent fd86093b48c1d0e71aab9536dd39957895988f23
Don't emit import-self and cyclic-import for relative imports of modules with the same name as the package itself.

The problem was partially the fault of astroid.modutils.get_module_part,
in combination with a given context file.
The function returned 'dummy' as the module part for the string
`dummy.dummy.Dummy`, which is in fact true, since the first dummy
is the package and the second dummy is the module from where Dummy
gets loaded. But get_module_part has no way to know this semantic
inference, that the second dummy is a relative import inside the
first one. As such, it's better to just skip the check if the
condition of being relative inside a __init__.py file is found,
since there's no way to load itself in that case.

Closes issues #708 and #706.

diff --git a/ChangeLog b/ChangeLog
@@ -16,10 +16,14 @@
1        leading to wrong-import-position being emitted by pylint.
2 
3      * Fix a crash which occurred when old visit methods are encountered
4        in plugin modules. Closes issue #711.
5 
6 +    * Don't emit import-self and cyclic-import for relative imports
7 +      of modules with the same name as the package itself.
8 +      Closes issues #708 and #706.
9 +
10 
11  2015-11-29 -- 1.5.0
12 
13      * Added multiple warnings related to imports. 'wrong-import-order'
14        is emitted when PEP 8 recommendations regarding imports are not
diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py
@@ -494,18 +494,30 @@
15                               args=(importedasname, importedmodnode.name),
16                               node=importnode)
17 
18      def _add_imported_module(self, node, importedmodname):
19          """notify an imported module, used to analyze dependencies"""
20 +        module_file = node.root().file
21 +        context_name = node.root().name
22 +        base = os.path.splitext(os.path.basename(module_file))[0]
23 +
24 +        # Determine if we have a `from .something import` in a package's
25 +        # __init__. This means the module will never be able to import
26 +        # itself using this condition (the level will be bigger or
27 +        # if the same module is named as the package, it will be different
28 +        # anyway).
29 +        if isinstance(node, astroid.ImportFrom):
30 +            if node.level and node.level > 0 and base == '__init__':
31 +                return
32 +
33          try:
34              importedmodname = get_module_part(importedmodname,
35 -                                              node.root().file)
36 +                                              module_file)
37          except ImportError:
38              pass
39 -        context_name = node.root().name
40 +
41          if context_name == importedmodname:
42 -            # module importing itself !
43              self.add_message('import-self', node=node)
44          elif not is_standard_module(importedmodname):
45              # handle dependencies
46              importedmodnames = self.stats['dependencies'].setdefault(
47                  importedmodname, set())
diff --git a/pylint/test/regrtest_data/dummy/__init__.py b/pylint/test/regrtest_data/dummy/__init__.py
@@ -0,0 +1,3 @@
48 +# pylint: disable=missing-docstring
49 +from .dummy import DUMMY
50 +from .another import ANOTHER
diff --git a/pylint/test/regrtest_data/dummy/another.py b/pylint/test/regrtest_data/dummy/another.py
@@ -0,0 +1,4 @@
51 +# pylint: disable=missing-docstring
52 +
53 +ANOTHER = 42
54 +
diff --git a/pylint/test/regrtest_data/dummy/dummy.py b/pylint/test/regrtest_data/dummy/dummy.py
@@ -0,0 +1,2 @@
55 +# pylint: disable=missing-docstring
56 +DUMMY = 42
diff --git a/pylint/test/test_self.py b/pylint/test/test_self.py
@@ -286,10 +286,15 @@
57          out = six.StringIO()
58          self._run_pylint(args, out=out)
59          actual_output = out.getvalue()
60          self.assertEqual(expected_output.strip(), actual_output.strip())
61 
62 -                      
63 +    def test_import_itself_not_accounted_for_relative_imports(self):
64 +        expected = 'No config file found, using default configuration'
65 +        package = join(HERE, 'regrtest_data', 'dummy')
66 +        self._test_output([package, '--disable=locally-disabled', '-rn'],
67 +                          expected_output=expected)
68 +                           
69 
70 
71  if __name__ == '__main__':
72      unittest.main()