Skip to content

Commit d80fc12

Browse files
committed
Merge remote-tracking branch 'yole/import-on-top-of-file' into issue304
2 parents c528dbe + 1ee296b commit d80fc12

File tree

4 files changed

+64
-1
lines changed

4 files changed

+64
-1
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Bug fixes:
3030

3131
* Add ``.tox/`` to default excludes. (Issue #335)
3232

33+
* Report E402 for import statements not at the top of the file. (Issue #264)
34+
3335

3436
1.5.7 (2014-05-29)
3537
------------------

docs/developer.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ additional information with extra arguments. All attributes of the
5959
* ``previous_indent_level``: indentation on previous line
6060
* ``previous_logical``: previous logical line
6161

62+
Check plugins can also maintain per-file state. If you need this, declare
63+
a parameter named ``checker_state``. You will be passed a dict, which will be
64+
the same one for all lines in the same file but a different one for different
65+
files. Each check plugin gets its own dict, so you don't need to worry about
66+
clobbering the state of other plugins.
67+
6268
The docstring of each check function shall be the relevant part of
6369
text from `PEP 8`_. It is printed if the user enables ``--show-pep8``.
6470
Several docstrings contain examples directly from the `PEP 8`_ document.

pep8.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,52 @@ def imports_on_separate_lines(logical_line):
839839
yield found, "E401 multiple imports on one line"
840840

841841

842+
def imports_on_top_of_file(logical_line, indent_level, checker_state, noqa):
843+
r"""Imports are always put at the top of the file, just after any module
844+
comments and docstrings, and before module globals and constants.
845+
846+
Okay: import os
847+
Okay: # this is a comment\nimport os
848+
Okay: '''this is a module docstring'''\nimport os
849+
Okay: r'''this is a module docstring'''\nimport os
850+
Okay: __version__ = "123"\nimport os
851+
E402: a=1\nimport os
852+
E402: 'One string'\n"Two string"\nimport os
853+
E402: a=1\nfrom sys import x
854+
855+
Okay: if x:\n import os
856+
"""
857+
def is_string_literal(line):
858+
if line[0] in 'uUbB':
859+
line = line[1:]
860+
if line and line[0] in 'rR':
861+
line = line[1:]
862+
return line and (line[0] == '"' or line[0] == "'")
863+
864+
if indent_level: # Allow imports in conditional statements or functions
865+
return
866+
if not logical_line: # Allow empty lines or comments
867+
return
868+
if noqa:
869+
return
870+
line = logical_line
871+
if line.startswith('import ') or line.startswith('from '):
872+
if checker_state.get('seen_non_imports', False):
873+
yield 0, "E402 import not at top of file"
874+
elif line.startswith('__version__ '):
875+
# These lines should be included after the module's docstring, before
876+
# any other code, separated by a blank line above and below.
877+
return
878+
elif is_string_literal(line):
879+
# The first literal is a docstring, allow it. Otherwise, report error.
880+
if checker_state.get('seen_docstring', False):
881+
checker_state['seen_non_imports'] = True
882+
else:
883+
checker_state['seen_docstring'] = True
884+
else:
885+
checker_state['seen_non_imports'] = True
886+
887+
842888
def compound_statements(logical_line):
843889
r"""Compound statements (on the same line) are generally discouraged.
844890
@@ -1251,6 +1297,8 @@ def __init__(self, filename=None, lines=None,
12511297
self.hang_closing = options.hang_closing
12521298
self.verbose = options.verbose
12531299
self.filename = filename
1300+
# Dictionary where a checker can store its custom state.
1301+
self._checker_states = {}
12541302
if filename is None:
12551303
self.filename = 'stdin'
12561304
self.lines = lines or []
@@ -1306,10 +1354,16 @@ def run_check(self, check, argument_names):
13061354
arguments.append(getattr(self, name))
13071355
return check(*arguments)
13081356

1357+
def init_checker_state(self, name, argument_names):
1358+
""" Prepares a custom state for the specific checker plugin."""
1359+
if 'checker_state' in argument_names:
1360+
self.checker_state = self._checker_states.setdefault(name, {})
1361+
13091362
def check_physical(self, line):
13101363
"""Run all physical checks on a raw input line."""
13111364
self.physical_line = line
13121365
for name, check, argument_names in self._physical_checks:
1366+
self.init_checker_state(name, argument_names)
13131367
result = self.run_check(check, argument_names)
13141368
if result is not None:
13151369
(offset, text) = result
@@ -1368,6 +1422,7 @@ def check_logical(self):
13681422
for name, check, argument_names in self._logical_checks:
13691423
if self.verbose >= 4:
13701424
print(' ' + name)
1425+
self.init_checker_state(name, argument_names)
13711426
for offset, text in self.run_check(check, argument_names) or ():
13721427
if not isinstance(offset, tuple):
13731428
for token_offset, pos in mapping:

testsuite/E12not.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ def other_example():
632632
else 0,
633633
}
634634
#
635-
from textwrap import dedent
635+
from textwrap import dedent # noqa
636636

637637

638638
print dedent(

0 commit comments

Comments
 (0)