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