Skip to content

Commit

Permalink
New check: import-outside-toplevel (close #3067)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickdrozd committed Sep 8, 2019
1 parent 6b3afd4 commit 514a5ed
Show file tree
Hide file tree
Showing 17 changed files with 75 additions and 19 deletions.
7 changes: 6 additions & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ What's New in Pylint 2.4.0?

Release date: TBA

* New check: ``import-outside-toplevel``

This check warns when modules are imported from places other than a
module toplevel, e.g. inside a function or a class.

* Added a new check, ``consider-using-sys-exit``

This check is emitted when we detect that a quit() or exit() is invoked
This check is emitted when we detect that a quit() or exit() is invoked
instead of sys.exit(), which is the preferred way of exiting in program.

Close #2925
Expand Down
11 changes: 8 additions & 3 deletions doc/whatsnew/2.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ Summary -- Release highlights
New checkers
============

* ``import-outside-toplevel``

This check warns when modules are imported from places other than a
module toplevel, e.g. inside a function or a class.

* Added a new check, ``consider-using-sys-exit``

This check is emitted when we detect that a quit() or exit() is invoked
This check is emitted when we detect that a quit() or exit() is invoked
instead of sys.exit(), which is the preferred way of exiting in program.

Close #2925

* Added a new check, ``arguments-out-of-order``

This check warns if you have arguments with names that match those in
Expand Down Expand Up @@ -118,7 +123,7 @@ New checkers
Other Changes
=============

* Don't emit ``protected-access`` when a single underscore prefixed attribute is used
* Don't emit ``protected-access`` when a single underscore prefixed attribute is used
inside a special method

Close #1802
Expand Down
15 changes: 15 additions & 0 deletions pylint/checkers/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,12 @@ def _make_graph(filename, dep_info, sect, gtype):
"Used when an import alias is same as original package."
"e.g using import numpy as numpy instead of import numpy as np",
),
"C0415": (
"Import outside toplevel (%s)",
"import-outside-toplevel",
"Used when an import statement is used anywhere other than the module
toplevel. Move this import to the top of the file."
),
}


Expand Down Expand Up @@ -476,6 +482,7 @@ def visit_import(self, node):
"""triggered when an import statement is seen"""
self._check_reimport(node)
self._check_import_as_rename(node)
self._check_toplevel(node)

modnode = node.root()
names = [name for name, _ in node.names]
Expand Down Expand Up @@ -969,6 +976,14 @@ def _wildcard_import_is_allowed(self, imported_module):
and "__all__" in imported_module.locals
)

def _check_toplevel(self, node):
if isinstance(node.parent, (astroid.FunctionDef, astroid.ClassDef)):
self.add_message(
"import-outside-toplevel",
args=", ".join(name[0] for name in node.names),
node=node,
)


def register(linter):
"""required method to auto register this checker """
Expand Down
3 changes: 1 addition & 2 deletions pylint/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import codecs
import os
import os.path as osp
import subprocess
import sys
import tempfile

