gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / pylint / checkers / imports.py @ 745
History | View | Annotate | Download (25.7 KB)
1 |
# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
|
---|---|
2 |
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
3 |
#
|
4 |
# This program is free software; you can redistribute it and/or modify it under
|
5 |
# the terms of the GNU General Public License as published by the Free Software
|
6 |
# Foundation; either version 2 of the License, or (at your option) any later
|
7 |
# version.
|
8 |
#
|
9 |
# This program is distributed in the hope that it will be useful, but WITHOUT
|
10 |
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
11 |
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
12 |
#
|
13 |
# You should have received a copy of the GNU General Public License along with
|
14 |
# this program; if not, write to the Free Software Foundation, Inc.,
|
15 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
16 |
"""imports checkers for Python code"""
|
17 |
|
18 |
import collections |
19 |
from distutils import sysconfig |
20 |
import os |
21 |
import sys |
22 |
|
23 |
import six |
24 |
|
25 |
import astroid |
26 |
from astroid import are_exclusive |
27 |
from astroid.modutils import (get_module_part, is_standard_module, |
28 |
file_from_modpath) |
29 |
|
30 |
from pylint.interfaces import IAstroidChecker |
31 |
from pylint.utils import EmptyReport, get_global_option |
32 |
from pylint.checkers import BaseChecker |
33 |
from pylint.checkers.utils import check_messages, node_ignores_exception |
34 |
from pylint.graph import get_cycles, DotBackend |
35 |
from pylint.reporters.ureports.nodes import VerbatimText, Paragraph |
36 |
|
37 |
|
38 |
def _qualified_names(modname): |
39 |
"""Split the names of the given module into subparts
|
40 |
|
41 |
For example,
|
42 |
_qualified_names('pylint.checkers.ImportsChecker')
|
43 |
returns
|
44 |
['pylint', 'pylint.checkers', 'pylint.checkers.ImportsChecker']
|
45 |
"""
|
46 |
names = modname.split('.')
|
47 |
return ['.'.join(names[0:i+1]) for i in range(len(names))] |
48 |
|
49 |
|
50 |
def _get_import_name(importnode, modname): |
51 |
"""Get a prepared module name from the given import node
|
52 |
|
53 |
In the case of relative imports, this will return the
|
54 |
absolute qualified module name, which might be useful
|
55 |
for debugging. Otherwise, the initial module name
|
56 |
is returned unchanged.
|
57 |
"""
|
58 |
if isinstance(importnode, astroid.ImportFrom): |
59 |
if importnode.level:
|
60 |
root = importnode.root() |
61 |
if isinstance(root, astroid.Module): |
62 |
modname = root.relative_to_absolute_name( |
63 |
modname, level=importnode.level) |
64 |
return modname
|
65 |
|
66 |
|
67 |
def _get_first_import(node, context, name, base, level): |
68 |
"""return the node where [base.]<name> is imported or None if not found
|
69 |
"""
|
70 |
fullname = '%s.%s' % (base, name) if base else name |
71 |
|
72 |
first = None
|
73 |
found = False
|
74 |
for first in context.body: |
75 |
if first is node: |
76 |
continue
|
77 |
if first.scope() is node.scope() and first.fromlineno > node.fromlineno: |
78 |
continue
|
79 |
if isinstance(first, astroid.Import): |
80 |
if any(fullname == iname[0] for iname in first.names): |
81 |
found = True
|
82 |
break
|
83 |
elif isinstance(first, astroid.ImportFrom): |
84 |
if level == first.level and any( |
85 |
fullname == '%s.%s' % (first.modname, iname[0]) |
86 |
for iname in first.names): |
87 |
found = True
|
88 |
break
|
89 |
if found and not are_exclusive(first, node): |
90 |
return first
|
91 |
|
92 |
# utilities to represents import dependencies as tree and dot graph ###########
|
93 |
|
94 |
def _make_tree_defs(mod_files_list): |
95 |
"""get a list of 2-uple (module, list_of_files_which_import_this_module),
|
96 |
it will return a dictionary to represent this as a tree
|
97 |
"""
|
98 |
tree_defs = {} |
99 |
for mod, files in mod_files_list: |
100 |
node = (tree_defs, ()) |
101 |
for prefix in mod.split('.'): |
102 |
node = node[0].setdefault(prefix, [{}, []])
|
103 |
node[1] += files
|
104 |
return tree_defs
|
105 |
|
106 |
|
107 |
def _repr_tree_defs(data, indent_str=None): |
108 |
"""return a string which represents imports as a tree"""
|
109 |
lines = [] |
110 |
nodes = data.items() |
111 |
for i, (mod, (sub, files)) in enumerate(sorted(nodes, key=lambda x: x[0])): |
112 |
if not files: |
113 |
files = ''
|
114 |
else:
|
115 |
files = '(%s)' % ','.join(files) |
116 |
if indent_str is None: |
117 |
lines.append('%s %s' % (mod, files))
|
118 |
sub_indent_str = ' '
|
119 |
else:
|
120 |
lines.append(r'%s\-%s %s' % (indent_str, mod, files))
|
121 |
if i == len(nodes)-1: |
122 |
sub_indent_str = '%s ' % indent_str
|
123 |
else:
|
124 |
sub_indent_str = '%s| ' % indent_str
|
125 |
if sub:
|
126 |
lines.append(_repr_tree_defs(sub, sub_indent_str)) |
127 |
return '\n'.join(lines) |
128 |
|
129 |
|
130 |
def _dependencies_graph(filename, dep_info): |
131 |
"""write dependencies as a dot (graphviz) file
|
132 |
"""
|
133 |
done = {} |
134 |
printer = DotBackend(filename[:-4], rankdir='LR') |
135 |
printer.emit('URL="." node[shape="box"]')
|
136 |
for modname, dependencies in sorted(six.iteritems(dep_info)): |
137 |
done[modname] = 1
|
138 |
printer.emit_node(modname) |
139 |
for modname in dependencies: |
140 |
if modname not in done: |
141 |
done[modname] = 1
|
142 |
printer.emit_node(modname) |
143 |
for depmodname, dependencies in sorted(six.iteritems(dep_info)): |
144 |
for modname in dependencies: |
145 |
printer.emit_edge(modname, depmodname) |
146 |
printer.generate(filename) |
147 |
|
148 |
|
149 |
def _make_graph(filename, dep_info, sect, gtype): |
150 |
"""generate a dependencies graph and add some information about it in the
|
151 |
report's section
|
152 |
"""
|
153 |
_dependencies_graph(filename, dep_info) |
154 |
sect.append(Paragraph('%simports graph has been written to %s'
|
155 |
% (gtype, filename))) |
156 |
|
157 |
|
158 |
# the import checker itself ###################################################
|
159 |
|
160 |
MSGS = { |
161 |
'E0401': ('Unable to import %s', |
162 |
'import-error',
|
163 |
'Used when pylint has been unable to import a module.',
|
164 |
{'old_names': [('F0401', 'import-error')]}), |
165 |
'R0401': ('Cyclic import (%s)', |
166 |
'cyclic-import',
|
167 |
'Used when a cyclic import between two or more modules is \
|
168 |
detected.'),
|
169 |
|
170 |
'W0401': ('Wildcard import %s', |
171 |
'wildcard-import',
|
172 |
'Used when `from module import *` is detected.'),
|
173 |
'W0402': ('Uses of a deprecated module %r', |
174 |
'deprecated-module',
|
175 |
'Used a module marked as deprecated is imported.'),
|
176 |
'W0403': ('Relative import %r, should be %r', |
177 |
'relative-import',
|
178 |
'Used when an import relative to the package directory is '
|
179 |
'detected.',
|
180 |
{'maxversion': (3, 0)}), |
181 |
'W0404': ('Reimport %r (imported line %s)', |
182 |
'reimported',
|
183 |
'Used when a module is reimported multiple times.'),
|
184 |
'W0406': ('Module import itself', |
185 |
'import-self',
|
186 |
'Used when a module is importing itself.'),
|
187 |
|
188 |
'W0410': ('__future__ import is not the first non docstring statement', |
189 |
'misplaced-future',
|
190 |
'Python 2.5 and greater require __future__ import to be the \
|
191 |
first non docstring statement in the module.'),
|
192 |
|
193 |
'C0410': ('Multiple imports on one line (%s)', |
194 |
'multiple-imports',
|
195 |
'Used when import statement importing multiple modules is '
|
196 |
'detected.'),
|
197 |
'C0411': ('%s comes before %s', |
198 |
'wrong-import-order',
|
199 |
'Used when PEP8 import order is not respected (standard imports '
|
200 |
'first, then third-party libraries, then local imports)'),
|
201 |
'C0412': ('Imports from package %s are not grouped', |
202 |
'ungrouped-imports',
|
203 |
'Used when imports are not grouped by packages'),
|
204 |
'C0413': ('Import "%s" should be placed at the top of the ' |
205 |
'module',
|
206 |
'wrong-import-position',
|
207 |
'Used when code and imports are mixed'),
|
208 |
} |
209 |
|
210 |
class ImportsChecker(BaseChecker): |
211 |
"""checks for
|
212 |
* external modules dependencies
|
213 |
* relative / wildcard imports
|
214 |
* cyclic imports
|
215 |
* uses of deprecated modules
|
216 |
"""
|
217 |
|
218 |
__implements__ = IAstroidChecker |
219 |
|
220 |
name = 'imports'
|
221 |
msgs = MSGS |
222 |
priority = -2
|
223 |
|
224 |
if six.PY2:
|
225 |
deprecated_modules = ('regsub', 'TERMIOS', 'Bastion', 'rexec') |
226 |
else:
|
227 |
deprecated_modules = ('optparse', )
|
228 |
options = (('deprecated-modules',
|
229 |
{'default' : deprecated_modules,
|
230 |
'type' : 'csv', |
231 |
'metavar' : '<modules>', |
232 |
'help' : 'Deprecated modules which should not be used, \ |
233 |
separated by a comma'}
|
234 |
), |
235 |
('import-graph',
|
236 |
{'default' : '', |
237 |
'type' : 'string', |
238 |
'metavar' : '<file.dot>', |
239 |
'help' : 'Create a graph of every (i.e. internal and \ |
240 |
external) dependencies in the given file (report RP0402 must not be disabled)'}
|
241 |
), |
242 |
('ext-import-graph',
|
243 |
{'default' : '', |
244 |
'type' : 'string', |
245 |
'metavar' : '<file.dot>', |
246 |
'help' : 'Create a graph of external dependencies in the \ |
247 |
given file (report RP0402 must not be disabled)'}
|
248 |
), |
249 |
('int-import-graph',
|
250 |
{'default' : '', |
251 |
'type' : 'string', |
252 |
'metavar' : '<file.dot>', |
253 |
'help' : 'Create a graph of internal dependencies in the \ |
254 |
given file (report RP0402 must not be disabled)'}
|
255 |
), |
256 |
) |
257 |
|
258 |
def __init__(self, linter=None): |
259 |
BaseChecker.__init__(self, linter)
|
260 |
self.stats = None |
261 |
self.import_graph = None |
262 |
self._imports_stack = []
|
263 |
self._first_non_import_node = None |
264 |
self.__int_dep_info = self.__ext_dep_info = None |
265 |
self.reports = (('RP0401', 'External dependencies', |
266 |
self._report_external_dependencies),
|
267 |
('RP0402', 'Modules dependencies graph', |
268 |
self._report_dependencies_graph),
|
269 |
) |
270 |
|
271 |
self._site_packages = self._compute_site_packages() |
272 |
|
273 |
@staticmethod
|
274 |
def _compute_site_packages(): |
275 |
def _normalized_path(path): |
276 |
return os.path.normcase(os.path.abspath(path))
|
277 |
|
278 |
paths = set()
|
279 |
real_prefix = getattr(sys, 'real_prefix', None) |
280 |
for prefix in filter(None, (real_prefix, sys.prefix)): |
281 |
path = sysconfig.get_python_lib(prefix=prefix) |
282 |
path = _normalized_path(path) |
283 |
paths.add(path) |
284 |
|
285 |
# Handle Debian's derivatives /usr/local.
|
286 |
if os.path.isfile("/etc/debian_version"): |
287 |
for prefix in filter(None, (real_prefix, sys.prefix)): |
288 |
libpython = os.path.join(prefix, "local", "lib", |
289 |
"python" + sysconfig.get_python_version(),
|
290 |
"dist-packages")
|
291 |
paths.add(libpython) |
292 |
return paths
|
293 |
|
294 |
def open(self): |
295 |
"""called before visiting project (i.e set of modules)"""
|
296 |
self.linter.add_stats(dependencies={})
|
297 |
self.linter.add_stats(cycles=[])
|
298 |
self.stats = self.linter.stats |
299 |
self.import_graph = collections.defaultdict(set) |
300 |
self._ignored_modules = get_global_option(
|
301 |
self, 'ignored-modules', default=[]) |
302 |
|
303 |
def close(self): |
304 |
"""called before visiting project (i.e set of modules)"""
|
305 |
# don't try to compute cycles if the associated message is disabled
|
306 |
if self.linter.is_message_enabled('cyclic-import'): |
307 |
vertices = list(self.import_graph) |
308 |
for cycle in get_cycles(self.import_graph, vertices=vertices): |
309 |
self.add_message('cyclic-import', args=' -> '.join(cycle)) |
310 |
|
311 |
@check_messages('wrong-import-position', 'multiple-imports', |
312 |
'relative-import', 'reimported') |
313 |
def visit_import(self, node): |
314 |
"""triggered when an import statement is seen"""
|
315 |
self._check_reimport(node)
|
316 |
|
317 |
modnode = node.root() |
318 |
names = [name for name, _ in node.names] |
319 |
if len(names) >= 2: |
320 |
self.add_message('multiple-imports', args=', '.join(names), node=node) |
321 |
|
322 |
for name in names: |
323 |
self._check_deprecated_module(node, name)
|
324 |
importedmodnode = self.get_imported_module(node, name)
|
325 |
if isinstance(node.scope(), astroid.Module): |
326 |
self._check_position(node)
|
327 |
self._record_import(node, importedmodnode)
|
328 |
|
329 |
if importedmodnode is None: |
330 |
continue
|
331 |
|
332 |
self._check_relative_import(modnode, node, importedmodnode, name)
|
333 |
self._add_imported_module(node, importedmodnode.name)
|
334 |
|
335 |
@check_messages(*(MSGS.keys()))
|
336 |
def visit_importfrom(self, node): |
337 |
"""triggered when a from statement is seen"""
|
338 |
basename = node.modname |
339 |
self._check_misplaced_future(node)
|
340 |
self._check_deprecated_module(node, basename)
|
341 |
self._check_wildcard_imports(node)
|
342 |
self._check_same_line_imports(node)
|
343 |
self._check_reimport(node, basename=basename, level=node.level)
|
344 |
|
345 |
modnode = node.root() |
346 |
importedmodnode = self.get_imported_module(node, basename)
|
347 |
if isinstance(node.scope(), astroid.Module): |
348 |
self._check_position(node)
|
349 |
self._record_import(node, importedmodnode)
|
350 |
if importedmodnode is None: |
351 |
return
|
352 |
self._check_relative_import(modnode, node, importedmodnode, basename)
|
353 |
|
354 |
for name, _ in node.names: |
355 |
if name != '*': |
356 |
self._add_imported_module(node, '%s.%s' % (importedmodnode.name, name)) |
357 |
|
358 |
@check_messages('wrong-import-order', 'ungrouped-imports', |
359 |
'wrong-import-position')
|
360 |
def leave_module(self, node): |
361 |
# Check imports are grouped by category (standard, 3rd party, local)
|
362 |
std_imports, ext_imports, loc_imports = self._check_imports_order(node)
|
363 |
|
364 |
# Check imports are grouped by package within a given category
|
365 |
met = set()
|
366 |
current_package = None
|
367 |
for import_node, import_name in std_imports + ext_imports + loc_imports: |
368 |
package, _, _ = import_name.partition('.')
|
369 |
if current_package and current_package != package and package in met: |
370 |
self.add_message('ungrouped-imports', node=import_node, |
371 |
args=package) |
372 |
current_package = package |
373 |
met.add(package) |
374 |
|
375 |
self._imports_stack = []
|
376 |
self._first_non_import_node = None |
377 |
|
378 |
def visit_if(self, node): |
379 |
# if the node does not contain an import instruction, and if it is the
|
380 |
# first node of the module, keep a track of it (all the import positions
|
381 |
# of the module will be compared to the position of this first
|
382 |
# instruction)
|
383 |
if self._first_non_import_node: |
384 |
return
|
385 |
if not isinstance(node.parent, astroid.Module): |
386 |
return
|
387 |
if any(node.nodes_of_class((astroid.Import, astroid.ImportFrom))): |
388 |
return
|
389 |
self._first_non_import_node = node
|
390 |
|
391 |
visit_tryfinally = visit_tryexcept = visit_assignattr = visit_assign \ |
392 |
= visit_ifexp = visit_comprehension = visit_if |
393 |
|
394 |
def visit_functiondef(self, node): |
395 |
# If it is the first non import instruction of the module, record it.
|
396 |
if self._first_non_import_node: |
397 |
return
|
398 |
|
399 |
# Check if the node belongs to an `If` or a `Try` block. If they
|
400 |
# contain imports, skip recording this node.
|
401 |
if not isinstance(node.parent.scope(), astroid.Module): |
402 |
return
|
403 |
|
404 |
root = node |
405 |
while not isinstance(root.parent, astroid.Module): |
406 |
root = root.parent |
407 |
|
408 |
if isinstance(root, (astroid.If, astroid.TryFinally, astroid.TryExcept)): |
409 |
if any(root.nodes_of_class((astroid.Import, astroid.ImportFrom))): |
410 |
return
|
411 |
|
412 |
self._first_non_import_node = node
|
413 |
|
414 |
visit_classdef = visit_for = visit_while = visit_functiondef |
415 |
|
416 |
def _check_misplaced_future(self, node): |
417 |
basename = node.modname |
418 |
if basename == '__future__': |
419 |
# check if this is the first non-docstring statement in the module
|
420 |
prev = node.previous_sibling() |
421 |
if prev:
|
422 |
# consecutive future statements are possible
|
423 |
if not (isinstance(prev, astroid.ImportFrom) |
424 |
and prev.modname == '__future__'): |
425 |
self.add_message('misplaced-future', node=node) |
426 |
return
|
427 |
|
428 |
def _check_same_line_imports(self, node): |
429 |
# Detect duplicate imports on the same line.
|
430 |
names = (name for name, _ in node.names) |
431 |
counter = collections.Counter(names) |
432 |
for name, count in counter.items(): |
433 |
if count > 1: |
434 |
self.add_message('reimported', node=node, |
435 |
args=(name, node.fromlineno)) |
436 |
|
437 |
def _check_position(self, node): |
438 |
"""Check `node` import or importfrom node position is correct
|
439 |
|
440 |
Send a message if `node` comes before another instruction
|
441 |
"""
|
442 |
# if a first non-import instruction has already been encountered,
|
443 |
# it means the import comes after it and therefore is not well placed
|
444 |
if self._first_non_import_node: |
445 |
self.add_message('wrong-import-position', node=node, |
446 |
args=node.as_string()) |
447 |
|
448 |
def _record_import(self, node, importedmodnode): |
449 |
"""Record the package `node` imports from"""
|
450 |
importedname = importedmodnode.name if importedmodnode else None |
451 |
if not importedname: |
452 |
importedname = node.names[0][0].split('.')[0] |
453 |
self._imports_stack.append((node, importedname))
|
454 |
|
455 |
@staticmethod
|
456 |
def _is_fallback_import(node, imports): |
457 |
imports = [import_node for (import_node, _) in imports] |
458 |
return any(astroid.are_exclusive(import_node, node) |
459 |
for import_node in imports) |
460 |
|
461 |
def _check_imports_order(self, node): |
462 |
"""Checks imports of module `node` are grouped by category
|
463 |
|
464 |
Imports must follow this order: standard, 3rd party, local
|
465 |
"""
|
466 |
extern_imports = [] |
467 |
local_imports = [] |
468 |
std_imports = [] |
469 |
for node, modname in self._imports_stack: |
470 |
package = modname.split('.')[0] |
471 |
if is_standard_module(modname):
|
472 |
std_imports.append((node, package)) |
473 |
wrong_import = extern_imports or local_imports
|
474 |
if not wrong_import: |
475 |
continue
|
476 |
if self._is_fallback_import(node, wrong_import): |
477 |
continue
|
478 |
self.add_message('wrong-import-order', node=node, |
479 |
args=('standard import "%s"' % node.as_string(),
|
480 |
'"%s"' % wrong_import[0][0].as_string())) |
481 |
else:
|
482 |
try:
|
483 |
filename = file_from_modpath([package]) |
484 |
except ImportError: |
485 |
continue
|
486 |
if not filename: |
487 |
continue
|
488 |
|
489 |
filename = os.path.normcase(os.path.abspath(filename)) |
490 |
if not any(filename.startswith(path) for path in self._site_packages): |
491 |
local_imports.append((node, package)) |
492 |
continue
|
493 |
extern_imports.append((node, package)) |
494 |
if not local_imports: |
495 |
continue
|
496 |
self.add_message('wrong-import-order', node=node, |
497 |
args=('external import "%s"' % node.as_string(),
|
498 |
'"%s"' % local_imports[0][0].as_string())) |
499 |
return std_imports, extern_imports, local_imports
|
500 |
|
501 |
def get_imported_module(self, importnode, modname): |
502 |
try:
|
503 |
return importnode.do_import_module(modname)
|
504 |
except astroid.InferenceError as ex: |
505 |
dotted_modname = _get_import_name(importnode, modname) |
506 |
if str(ex) != modname: |
507 |
args = '%r (%s)' % (dotted_modname, ex)
|
508 |
else:
|
509 |
args = repr(dotted_modname)
|
510 |
|
511 |
for submodule in _qualified_names(modname): |
512 |
if submodule in self._ignored_modules: |
513 |
return None |
514 |
|
515 |
if not node_ignores_exception(importnode, ImportError): |
516 |
self.add_message("import-error", args=args, node=importnode) |
517 |
|
518 |
def _check_relative_import(self, modnode, importnode, importedmodnode, |
519 |
importedasname): |
520 |
"""check relative import. node is either an Import or From node, modname
|
521 |
the imported module name.
|
522 |
"""
|
523 |
if not self.linter.is_message_enabled('relative-import'): |
524 |
return
|
525 |
if importedmodnode.file is None: |
526 |
return False # built-in module |
527 |
if modnode is importedmodnode: |
528 |
return False # module importing itself |
529 |
if modnode.absolute_import_activated() or getattr(importnode, 'level', None): |
530 |
return False |
531 |
if importedmodnode.name != importedasname:
|
532 |
# this must be a relative import...
|
533 |
self.add_message('relative-import', |
534 |
args=(importedasname, importedmodnode.name), |
535 |
node=importnode) |
536 |
|
537 |
def _add_imported_module(self, node, importedmodname): |
538 |
"""notify an imported module, used to analyze dependencies"""
|
539 |
module_file = node.root().file |
540 |
context_name = node.root().name |
541 |
base = os.path.splitext(os.path.basename(module_file))[0]
|
542 |
|
543 |
# Determine if we have a `from .something import` in a package's
|
544 |
# __init__. This means the module will never be able to import
|
545 |
# itself using this condition (the level will be bigger or
|
546 |
# if the same module is named as the package, it will be different
|
547 |
# anyway).
|
548 |
if isinstance(node, astroid.ImportFrom): |
549 |
if node.level and node.level > 0 and base == '__init__': |
550 |
return
|
551 |
|
552 |
try:
|
553 |
importedmodname = get_module_part(importedmodname, |
554 |
module_file) |
555 |
except ImportError: |
556 |
pass
|
557 |
|
558 |
if context_name == importedmodname:
|
559 |
self.add_message('import-self', node=node) |
560 |
elif not is_standard_module(importedmodname): |
561 |
# handle dependencies
|
562 |
importedmodnames = self.stats['dependencies'].setdefault( |
563 |
importedmodname, set())
|
564 |
if context_name not in importedmodnames: |
565 |
importedmodnames.add(context_name) |
566 |
# update import graph
|
567 |
mgraph = self.import_graph[context_name]
|
568 |
if importedmodname not in mgraph: |
569 |
mgraph.add(importedmodname) |
570 |
|
571 |
def _check_deprecated_module(self, node, mod_path): |
572 |
"""check if the module is deprecated"""
|
573 |
for mod_name in self.config.deprecated_modules: |
574 |
if mod_path == mod_name or mod_path.startswith(mod_name + '.'): |
575 |
self.add_message('deprecated-module', node=node, args=mod_path) |
576 |
|
577 |
def _check_reimport(self, node, basename=None, level=None): |
578 |
"""check if the import is necessary (i.e. not already done)"""
|
579 |
if not self.linter.is_message_enabled('reimported'): |
580 |
return
|
581 |
|
582 |
frame = node.frame() |
583 |
root = node.root() |
584 |
contexts = [(frame, level)] |
585 |
if root is not frame: |
586 |
contexts.append((root, None))
|
587 |
|
588 |
for context, level in contexts: |
589 |
for name, _ in node.names: |
590 |
first = _get_first_import(node, context, name, basename, level) |
591 |
if first is not None: |
592 |
self.add_message('reimported', node=node, |
593 |
args=(name, first.fromlineno)) |
594 |
|
595 |
def _report_external_dependencies(self, sect, _, dummy): |
596 |
"""return a verbatim layout for displaying dependencies"""
|
597 |
dep_info = _make_tree_defs(six.iteritems(self._external_dependencies_info()))
|
598 |
if not dep_info: |
599 |
raise EmptyReport()
|
600 |
tree_str = _repr_tree_defs(dep_info) |
601 |
sect.append(VerbatimText(tree_str)) |
602 |
|
603 |
def _report_dependencies_graph(self, sect, _, dummy): |
604 |
"""write dependencies as a dot (graphviz) file"""
|
605 |
dep_info = self.stats['dependencies'] |
606 |
if not dep_info or not (self.config.import_graph |
607 |
or self.config.ext_import_graph |
608 |
or self.config.int_import_graph): |
609 |
raise EmptyReport()
|
610 |
filename = self.config.import_graph
|
611 |
if filename:
|
612 |
_make_graph(filename, dep_info, sect, '')
|
613 |
filename = self.config.ext_import_graph
|
614 |
if filename:
|
615 |
_make_graph(filename, self._external_dependencies_info(),
|
616 |
sect, 'external ')
|
617 |
filename = self.config.int_import_graph
|
618 |
if filename:
|
619 |
_make_graph(filename, self._internal_dependencies_info(),
|
620 |
sect, 'internal ')
|
621 |
|
622 |
def _external_dependencies_info(self): |
623 |
"""return cached external dependencies information or build and
|
624 |
cache them
|
625 |
"""
|
626 |
if self.__ext_dep_info is None: |
627 |
package = self.linter.current_name
|
628 |
self.__ext_dep_info = result = {}
|
629 |
for importee, importers in six.iteritems(self.stats['dependencies']): |
630 |
if not importee.startswith(package): |
631 |
result[importee] = importers |
632 |
return self.__ext_dep_info |
633 |
|
634 |
def _internal_dependencies_info(self): |
635 |
"""return cached internal dependencies information or build and
|
636 |
cache them
|
637 |
"""
|
638 |
if self.__int_dep_info is None: |
639 |
package = self.linter.current_name
|
640 |
self.__int_dep_info = result = {}
|
641 |
for importee, importers in six.iteritems(self.stats['dependencies']): |
642 |
if importee.startswith(package):
|
643 |
result[importee] = importers |
644 |
return self.__int_dep_info |
645 |
|
646 |
def _check_wildcard_imports(self, node): |
647 |
for name, _ in node.names: |
648 |
if name == '*': |
649 |
self.add_message('wildcard-import', args=node.modname, node=node) |
650 |
|
651 |
|
652 |
def register(linter): |
653 |
"""required method to auto register this checker """
|
654 |
linter.register_checker(ImportsChecker(linter)) |