Linting¶
Before writing any code, set up formatters and linters.
In general, add all project configuration to pyproject.toml. Do not use setup.cfg, setup.py, .editorconfig or tool-specific files like .coveragerc or pytest.ini.
Configuration¶
New projects should use the Ruff formatter and linter, with line lengths of 119 (the Django coding style until 4.0). A starting point, based on script.sh in standard-maintenance-scripts:
[tool.ruff]
line-length = 119
target-version = "py311"
[tool.ruff.lint]
select = ["ALL"]
ignore = [
"ANN", "C901", "COM812", "D203", "D212", "D415", "EM", "ISC001", "PERF203", "PLR091", "Q000",
"D1",
]
[tool.ruff.lint.flake8-builtins]
builtins-ignorelist = ["copyright"]
[tool.ruff.lint.flake8-unused-arguments]
ignore-variadic-names = true
[tool.ruff.lint.per-file-ignores]
"docs/conf.py" = ["D100", "INP001"]
"tests/*" = [
"ARG001", "D", "FBT003", "INP001", "PLR2004", "S", "TRY003",
]
With this starting point, check which rules fail:
ruff check . --statistics
And check individual failures, for example:
ruff check . --select D400
As general guidance:
Fix failures, if possible.
Use a
# noqa: RULEcomment if the failure is rare (for example, anSrule), or if it should be fixed, given more time. Add a short comment to explain the failure. For example:# noqa: S104 # DockerUse
per-file-ignoresif the failures occur in a single (or a set of) files. For example:"*/__main__.py" = ["T201"] # printUse
ignoreif the failures occur in disparate files and are expected to occur in new code. For example:"TRY003", # errorsUse settings where possible, instead of ignoring rules entirely. Notably, use:
builtins-ignorelist, instead of A002
extend-immutable-calls, instead of B008
allowed-confusables, instead of RUF001
extend-ignore-names, instead of SLF001
isort:skip and type: ignore comments should be avoided, and should reference the specific error if used, to avoid shadowing another error: for example, # type: ignore[attr-defined].
Complexity rules
We ignore the C901 and all PLR091 rules.
Complexity is best measured by the effort required to read and modify code. This cannot be measured using techniques like cyclomatic complexity. Reducing cyclomatic complexity typically means extracting single-caller methods and/or using object-oriented programming, which frequently increases cognitive complexity.
See the note under Create products sustainably.
Pre-commit hooks¶
To avoid pushing commits that fail formatting or linting checks, new projects should use pre-commit. For example, if Ruff is configured as above, create a .pre-commit-config.yaml file:
ci:
autoupdate_schedule: quarterly
skip: [pip-compile]
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.2
hooks:
- id: ruff-check
- id: ruff-format
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.7.19
hooks:
- id: pip-compile
name: pip-compile requirements.in
args: [requirements.in, -o, requirements.txt]
- id: pip-compile
name: pip-compile requirements_dev.in
args: [requirements_dev.in, -o, requirements_dev.txt]
files: ^requirements(_dev)?\.(in|txt)$
ci:
autoupdate_schedule: quarterly
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.2
hooks:
- id: ruff-check
- id: ruff-format
Note
pre-commit.ci disallows network connections. As such, the pip-compile hook is configured to be skipped in the ci section, and is run by the lint.yml workflow, instead.
Note
pre-commit/pre-commit-hooks is not used in the templates, as the errors it covers are rarely encountered.
Tip
If you encounter an error like:
RuntimeError: failed to find interpreter for Builtin discover of python_spec='python3.10'
pre-commit uses virtualenv to discover Python interpreters. On macOS, install the missing version with Homebrew, instead of uv:
brew install python@3.10
Continuous integration¶
Create a .github/workflows/lint.yml file.
The Django and Pypackage Cookiecutter templates contain default workflows.
See also
Workflow files for linting shell scripts and Javascript files
standard-maintenance-scripts to learn about the Bash scripts
Additional linting¶
codespell finds typographical errors. It is especially useful in repositories with lengthy documentation. Otherwise, all repositories can be periodically checked with:
codespell -S '.git,.pytest_cache,cassettes,fixtures,_build,build,dist,target,locale,locales,vendor,node_modules,docson,htmlcov,schemaspy,*.csv,*.json,*.jsonl,*.map,*.po,european-union-support'