diff --git a/docs/Coding-Conventions.md b/docs/Coding-Conventions.md index d9154cee..b28cc3b1 100644 --- a/docs/Coding-Conventions.md +++ b/docs/Coding-Conventions.md @@ -712,17 +712,30 @@ import ministry URL = "http://python.org" ``` -### [O.1.3] ✔️ **DO** Group imports by standard library, third party, then first_party +### [O.1.3] ✔️ **DO** Group imports by standard library, third party, then first_party 💻 > 🐍 This rule stems from [PEP 8](https://www.python.org/dev/peps/pep-0008) -Additionally, you should put a blank line between each group of imports. +> 💻 This rule is enforced by error codes I201, I202 + +Additionally, you should put a single blank line between each group of imports. ```python -# Bad +# Bad - will produce I201 +import os +import ministry import my_app.utils +``` + +```python +# Bad - will produce I202 import os + +import cheese_shop + import ministry + +import my_app.utils ``` ```python @@ -734,7 +747,18 @@ import ministry import my_app.utils ``` -### [O.1.4] ✔️ **DO** Use absolute imports +### [O.1.4] ✔️ **DO** List imports in alphabetical order 💻 + +> 💻 This rule is enforced by error code I100 + +```python +# Bad +import pathlib +import os +from abc import ABC +``` + +### [O.1.5] ✔️ **DO** Use absolute imports > 🐍 This rule stems from [PEP 8](https://www.python.org/dev/peps/pep-0008) @@ -742,6 +766,7 @@ import my_app.utils ```python # Bad +from . import sibling from .sibling import rivalry ``` @@ -750,7 +775,7 @@ from .sibling import rivalry from my_app.relationships.sibling import rivalry ``` -### [O.1.5] ❌ **DO NOT** Use wildcard imports 💻 +### [O.1.6] ❌ **DO NOT** Use wildcard imports 💻 > 🐍 This rule stems from [PEP 8](https://www.python.org/dev/peps/pep-0008) @@ -773,7 +798,7 @@ from ministry import silly_walk import ministry ``` -### [O.1.6] ❌ **DO NOT** Rely on a module's imported names +### [O.1.7] ❌ **DO NOT** Rely on a module's imported names > 🐍 This rule stems from [PEP 8](https://www.python.org/dev/peps/pep-0008) @@ -784,14 +809,11 @@ import ministry ```python # Bad -# cheese_shop.py - Imports module `brie` -import brie - -# customer.py - Relying on the fact that `cheese_shop` imported module `brie` +# Assuming the module cheese_shop imported module `brie`, the following would be wrong: import cheese_shop.brie ``` -### [O.1.7] ❌ **DO NOT** Import definitions that are not used 💻 +### [O.1.8] ❌ **DO NOT** Import definitions that are not used 💻 > 💻 This rule is enforced by error code F401 @@ -800,7 +822,7 @@ import cheese_shop.brie import os # Assuming os is never used ``` -### [O.1.8] ❌ **DO NOT** Change an imported object's case 💻 +### [O.1.9] ❌ **DO NOT** Change an imported object's case 💻 > 💻 This rule is enforced by error codes N811, N812, N813, N814, N817 diff --git a/ni_python_styleguide/_acknowledge_existing_errors/__init__.py b/ni_python_styleguide/_acknowledge_existing_errors/__init__.py index 424fcccb..16b51e02 100644 --- a/ni_python_styleguide/_acknowledge_existing_errors/__init__.py +++ b/ni_python_styleguide/_acknowledge_existing_errors/__init__.py @@ -1,7 +1,7 @@ from collections import defaultdict import logging -import re import pathlib +import re from ni_python_styleguide._acknowledge_existing_errors import _lint_errors_parser diff --git a/ni_python_styleguide/_acknowledge_existing_errors/_lint_errors_parser.py b/ni_python_styleguide/_acknowledge_existing_errors/_lint_errors_parser.py index 7435e3c9..79b95c38 100644 --- a/ni_python_styleguide/_acknowledge_existing_errors/_lint_errors_parser.py +++ b/ni_python_styleguide/_acknowledge_existing_errors/_lint_errors_parser.py @@ -1,6 +1,6 @@ -import re -import logging from collections import namedtuple +import logging +import re LintError = namedtuple("LintError", ["file", "line", "column", "code", "explanation"]) diff --git a/ni_python_styleguide/_cli.py b/ni_python_styleguide/_cli.py index 90a5439e..796dae6c 100644 --- a/ni_python_styleguide/_cli.py +++ b/ni_python_styleguide/_cli.py @@ -1,10 +1,11 @@ -import click import contextlib -import flake8.main.application +from io import StringIO import logging import pathlib + +import click +import flake8.main.application import toml -from io import StringIO from ni_python_styleguide import _acknowledge_existing_errors @@ -25,6 +26,8 @@ def _read_pyproject_toml(ctx, param, value): except (toml.TomlDecodeError, OSError) as e: raise click.FileError(filename=value, hint=f"Error reading configuration file: {e}") + ctx.ensure_object(dict) + ctx.obj["PYPROJECT"] = pyproject_data config = pyproject_data.get("tool", {}).get("ni-python-styleguide", {}) config.pop("quiet", None) @@ -36,7 +39,23 @@ def _read_pyproject_toml(ctx, param, value): return value -class AllowConfigGroup(click.Group): +def _get_application_import_names(pyproject): + """Return the application package name the config.""" + # Otherwise override with what was specified + app_name = ( + pyproject.get("tool", {}) + .get("ni-python-styleguide", {}) + .get("application-import-names", "") + ) + + # Allow the poetry name as a fallback + if not app_name: + app_name = pyproject.get("tool", {}).get("poetry", {}).get("name", "").replace("-", "_") + + return f"{app_name},tests" + + +class ConfigGroup(click.Group): """click.Group subclass which allows for a config option to load options from.""" def __init__(self, *args, **kwargs): @@ -61,7 +80,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@click.group(cls=AllowConfigGroup) +@click.group(cls=ConfigGroup) @click.option( "-v", "--verbose", @@ -94,6 +113,7 @@ def main(ctx, verbose, quiet, config, exclude, extend_exclude): ctx.ensure_object(dict) ctx.obj["VERBOSITY"] = verbose - quiet ctx.obj["EXCLUDE"] = ",".join(filter(bool, [exclude.strip(","), extend_exclude.strip(",")])) + ctx.obj["APP_IMPORT_NAMES"] = _get_application_import_names(ctx.obj.get("PYPROJECT", {})) def _lint(obj, format, extend_ignore, file_or_dir): @@ -108,6 +128,7 @@ def _lint(obj, format, extend_ignore, file_or_dir): # [tool.black] setting (which makes sense if you think about it) # So we need to give it one f"--black-config={(pathlib.Path(__file__).parent / 'config.toml').resolve()}", + f"--application-import-names={obj['APP_IMPORT_NAMES']}", *file_or_dir, ] app.run(list(filter(bool, args))) diff --git a/ni_python_styleguide/config.ini b/ni_python_styleguide/config.ini index 6d5e0596..33722cf2 100644 --- a/ni_python_styleguide/config.ini +++ b/ni_python_styleguide/config.ini @@ -77,6 +77,8 @@ ignore = D213 # Multi-line docstring summary should start at the second line D400 # First line should end with a period + # flake8-import-order + I101 # The names in your from import are in the wrong order. (Enforced by E401) # Flake8 includes mccabe by default. # We have yet to evaluate it, so ignore the errors for now @@ -84,3 +86,6 @@ extend-ignore=C90 # flake8-docstrings docstring-convention=all + +# flake8-import-order +import-order-style=google diff --git a/poetry.lock b/poetry.lock index f93f2953..0a5770ac 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,45 +1,45 @@ [[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "main" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +name = "appdirs" optional = false python-versions = "*" +version = "1.4.4" [[package]] -name = "atomicwrites" -version = "1.4.0" -description = "Atomic file writes." category = "dev" +description = "Atomic file writes." +marker = "sys_platform == \"win32\"" +name = "atomicwrites" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.4.0" [[package]] -name = "attrs" -version = "20.3.0" -description = "Classes Without Boilerplate" category = "dev" +description = "Classes Without Boilerplate" +name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.3.0" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] -name = "black" -version = "20.8b1" -description = "The uncompromising code formatter." category = "main" +description = "The uncompromising code formatter." +name = "black" optional = false python-versions = ">=3.6" +version = "20.8b1" [package.dependencies] appdirs = "*" click = ">=7.1.2" -dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} mypy-extensions = ">=0.4.3" pathspec = ">=0.6,<1" regex = ">=2020.1.8" @@ -47,312 +47,343 @@ toml = ">=0.10.1" typed-ast = ">=1.4.0" typing-extensions = ">=3.7.4" +[package.dependencies.dataclasses] +python = "<3.7" +version = ">=0.6" + [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] -name = "click" -version = "7.1.2" -description = "Composable command line interface toolkit" category = "main" +description = "Composable command line interface toolkit" +name = "click" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "7.1.2" [[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." category = "dev" +description = "Cross-platform colored terminal text." +marker = "sys_platform == \"win32\"" +name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.4" [[package]] -name = "dataclasses" -version = "0.8" -description = "A backport of the dataclasses module for Python 3.6" category = "main" +description = "A backport of the dataclasses module for Python 3.6" +marker = "python_version < \"3.7\"" +name = "dataclasses" optional = false -python-versions = ">=3.6, <3.7" +python-versions = "*" +version = "0.6" [[package]] -name = "flake8" -version = "3.8.4" -description = "the modular source code checker: pep8 pyflakes and co" category = "main" +description = "the modular source code checker: pep8 pyflakes and co" +name = "flake8" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "3.8.4" [package.dependencies] -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" +[package.dependencies.importlib-metadata] +python = "<3.8" +version = "*" + [[package]] -name = "flake8-black" -version = "0.2.1" -description = "flake8 plugin to call black as a code style validator" category = "main" +description = "flake8 plugin to call black as a code style validator" +name = "flake8-black" optional = false python-versions = "*" +version = "0.2.1" [package.dependencies] black = "*" flake8 = ">=3.0.0" [[package]] -name = "flake8-docstrings" -version = "1.5.0" -description = "Extension for flake8 which uses pydocstyle to check docstrings" category = "main" +description = "Extension for flake8 which uses pydocstyle to check docstrings" +name = "flake8-docstrings" optional = false python-versions = "*" +version = "1.5.0" [package.dependencies] flake8 = ">=3" pydocstyle = ">=2.1" [[package]] -name = "flake8-polyfill" -version = "1.0.2" -description = "Polyfill package for Flake8 plugins" category = "main" +description = "Flake8 and pylama plugin that checks the ordering of import statements." +name = "flake8-import-order" +optional = false +python-versions = "*" +version = "0.18.1" + +[package.dependencies] +pycodestyle = "*" +setuptools = "*" + +[[package]] +category = "main" +description = "Polyfill package for Flake8 plugins" +name = "flake8-polyfill" optional = false python-versions = "*" +version = "1.0.2" [package.dependencies] flake8 = "*" [[package]] -name = "importlib-metadata" -version = "3.4.0" -description = "Read metadata from Python packages" category = "main" +description = "Read metadata from Python packages" +marker = "python_version < \"3.8\"" +name = "importlib-metadata" optional = false python-versions = ">=3.6" +version = "3.4.0" [package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" +[package.dependencies.typing-extensions] +python = "<3.8" +version = ">=3.6.4" + [package.extras] docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "pytest-enabler", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] [[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" +description = "iniconfig: brain-dead simple config-ini parsing" +name = "iniconfig" optional = false python-versions = "*" +version = "1.1.1" [[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" category = "main" +description = "McCabe checker, plugin for flake8" +name = "mccabe" optional = false python-versions = "*" +version = "0.6.1" [[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." category = "main" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +name = "mypy-extensions" optional = false python-versions = "*" +version = "0.4.3" [[package]] -name = "packaging" -version = "20.9" -description = "Core utilities for Python packages" category = "dev" +description = "Core utilities for Python packages" +name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "20.9" [package.dependencies] pyparsing = ">=2.0.2" [[package]] -name = "pathspec" -version = "0.8.1" -description = "Utility library for gitignore style pattern matching of file paths." category = "main" +description = "Utility library for gitignore style pattern matching of file paths." +name = "pathspec" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.8.1" [[package]] -name = "pep8-naming" -version = "0.11.1" -description = "Check PEP-8 naming conventions, plugin for flake8" category = "main" +description = "Check PEP-8 naming conventions, plugin for flake8" +name = "pep8-naming" optional = false python-versions = "*" +version = "0.11.1" [package.dependencies] flake8-polyfill = ">=1.0.2,<2" [[package]] -name = "pluggy" -version = "0.13.1" -description = "plugin and hook calling mechanisms for python" category = "dev" +description = "plugin and hook calling mechanisms for python" +name = "pluggy" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "0.13.1" [package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" [package.extras] dev = ["pre-commit", "tox"] [[package]] -name = "py" -version = "1.10.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +name = "py" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "1.10.0" [[package]] -name = "pycodestyle" -version = "2.6.0" -description = "Python style guide checker" category = "main" +description = "Python style guide checker" +name = "pycodestyle" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.6.0" [[package]] -name = "pydocstyle" -version = "5.1.1" -description = "Python docstring style checker" category = "main" +description = "Python docstring style checker" +name = "pydocstyle" optional = false python-versions = ">=3.5" +version = "5.1.1" [package.dependencies] snowballstemmer = "*" [[package]] -name = "pyflakes" -version = "2.2.0" -description = "passive checker of Python programs" category = "main" +description = "passive checker of Python programs" +name = "pyflakes" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "2.2.0" [[package]] -name = "pyparsing" -version = "2.4.7" -description = "Python parsing module" category = "dev" +description = "Python parsing module" +name = "pyparsing" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "2.4.7" [[package]] -name = "pytest" -version = "6.2.2" -description = "pytest: simple powerful testing with Python" category = "dev" +description = "pytest: simple powerful testing with Python" +name = "pytest" optional = false python-versions = ">=3.6" +version = "6.2.2" [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +atomicwrites = ">=1.0" attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +colorama = "*" iniconfig = "*" packaging = "*" pluggy = ">=0.12,<1.0.0a1" py = ">=1.8.2" toml = "*" +[package.dependencies.importlib-metadata] +python = "<3.8" +version = ">=0.12" + [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] -name = "pytest-click" -version = "1.0.2" -description = "Py.test plugin for Click" category = "dev" +description = "Py.test plugin for Click" +name = "pytest-click" optional = false python-versions = "*" +version = "1.0.2" [package.dependencies] click = ">=6.0" pytest = ">=5.0" [[package]] -name = "pytest-snapshot" -version = "0.4.2" -description = "A plugin to enable snapshot testing with pytest." category = "dev" +description = "A plugin to enable snapshot testing with pytest." +name = "pytest-snapshot" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.2" [package.dependencies] packaging = "*" pytest = ">=3.0.0" [[package]] -name = "regex" -version = "2020.11.13" -description = "Alternative regular expression module, to replace re." category = "main" +description = "Alternative regular expression module, to replace re." +name = "regex" optional = false python-versions = "*" +version = "2020.11.13" [[package]] -name = "snowballstemmer" -version = "2.1.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." category = "main" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +name = "snowballstemmer" optional = false python-versions = "*" +version = "2.1.0" [[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" category = "main" +description = "Python Library for Tom's Obvious, Minimal Language" +name = "toml" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +version = "0.10.2" [[package]] -name = "typed-ast" -version = "1.4.2" -description = "a fork of Python 2 and 3 ast modules with type comment support" category = "main" +description = "a fork of Python 2 and 3 ast modules with type comment support" +name = "typed-ast" optional = false python-versions = "*" +version = "1.4.2" [[package]] -name = "typing-extensions" -version = "3.7.4.3" -description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" +description = "Backported and Experimental Type Hints for Python 3.5+" +name = "typing-extensions" optional = false python-versions = "*" +version = "3.7.4.3" [[package]] -name = "zipp" -version = "3.4.0" -description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" +description = "Backport of pathlib-compatible object wrapper for zip files" +marker = "python_version < \"3.8\"" +name = "zipp" optional = false python-versions = ">=3.6" +version = "3.4.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] -lock-version = "1.1" +content-hash = "2500e56200ca209fa48780e65786f931763d0f3851fad32e6e932d58618fe9f9" +lock-version = "1.0" python-versions = "^3.6" -content-hash = "698a64cb82b7418354e7c4a48dd55e7e5fa088137f5843a1f1869b0465fb4f3e" [metadata.files] appdirs = [ @@ -368,6 +399,7 @@ attrs = [ {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] black = [ + {file = "black-20.8b1-py3-none-any.whl", hash = "sha256:70b62ef1527c950db59062cda342ea224d772abdf6adc58b86a45421bab20a6b"}, {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, ] click = [ @@ -379,8 +411,8 @@ colorama = [ {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] dataclasses = [ - {file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"}, - {file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"}, + {file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"}, + {file = "dataclasses-0.6.tar.gz", hash = "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"}, ] flake8 = [ {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, @@ -393,6 +425,10 @@ flake8-docstrings = [ {file = "flake8-docstrings-1.5.0.tar.gz", hash = "sha256:3d5a31c7ec6b7367ea6506a87ec293b94a0a46c0bce2bb4975b7f1d09b6f3717"}, {file = "flake8_docstrings-1.5.0-py2.py3-none-any.whl", hash = "sha256:a256ba91bc52307bef1de59e2a009c3cf61c3d0952dbe035d6ff7208940c2edc"}, ] +flake8-import-order = [ + {file = "flake8-import-order-0.18.1.tar.gz", hash = "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"}, + {file = "flake8_import_order-0.18.1-py2.py3-none-any.whl", hash = "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543"}, +] flake8-polyfill = [ {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, diff --git a/pyproject.toml b/pyproject.toml index ea2a7284..fc159d24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ toml = "^0.10.1" # flake8 plugins should be listed here (in alphabetical order) flake8-black = "^0.2.1" flake8-docstrings = "^1.5.0" +flake8-import-order = "^0.18.1" pep8-naming = "^0.11.1" # Rejected flake8 plugins should be listed here (in alphabetical order) @@ -58,7 +59,8 @@ addopts = "--doctest-modules" norecursedirs = "*__snapshots" [tool.ni-python-styleguide] -exclude = "*__snapshots/*/*input.py" +extend_exclude = "*__snapshots/*/*input.py" + [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" diff --git a/tests/conftest.py b/tests/conftest.py index c490f25c..05134893 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,6 @@ import os import click.testing - import pytest from ni_python_styleguide.__main__ import main as styleguide_main diff --git a/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/import_line_tests/input.py b/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/import_line_tests/input.py index df444070..ee55ddeb 100644 --- a/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/import_line_tests/input.py +++ b/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/import_line_tests/input.py @@ -1,17 +1,15 @@ """example of a python file with linter errors. """ +import pathlib, glob import os from os import path -import pathlib, glob - from os.path import * - from os.path.lorem.ipsum.dolor.sit.amet.consectetur.adipiscing.elit.sed.do.eiusmod.tempor.incididunt.ut.labore.et.dolore.magna import ( aliqua, - lorem, - ipsum, dolor, + ipsum, + lorem, ) aliqua() diff --git a/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/import_line_tests/output.py b/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/import_line_tests/output.py index 43663f7c..52e065cb 100644 --- a/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/import_line_tests/output.py +++ b/tests/test_cli/acknowledge_existing_errors_test_cases__snapshots/import_line_tests/output.py @@ -1,17 +1,15 @@ """example of a python file with linter errors. """ -import os -from os import path # noqa F401: 'os.path' imported but unused (auto-generated noqa) import pathlib, glob # noqa F401: 'pathlib' imported but unused (auto-generated noqa) # noqa E401: multiple imports on one line (auto-generated noqa) - +import os # noqa I100: Import statements are in the wrong order. 'import os' should be before 'import pathlib, glob' (auto-generated noqa) +from os import path # noqa F401: 'os.path' imported but unused (auto-generated noqa) from os.path import * # noqa F403: 'from os.path import *' used; unable to detect undefined names (auto-generated noqa) - from os.path.lorem.ipsum.dolor.sit.amet.consectetur.adipiscing.elit.sed.do.eiusmod.tempor.incididunt.ut.labore.et.dolore.magna import ( # noqa F401: 'os.path.lorem.ipsum.dolor.sit.amet.consectetur.adipiscing.elit.sed.do.eiusmod.tempor.incididunt.ut.labore.et.dolore.magna.lorem' imported but unused (auto-generated noqa) aliqua, - lorem, - ipsum, dolor, + ipsum, + lorem, ) aliqua() @@ -25,8 +23,8 @@ def _test_os_name(): - for os in range(3): # noqa F402: import 'os' from line 4 shadowed by loop variable (auto-generated noqa) + for os in range(3): # noqa F402: import 'os' from line 5 shadowed by loop variable (auto-generated noqa) print(os) -import collections # noqa E402: module level import not at top of file (auto-generated noqa) # noqa F401: 'collections' imported but unused (auto-generated noqa) +import collections # noqa E402: module level import not at top of file (auto-generated noqa) # noqa F401: 'collections' imported but unused (auto-generated noqa) # noqa I100: Import statements are in the wrong order. 'import collections' should be before 'from os.path.lorem.ipsum.dolor.sit.amet.consectetur.adipiscing.elit.sed.do.eiusmod.tempor.incididunt.ut.labore.et.dolore.magna import aliqua, dolor, ipsum, lorem' (auto-generated noqa) # noqa I202: Additional newline in a group of imports. 'import collections' is identified as Stdlib and 'from os.path.lorem.ipsum.dolor.sit.amet.consectetur.adipiscing.elit.sed.do.eiusmod.tempor.incididunt.ut.labore.et.dolore.magna import aliqua, dolor, ipsum, lorem' is identified as Stdlib. (auto-generated noqa) diff --git a/tests/test_cli/test_acknowledge_existing_errors.py b/tests/test_cli/test_acknowledge_existing_errors.py index 188da734..b472f4ee 100644 --- a/tests/test_cli/test_acknowledge_existing_errors.py +++ b/tests/test_cli/test_acknowledge_existing_errors.py @@ -3,9 +3,10 @@ import pathlib import shutil +import pytest + from ni_python_styleguide import _acknowledge_existing_errors -import pytest TEST_CASE_DIR = ( pathlib.Path(__file__).parent.absolute() / "acknowledge_existing_errors_test_cases__snapshots" diff --git a/tests/test_cli/test_lint.py b/tests/test_cli/test_lint.py index 5f11d452..c27801dc 100644 --- a/tests/test_cli/test_lint.py +++ b/tests/test_cli/test_lint.py @@ -2,9 +2,9 @@ import itertools +import pytest import toml -import pytest TOO_LONG_LINE = "a_really_long_order = [" + ", ".join(itertools.repeat('"spam"', 10)) + "]\n" diff --git a/tests/test_cli/test_lint_errors_parser.py b/tests/test_cli/test_lint_errors_parser.py index 4e514a21..2e9111d3 100644 --- a/tests/test_cli/test_lint_errors_parser.py +++ b/tests/test_cli/test_lint_errors_parser.py @@ -1,8 +1,9 @@ """Tests for the lint_errors_parser sub-module.""" +import pytest + import ni_python_styleguide._acknowledge_existing_errors._lint_errors_parser -import pytest EXAMPLE_LINT_ERROR_LINES = [ # noqa W505 r".\source\lorem.py:158:101: W505 doc line too long (186 > 100 characters)", diff --git a/tests/test_convention_doc/codeblock_config.toml b/tests/test_convention_doc/codeblock_config.toml new file mode 100644 index 00000000..9dc51e31 --- /dev/null +++ b/tests/test_convention_doc/codeblock_config.toml @@ -0,0 +1,3 @@ +# ni-python-styleguide config used for the codeblock tests +[tool.ni-python-styleguide] +application-import-names = "my_app" \ No newline at end of file diff --git a/tests/test_convention_doc/test_codeblocks.py b/tests/test_convention_doc/test_codeblocks.py index 614cfddc..bdd2e1cc 100644 --- a/tests/test_convention_doc/test_codeblocks.py +++ b/tests/test_convention_doc/test_codeblocks.py @@ -1,5 +1,7 @@ """Tests for the codeblocks in the convention document.""" +import pathlib + import pytest @@ -15,7 +17,6 @@ def run_linter( # code to trivial examples, detracting from the interesting lines. ignore_unused_imports=True, ): - extend_ignore = [ # Undefined name. Defining all the names in each example would detract from the # interesting lines. @@ -29,7 +30,13 @@ def run_linter( test_file = tmp_path / "test.py" test_file.write_text(codeblock.contents, encoding="utf-8") - return styleguide("lint", *styleguide_args, test_file) + return styleguide( + "--config", + pathlib.Path(__file__).parent / "codeblock_config.toml", + "lint", + *styleguide_args, + test_file, + ) return run_linter