Skip to content

Commit f3a12ba

Browse files
committed
Merge branch 'issue304'
2 parents c528dbe + ed36d8b commit f3a12ba

File tree

5 files changed

+67
-1
lines changed

5 files changed

+67
-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.

docs/intro.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,8 @@ This is the current list of error and warning codes:
324324
+----------+----------------------------------------------------------------------+
325325
| E401 | multiple imports on one line |
326326
+----------+----------------------------------------------------------------------+
327+
| E402 | module level import not at top of file |
328+
+----------+----------------------------------------------------------------------+
327329
+----------+----------------------------------------------------------------------+
328330
| **E5** | *Line length* |
329331
+----------+----------------------------------------------------------------------+

pep8.py

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

841841

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

1358+
def init_checker_state(self, name, argument_names):
1359+
""" Prepares a custom state for the specific checker plugin."""
1360+
if 'checker_state' in argument_names:
1361+
self.checker_state = self._checker_states.setdefault(name, {})
1362+
13091363
def check_physical(self, line):
13101364
"""Run all physical checks on a raw input line."""
13111365
self.physical_line = line
13121366
for name, check, argument_names in self._physical_checks:
1367+
self.init_checker_state(name, argument_names)
13131368
result = self.run_check(check, argument_names)
13141369
if result is not None:
13151370
(offset, text) = result
@@ -1368,6 +1423,7 @@ def check_logical(self):
13681423
for name, check, argument_names in self._logical_checks:
13691424
if self.verbose >= 4:
13701425
print(' ' + name)
1426+
self.init_checker_state(name, argument_names)
13711427
for offset, text in self.run_check(check, argument_names) or ():
13721428
if not isinstance(offset, tuple):
13731429
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)