# Python: Linting, Formatting, and Testing
After much experience, this is the list of modern tooling for python projects.
## Linting
### Flake8
[flake8](http://flake8.pycqa.org) does fast single-file linting with
plugins for rules.
Add to project with these plugins:
```
poetry add -D flake8 flake8-bugbear flake8-logging-format \
flake8-pep3101 flake8-builtins flake8-comprehensions \
flake8-string-format flake8-eradicate flake8-mutable \
flake8-pytest flake8-mock flake8-docstrings \
flake8-variables-names pep8-naming
```
Add a `.flake8` configuration file containing this base configuration:
```
[flake8]
ignore = D203,D103,E722
exclude =
.git,
__pycache__,
docs/source/conf.py,
old,
build,
dist
max-complexity = 10
max-line-length = 88
no-isort-config = true
use-varnames-strict-mode = true
```
### MyPy
[mypy](http://mypy-lang.org/) does type checking and important linting even
when not using type annotations.
Add to the project:
```
poetry add -D mypy
```
Add a `mypy.ini` configuration file with this configuration:
```
# Global options:
[mypy]
python_version = 3.7
ignore_missing_imports = True
follow_imports = silent
show_column_numbers = True
warn_unused_configs = True
disallow_subclassing_any = True
disallow_any_generics = True
disallow_untyped_calls = False
disallow_untyped_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
disallow_untyped_decorators = True
no_implicit_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_return_any = True
# Per-module options:
## In this section, add overrides for specific files, modules, or functions
## that don't yet have type annotations. For example:
## [mypy-older_module.older_file]
## disallow_untyped_defs = False
```
### Bandit
[bandit](https://github.com/PyCQA/bandit) finds common security issues
in Python code.
Add to the project:
```
poetry add -D bandit
```
Create a `.bandit` configuration file:
```
[bandit]
exclude: tests/
```
Bandit doesn't seem to use the configuration file by default, so you'll
need to execute it like this: `bandit --ini .bandit -r *`
## Formatting
### Black
[black](https://github.com/python/black) is an
"uncompromising Python code formatter".
Add to the project:
```
poetry add -D --allow-prereleases black
```
`--allow-prereleases` is necessary because at the time of this writing Black
does not have a stable release yet.
### isort
[isort](https://github.com/timothycrosley/isort) sorts and lints the imports at
the top of a python file.
Add to the project:
```
poetry add -D isort toml
```
Add the following to `pyproject.toml`.
This configuration is compatible with Black.
```
[tool.isort]
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
line_length = 88
known_first_party = []
known_third_party = []
```
## Testing
[pytest](https://docs.pytest.org/en/latest/) is a modern test framework that
supports fixtures and parameterization. It can be used with
`unittest.mock` for mocks, stubs, and spies.
Add to the project with these plugins:
```
poetry add -D pytest pytest-mock pytest-cov pytest-env pytest-sugar
```
See also the `pytest-xdist` plugin if concurrent testing is desired.
Create a `pytest.ini` configuration file as needed. There is no recommended
default configuration.
Create a `/tests` directory and add test files with file names that start with
`test_`, for example `test_something.py` where 'something' is the file, module,
or functionality under test.
## VSCode
To activate the above capabilities in VSCode, add the following settings to
`.vscode/settings.json` in your project directory:
```
{
"python.pythonPath": ".venv/bin/python",
"files.exclude": {
"**/__pycache__": true
},
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"python.testing.unittestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": ["--no-cov"],
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.banditEnabled": true,
"python.linting.flake8Enabled": true,
"python.linting.flake8CategorySeverity.E": "Warning",
"python.linting.mypyEnabled": true,
"python.formatting.provider": "black",
"editor.formatOnSave": true
}
```
Also, add the `rope` library to the project to allow for VSCode to refactor
python files:
```
poetry add -D rope
```
## EditorConfig
Create a `.editorconfig` file in the root of the project:
```
[*]
indent_style = space
[*.{py}]
indent_size = 4
[*.{sh}]
indent_size = 2
```
## Pre-commit
Enforce the above rules on every commit
using [pre-commit](https://pre-commit.com/).
Add to the project:
```
poetry add -D pre-commit
poetry run pre-commit install
```
Add the attached `.pre-commit-config.yaml` file to the root of the project.
## Tox
I haven't extensively tested Tox yet, but here's what I have so far in
a `tox.ini` file:
```
[tox]
isolated_build = true
envlist = py37
[testenv]
whitelist_externals = poetry
# passenv per https://pre-commit.com/#usage-with-tox
passenv = SSH_AUTH_SOCK http_proxy https_proxy no_proxy
commands =
poetry install -v
poetry run pytest tests/
```
## Further Reading
Python Code Quality: Tools & Best Practices
https://realpython.com/python-code-quality/