diff --git a/docs/intro.rst b/docs/intro.rst index 8c74c782..6a3a76d4 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -335,6 +335,8 @@ This is the current list of error and warning codes: +------------+----------------------------------------------------------------------+ | E402 | module level import not at top of file | +------------+----------------------------------------------------------------------+ +| E403 | module level dunder name found after non-future imports | ++------------+----------------------------------------------------------------------+ +------------+----------------------------------------------------------------------+ | **E5** | *Line length* | +------------+----------------------------------------------------------------------+ diff --git a/pycodestyle.py b/pycodestyle.py index 6443a42a..754be0bd 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -46,6 +46,9 @@ 700 statements 900 syntax error """ + +__version__ = '2.11.1' + import bisect import configparser import inspect @@ -68,8 +71,6 @@ ): # pragma: no cover ( 1: + yield 0, "E303 too many blank lines (%d)" % blank_lines + elif DUNDER_REGEX.match(logical_line): + if not blank_lines: + yield 0, "E301 expected 1 blank line, found 0" + if blank_lines > 1: + yield 0, "E303 too many blank lines (%d)" % blank_lines elif STARTSWITH_TOP_LEVEL_REGEX.match(logical_line): # allow a group of one-liners if ( @@ -1161,7 +1174,12 @@ def is_string_literal(line): if line.startswith('import ') or line.startswith('from '): if checker_state.get('seen_non_imports', False): yield 0, "E402 module level import not at top of file" + if line.split()[1] == '__future__': + return # Allow before dunders - https://bugs.python.org/issue27187 + checker_state['seen_imports'] = True elif re.match(DUNDER_REGEX, line): + if checker_state.get('seen_imports', False): + yield 0, "E403 module-level dunder name after non-future imports" return elif any(line.startswith(kw) for kw in allowed_keywords): # Allow certain keywords intermixed with imports in order to diff --git a/testing/data/E30.py b/testing/data/E30.py index 7ef46890..be5baa29 100644 --- a/testing/data/E30.py +++ b/testing/data/E30.py @@ -203,3 +203,24 @@ def f(): @hi def g(): pass +#: E303:4:1 E303:7:1 +from __future__ import annotations + + +__all__ = ('xyz',) + + +import foo +#: E301:2:1 E301:3:1 +from __future__ import annotations +__all__ = ('xyz',) +import foo +#: E303:8:1 +__all__ = ('together',) + + + + + + +import foo diff --git a/testing/data/E30not.py b/testing/data/E30not.py index 9c33236b..22a9ba34 100644 --- a/testing/data/E30not.py +++ b/testing/data/E30not.py @@ -213,3 +213,8 @@ def f( a, ): pass +#: Okay +from __future__ import annotations + +__all__ = ('not_lost',) +__author__ = 'you' diff --git a/testing/data/python38.py b/testing/data/python38.py index 44737fed..d43b68fc 100644 --- a/testing/data/python38.py +++ b/testing/data/python38.py @@ -28,7 +28,7 @@ def f3( #: E225:1:18 if False or (x :=1): pass -#: Okay +#: E403:3:1 import typing as t __all__: t.List[str] = []