summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Lee <IanLee1521@gmail.com>2014-12-14 17:21:09 -0800
committerIan Lee <IanLee1521@gmail.com>2014-12-14 17:21:09 -0800
commitd80fc1241c9a13c6585f65b217cac33f8dc6d2d7 (patch)
tree9d292b466e2b11af010fec125f935a3b7fb1dbf5
parentc528dbe17fe428854cdbfcf958fcad6df57d82d3 (diff)
parent1ee296bca0fa611d3dbe87c5c5c8009e448d2556 (diff)
downloadpep8-d80fc1241c9a13c6585f65b217cac33f8dc6d2d7.tar.gz
Merge remote-tracking branch 'yole/import-on-top-of-file' into issue304
-rw-r--r--CHANGES.txt2
-rw-r--r--docs/developer.rst6
-rwxr-xr-xpep8.py55
-rw-r--r--testsuite/E12not.py2
4 files changed, 64 insertions, 1 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index bb875a9..25222ab 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -30,6 +30,8 @@ Bug fixes:
* Add ``.tox/`` to default excludes. (Issue #335)
+* Report E402 for import statements not at the top of the file. (Issue #264)
+
1.5.7 (2014-05-29)
------------------
diff --git a/docs/developer.rst b/docs/developer.rst
index 7df1734..4725acd 100644
--- a/docs/developer.rst
+++ b/docs/developer.rst
@@ -59,6 +59,12 @@ additional information with extra arguments. All attributes of the
* ``previous_indent_level``: indentation on previous line
* ``previous_logical``: previous logical line
+Check plugins can also maintain per-file state. If you need this, declare
+a parameter named ``checker_state``. You will be passed a dict, which will be
+the same one for all lines in the same file but a different one for different
+files. Each check plugin gets its own dict, so you don't need to worry about
+clobbering the state of other plugins.
+
The docstring of each check function shall be the relevant part of
text from `PEP 8`_. It is printed if the user enables ``--show-pep8``.
Several docstrings contain examples directly from the `PEP 8`_ document.
diff --git a/pep8.py b/pep8.py
index 5323592..aa1b835 100755
--- a/pep8.py
+++ b/pep8.py
@@ -839,6 +839,52 @@ def imports_on_separate_lines(logical_line):
yield found, "E401 multiple imports on one line"
+def imports_on_top_of_file(logical_line, indent_level, checker_state, noqa):
+ r"""Imports are always put at the top of the file, just after any module
+ comments and docstrings, and before module globals and constants.
+
+ Okay: import os
+ Okay: # this is a comment\nimport os
+ Okay: '''this is a module docstring'''\nimport os
+ Okay: r'''this is a module docstring'''\nimport os
+ Okay: __version__ = "123"\nimport os
+ E402: a=1\nimport os
+ E402: 'One string'\n"Two string"\nimport os
+ E402: a=1\nfrom sys import x
+
+ Okay: if x:\n import os
+ """
+ def is_string_literal(line):
+ if line[0] in 'uUbB':
+ line = line[1:]
+ if line and line[0] in 'rR':
+ line = line[1:]
+ return line and (line[0] == '"' or line[0] == "'")
+
+ if indent_level: # Allow imports in conditional statements or functions
+ return
+ if not logical_line: # Allow empty lines or comments
+ return
+ if noqa:
+ return
+ line = logical_line
+ if line.startswith('import ') or line.startswith('from '):
+ if checker_state.get('seen_non_imports', False):
+ yield 0, "E402 import not at top of file"
+ elif line.startswith('__version__ '):
+ # These lines should be included after the module's docstring, before
+ # any other code, separated by a blank line above and below.
+ return
+ elif is_string_literal(line):
+ # The first literal is a docstring, allow it. Otherwise, report error.
+ if checker_state.get('seen_docstring', False):
+ checker_state['seen_non_imports'] = True
+ else:
+ checker_state['seen_docstring'] = True
+ else:
+ checker_state['seen_non_imports'] = True
+
+
def compound_statements(logical_line):
r"""Compound statements (on the same line) are generally discouraged.
@@ -1251,6 +1297,8 @@ class Checker(object):
self.hang_closing = options.hang_closing
self.verbose = options.verbose
self.filename = filename
+ # Dictionary where a checker can store its custom state.
+ self._checker_states = {}
if filename is None:
self.filename = 'stdin'
self.lines = lines or []
@@ -1306,10 +1354,16 @@ class Checker(object):
arguments.append(getattr(self, name))
return check(*arguments)
+ def init_checker_state(self, name, argument_names):
+ """ Prepares a custom state for the specific checker plugin."""
+ if 'checker_state' in argument_names:
+ self.checker_state = self._checker_states.setdefault(name, {})
+
def check_physical(self, line):
"""Run all physical checks on a raw input line."""
self.physical_line = line
for name, check, argument_names in self._physical_checks:
+ self.init_checker_state(name, argument_names)
result = self.run_check(check, argument_names)
if result is not None:
(offset, text) = result
@@ -1368,6 +1422,7 @@ class Checker(object):
for name, check, argument_names in self._logical_checks:
if self.verbose >= 4:
print(' ' + name)
+ self.init_checker_state(name, argument_names)
for offset, text in self.run_check(check, argument_names) or ():
if not isinstance(offset, tuple):
for token_offset, pos in mapping:
diff --git a/testsuite/E12not.py b/testsuite/E12not.py
index a53d9a4..995a368 100644
--- a/testsuite/E12not.py
+++ b/testsuite/E12not.py
@@ -632,7 +632,7 @@ some_hash = {
else 0,
}
#
-from textwrap import dedent
+from textwrap import dedent # noqa
print dedent(