Expand Down Expand Up @@ -80,8 +81,6 @@ def generate(self, outputfile=None, dotfile=None, mapfile=None):
:rtype: str
:return: a path to the generated file
"""
import subprocess # introduced in py 2.4

name = self.graphname
if not dotfile:
# if 'outputfile' is a dot file use it as 'dotfile'
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ def define_constant():
def global_with_import():
"""should only warn for global-statement"""
global sys # [global-statement]
import sys
import sys # pylint: disable=import-outside-toplevel
26 changes: 26 additions & 0 deletions tests/functional/import_outside_toplevel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# pylint: disable=unused-import,multiple-imports,no-self-use,missing-docstring,invalid-name,too-few-public-methods

import subprocess


def f():
import json # [import-outside-toplevel]


def g():
import os, sys # [import-outside-toplevel]


def h():
import time as thyme # [import-outside-toplevel]


def i():
import random as rand, socket as sock # [import-outside-toplevel]


class C:
import re # [import-outside-toplevel]

def j(self):
import ssl # [import-outside-toplevel]
6 changes: 6 additions & 0 deletions tests/functional/import_outside_toplevel.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import-outside-toplevel:7:f:Import outside toplevel (json)
import-outside-toplevel:11:g:Import outside toplevel (os, sys)
import-outside-toplevel:15:h:Import outside toplevel (time)
import-outside-toplevel:19:i:Import outside toplevel (random, socket)
import-outside-toplevel:23:C:Import outside toplevel (re)
import-outside-toplevel:26:C.j:Import outside toplevel (ssl)
4 changes: 2 additions & 2 deletions tests/functional/invalid_exceptions_raised.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pylint:disable=too-few-public-methods,no-init,import-error,missing-docstring, not-callable, useless-object-inheritance
# pylint:disable=too-few-public-methods,no-init,import-error,missing-docstring, not-callable, useless-object-inheritance,import-outside-toplevel
"""test pb with exceptions and old/new style classes"""


Expand Down Expand Up @@ -77,7 +77,7 @@ def bad_case9():

def unknown_bases():
"""Don't emit when we don't know the bases."""
from lala import bala
from lala import bala # pylint: disable=import-outside-toplevel
class MyException(bala):
pass
raise MyException
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/invalid_name.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
""" Tests for invalid-name checker. """
# pylint: disable=unused-import, no-absolute-import, wrong-import-position
# pylint: disable=unused-import, no-absolute-import, wrong-import-position,import-outside-toplevel

AAA = 24
try:
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/member_checks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pylint: disable=print-statement,missing-docstring,no-self-use,too-few-public-methods,bare-except,broad-except, useless-object-inheritance
# pylint: disable=using-constant-test,expression-not-assigned, assigning-non-slot, unused-variable,pointless-statement, wrong-import-order, wrong-import-position
# pylint: disable=using-constant-test,expression-not-assigned, assigning-non-slot, unused-variable,pointless-statement, wrong-import-order, wrong-import-position,import-outside-toplevel
from __future__ import print_function
import six
class Provider(object):
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/raising_format_tuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ def bad_unicode(arg):

def raise_something_without_name(arg):
'''Regression test for nodes without .node attribute'''
import standard_exceptions # pylint: disable=import-error
import standard_exceptions # pylint: disable=import-error,import-outside-toplevel
raise standard_exceptions.MyException(u'An %s', arg) # [raising-format-tuple]
2 changes: 1 addition & 1 deletion tests/functional/undefined_variable.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pylint: disable=missing-docstring, multiple-statements, useless-object-inheritance
# pylint: disable=missing-docstring, multiple-statements, useless-object-inheritance,import-outside-toplevel
# pylint: disable=too-few-public-methods, no-init, no-self-use,bare-except,broad-except, import-error
from __future__ import print_function
DEFINED = 1
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/unused_variable.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, no-self-use, useless-object-inheritance
# pylint: disable=missing-docstring, invalid-name, too-few-public-methods, no-self-use, useless-object-inheritance,import-outside-toplevel

def test_regression_737():
import xml # [unused-import]
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/wrong_import_position4.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Checks import position rule"""
# pylint: disable=unused-import,relative-import,ungrouped-imports,import-error,no-name-in-module,relative-beyond-top-level,unused-variable
# pylint: disable=unused-import,relative-import,ungrouped-imports,import-error,no-name-in-module,relative-beyond-top-level,unused-variable,import-outside-toplevel
def method1():
"""Method 1"""
import x
4 changes: 2 additions & 2 deletions tests/input/func_w0404.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@

def no_reimport():
"""docstring"""
import os
import os #pylint: disable=import-outside-toplevel
print(os)


def reimport():
"""This function contains a reimport."""
import sys
import sys #pylint: disable=import-outside-toplevel
del sys


Expand Down
2 changes: 1 addition & 1 deletion tests/input/func_w0405.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
from __future__ import absolute_import, print_function

# pylint: disable=using-constant-test,ungrouped-imports,wrong-import-position
# pylint: disable=using-constant-test,ungrouped-imports,wrong-import-position,import-outside-toplevel
import os
from os.path import join, exists
import os
Expand Down
2 changes: 1 addition & 1 deletion tests/input/func_w0612.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""test unused variable
"""
# pylint: disable=invalid-name, redefined-outer-name, no-absolute-import
# pylint: disable=invalid-name, redefined-outer-name, no-absolute-import,import-outside-toplevel
from __future__ import print_function
PATH = OS = collections = deque = None

Expand Down

0 comments on commit 514a5ed

Please sign in to comment.