Added a new error, 'relative-beyond-top-level'.

This is emitted when a relative import was attempted beyond the top level package. For instance, if a package has X levels, trying to climb X + n levels with a relative import, as in from ..stuff import Stuff, will result in an error. Closes issue #588.

authorClaudiu Popa <pcmanticore@gmail.com>
changeset4e5a1be18df2
branchdefault
phasepublic
hiddenno
parent revision#ffe24a7d5a37 Make pylint work with new astroid exceptions, AstroidImportError and AstroidSyntaxError.
child revision#83afd8d7a075 Move the construction of generated_members into open.
files modified by this revision
ChangeLog
pylint/checkers/imports.py
pylint/test/regrtest_data/beyond_top/__init__.py
pylint/test/regrtest_data/beyond_top/data.py
pylint/test/unittest_checker_imports.py
# HG changeset patch
# User Claudiu Popa <pcmanticore@gmail.com>
# Date 1449420094 -7200
# Sun Dec 06 18:41:34 2015 +0200
# Node ID 4e5a1be18df2799d29847ed51dd4b86c2903c2ea
# Parent ffe24a7d5a373f348c423dfd77f392d785605f16
Added a new error, 'relative-beyond-top-level'.

This is emitted when a relative import was attempted beyond the top level package.
For instance, if a package has X levels, trying to climb X + n levels with a relative
import, as in `from ..stuff import Stuff`, will result in an error.
Closes issue #588.

diff --git a/ChangeLog b/ChangeLog
@@ -1,13 +1,19 @@
1  ChangeLog for Pylint
2  --------------------
3 
4  --
5 -     * Accept only functions and methods for the deprecated-method checker.
6 -
7 -      This prevents a crash which can occur when an object doesn't have
8 -      .qname() method after the inference.
9 +    
10 +    * Added a new error, 'relative-beyond-top-level', which is emitted
11 +      when a relative import was attempted beyond the top level package.
12 +
13 +      Closes issue #588.
14 +
15 +    * Accept only functions and methods for the deprecated-method checker.
16 +
17 +     This prevents a crash which can occur when an object doesn't have
18 +     .qname() method after the inference.
19 
20      * Don't emit super-on-old-class on classes with unknown bases.
21        Closes issue #721.
22 
23      * Added a new warning, 'unsupported-assignment-operation', which is
diff --git a/pylint/checkers/imports.py b/pylint/checkers/imports.py
@@ -86,10 +86,18 @@
24                  found = True
25                  break
26      if found and not are_exclusive(first, node):
27          return first
28 
29 +
30 +def _ignore_import_failure(node, modname, ignored_modules):
31 +    for submodule in _qualified_names(modname):
32 +        if submodule in ignored_modules:
33 +            return True
34 +
35 +    return node_ignores_exception(node, ImportError)
36 +
37  # utilities to represents import dependencies as tree and dot graph ###########
38 
39  def _make_tree_defs(mod_files_list):
40      """get a list of 2-uple (module, list_of_files_which_import_this_module),
41      it will return a dictionary to represent this as a tree
@@ -159,10 +167,14 @@
42  MSGS = {
43      'E0401': ('Unable to import %s',
44                'import-error',
45                'Used when pylint has been unable to import a module.',
46                {'old_names': [('F0401', 'import-error')]}),
47 +    'E0402': ('Attempted relative import beyond top-level package',
48 +              'relative-beyond-top-level',
49 +              'Used when a relative import tries to access too many levels '
50 +              'in the current package.'),
51      'R0401': ('Cyclic import (%s)',
52                'cyclic-import',
53                'Used when a cyclic import between two or more modules is \
54                detected.'),
55 
@@ -459,20 +471,22 @@
56          return std_imports, extern_imports, local_imports
57 
58      def _get_imported_module(self, importnode, modname):
59          try:
60              return importnode.do_import_module(modname)
61 +        except astroid.TooManyLevelsError:
62 +            if _ignore_import_failure(importnode, modname, self._ignored_modules):
63 +                return None
64 +
65 +            self.add_message('relative-beyond-top-level', node=importnode)            
66 +
67          except astroid.AstroidBuildingException as exc:
68 -            for submodule in _qualified_names(modname):
69 -                if submodule in self._ignored_modules:
70 -                    return None
71 -
72 -            if node_ignores_exception(importnode, ImportError):
73 +            if _ignore_import_failure(importnode, modname, self._ignored_modules):
74                  return None
75 
76              dotted_modname = _get_import_name(importnode, modname)
77 -            self.add_message("import-error", args=repr(dotted_modname),
78 +            self.add_message('import-error', args=repr(dotted_modname),
79                               node=importnode)
80 
81      def _check_relative_import(self, modnode, importnode, importedmodnode,
82                                 importedasname):
83          """check relative import. node is either an Import or From node, modname
diff --git a/pylint/test/regrtest_data/beyond_top/__init__.py b/pylint/test/regrtest_data/beyond_top/__init__.py
@@ -0,0 +1,6 @@
84 +from ... import Something

85 +from . import data

86 +try:

87 +    from ... import Lala

88 +except ImportError:

89 +    pass
90 \ No newline at end of file
diff --git a/pylint/test/regrtest_data/beyond_top/data.py b/pylint/test/regrtest_data/beyond_top/data.py
@@ -0,0 +1,1 @@
91 +Anything = 42
92 \ No newline at end of file
diff --git a/pylint/test/unittest_checker_imports.py b/pylint/test/unittest_checker_imports.py
@@ -1,8 +1,10 @@
93  """Unit tests for the imports checker."""
94 +import os
95  import unittest
96 
97 +import astroid
98  from astroid import test_utils
99  from pylint.checkers import imports
100  from pylint.testutils import CheckerTestCase, Message, set_config
101 
102 
@@ -60,16 +62,34 @@
103          import bar
104          """)
105          with self.assertNoMessages():
106              self.checker.visit_import(node)
107 
108 -    def test_visit_importfrom(self):
109 +    def test_reimported_same_line(self):
110          """
111          Test that duplicate imports on single line raise 'reimported'.
112          """
113          node = test_utils.extract_node('from time import sleep, sleep, time')
114          msg = Message(msg_id='reimported', node=node, args=('sleep', 1))
115          with self.assertAddsMessages(msg):
116              self.checker.visit_importfrom(node)
117 
118 +    def test_relative_beyond_top_level(self):
119 +        here = os.path.abspath(os.path.dirname(__file__))
120 +        path = os.path.join(here, 'regrtest_data', 'beyond_top', '__init__.py')
121 +        with open(path) as stream:
122 +            data = stream.read()
123 +        module = astroid.parse(data, module_name='beyond_top', path=path)
124 +        import_from = module.body[0]
125 +
126 +        msg = Message(msg_id='relative-beyond-top-level',
127 +                      node=import_from)
128 +        with self.assertAddsMessages(msg):
129 +            self.checker.visit_importfrom(import_from)    
130 +        with self.assertNoMessages():
131 +            self.checker.visit_importfrom(module.body[1])
132 +        with self.assertNoMessages():
133 +            self.checker.visit_importfrom(module.body[2].body[0])
134 +
135 +
136  if __name__ == '__main__':
137      unittest.main()