Skip to content

Commit 89e8e1b

Browse files
authored
feat: introduce the Ruff pre-commit hook to replace a bunch of other code formatters and linter hooks (#593)
1 parent 1a5a2c2 commit 89e8e1b

6 files changed

Lines changed: 75 additions & 144 deletions

File tree

.flake8

Lines changed: 0 additions & 52 deletions
This file was deleted.

.pre-commit-config.yaml

Lines changed: 6 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -21,50 +21,14 @@ repos:
2121
name: Check conventional commit message
2222
stages: [commit-msg]
2323

24-
# Sort imports.
25-
- repo: https://github.com/pycqa/isort
26-
rev: dac090ce4d9ee313d086e2e89ab1acb8c2664fa1 # frozen: 9.0.0a3
24+
# Ruff formats and lints code.
25+
- repo: https://github.com/astral-sh/ruff-pre-commit
26+
rev: 3b3f7c3f57fe9925356faf5fe6230835138be230 # frozen: v0.15.17
2727
hooks:
28-
- id: isort
29-
name: Sort import statements
30-
args: [--settings-path, pyproject.toml]
31-
stages: [pre-commit]
32-
33-
# Add Black code formatters.
34-
- repo: https://github.com/ambv/black
35-
rev: c6755bb741b6481d6b3d3bb563c83fa060db96c9 # frozen: 26.3.1
36-
hooks:
37-
- id: black
38-
name: Format code
28+
- id: ruff-format
3929
args: [--config, pyproject.toml]
40-
- repo: https://github.com/asottile/blacken-docs
41-
rev: dda8db18cfc68df532abf33b185ecd12d5b7b326 # frozen: 1.20.0
42-
hooks:
43-
- id: blacken-docs
44-
name: Format code in docstrings
45-
args: [--line-length, '120']
46-
additional_dependencies: [black==26.3.1]
47-
48-
# Upgrade and rewrite Python idioms.
49-
- repo: https://github.com/asottile/pyupgrade
50-
rev: 75992aaa40730136014f34227e0135f63fc951b4 # frozen: v3.21.2
51-
hooks:
52-
- id: pyupgrade
53-
name: Upgrade code idioms
54-
files: ^src/package/|^tests/
55-
args: [--py310-plus]
56-
57-
# Similar to pylint, with a few more/different checks. For more available
58-
# extensions: https://github.com/DmytroLitvinov/awesome-flake8-extensions
59-
- repo: https://github.com/pycqa/flake8
60-
rev: d93590f5be797aabb60e3b09f2f52dddb02f349f # frozen: 7.3.0
61-
hooks:
62-
- id: flake8
63-
name: Check flake8 issues
64-
files: ^src/package/|^tests/
65-
types: [text, python]
66-
additional_dependencies: [flake8-bugbear==25.11.29, flake8-builtins==3.1.0, flake8-comprehensions==3.17.0, flake8-docstrings==1.7.0, flake8-logging==1.8.0, flake8-mutable==1.2.0, flake8-noqa==1.4.0, flake8-print==5.0.0, flake8-pyi==25.5.0, flake8-pytest-style==2.2.0, flake8-rst-docstrings==0.4.0, pep8-naming==0.15.1]
67-
args: [--config, .flake8]
30+
- id: ruff-check
31+
args: [--config, pyproject.toml, --fix, --exit-non-zero-on-fix]
6832

6933
# Run Pylint from the local repo to make sure venv packages
7034
# specified in pyproject.toml are available.
@@ -89,17 +53,6 @@ repos:
8953
types: [text, python]
9054
args: [--explicit-package-bases, --config-file, pyproject.toml]
9155

92-
# Check for potential security issues.
93-
- repo: https://github.com/PyCQA/bandit
94-
rev: 92ae8b82fb422a639f0ed8d99e96cea769594e08 # frozen: 1.9.4
95-
hooks:
96-
- id: bandit
97-
name: Check for security issues
98-
args: [--configfile, pyproject.toml]
99-
files: ^src/package/|^tests/
100-
types: [text, python]
101-
additional_dependencies: ['bandit[toml]']
102-
10356
# Enable a whole bunch of useful helper hooks, too.
10457
# See https://pre-commit.com/hooks.html for more hooks.
10558
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -146,8 +99,6 @@ repos:
14699
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
147100
rev: 4380fbb73a154b5f5624794c1c78d9719ccc860f # frozen: v2.16.0
148101
hooks:
149-
- id: pretty-format-ini
150-
args: [--autofix]
151102
- id: pretty-format-yaml
152103
args: [--autofix]
153104
# Commenting this out because https://github.com/pappasam/toml-sort/issues/11

Makefile

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,10 @@ audit:
161161
python -m pip_audit --skip-editable --desc on --fix --dry-run
162162

163163
# Run some or all checks over the package code base.
164-
.PHONY: check check-code check-bandit check-flake8 check-lint check-mypy check-actionlint
165-
check-code: check-bandit check-flake8 check-lint check-mypy check-actionlint
166-
check-bandit:
167-
pre-commit run bandit --all-files
168-
check-flake8:
169-
pre-commit run flake8 --all-files
164+
.PHONY: check check-code check-ruff check-lint check-mypy check-actionlint
165+
check-code: check-ruff check-lint check-mypy check-actionlint
166+
check-ruff:
167+
pre-commit run ruff --all-files
170168
check-lint:
171169
pre-commit run pylint --all-files
172170
check-mypy:
@@ -259,7 +257,7 @@ dist-clean:
259257
rm -fr dist/*
260258
rm -f requirements.txt
261259
clean: dist-clean
262-
rm -fr .coverage .hypothesis/ .mypy_cache/ .pytest_cache/
260+
rm -fr .coverage .hypothesis/ .mypy_cache/ .pytest_cache/ .ruff_cache/
263261
rm -fr docs/_build/
264262

265263
# Remove code caches, or the entire virtual environment.

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The badges above give you an idea of what this project template provides. It’s
3434

3535
### Typing
3636

37-
The package requires a minimum of [Python 3.10](https://www.python.org/downloads/release/python-31020/), and it supports [Python 3.11](https://www.python.org/downloads/release/python-31115/), [Python 3.12](https://www.python.org/downloads/release/python-31213/), [Python 3.13](https://www.python.org/downloads/release/python-31313/), and [Python 3.14](https://www.python.org/downloads/release/python-3144/) ( (default). All code requires comprehensive [typing](https://docs.python.org/3/library/typing.html). The [mypy](http://mypy-lang.org/) static type checker and the [flake8-pyi](https://github.com/PyCQA/flake8-pyi) plugin are invoked by git hooks and through a Github Action to enforce continuous type checks on Python source and [stub files](https://peps.python.org/pep-0484/#stub-files). Make sure to add type hints to your code or to use [stub files](https://mypy.readthedocs.io/en/stable/stubs.html) for types, to ensure that users of your package can `import` and type-check your code (see also [PEP 561](https://www.python.org/dev/peps/pep-0561/)).
37+
The package requires a minimum of [Python 3.10](https://www.python.org/downloads/release/python-31020/), and it supports [Python 3.11](https://www.python.org/downloads/release/python-31115/), [Python 3.12](https://www.python.org/downloads/release/python-31213/), [Python 3.13](https://www.python.org/downloads/release/python-31313/), and [Python 3.14](https://www.python.org/downloads/release/python-3144/) (default). All code requires comprehensive [typing](https://docs.python.org/3/library/typing.html). The [mypy](http://mypy-lang.org/) static type checker and ruff’s [pyi](https://docs.astral.sh/ruff/rules/#flake8-pyi-pyi) plugin are invoked by git hooks and through a Github Action to enforce continuous type checks on Python source and [stub files](https://peps.python.org/pep-0484/#stub-files). Make sure to add type hints to your code or to use [stub files](https://mypy.readthedocs.io/en/stable/stubs.html) for types, to ensure that users of your package can `import` and type-check your code (see also [PEP 561](https://www.python.org/dev/peps/pep-0561/)).
3838

3939
### Quality assurance
4040

@@ -146,7 +146,7 @@ make upgrade
146146

147147
Using the pre-commit tool and its `.pre-commit-config.yaml` configuration, the following git hooks are active in this repository:
148148

149-
- When committing code, a number of [pre-commit hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#_committing_workflow_hooks) ensure that your code is formatted according to [PEP 8](https://www.python.org/dev/peps/pep-0008/) using the [`black`](https://github.com/psf/black) tool, and they’ll invoke [`flake8`](https://github.com/PyCQA/flake8) (and various plugins), [`pylint`](https://github.com/PyCQA/pylint) and [`mypy`](https://github.com/python/mypy) to check for lint and correct types. There are more checks, but those two are the important ones. You can adjust the settings for these tools in the `pyproject.toml` or `.flake8` configuration files.
149+
- When committing code, a number of [pre-commit hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#_committing_workflow_hooks) ensure that your code is formatted according to [PEP 8](https://www.python.org/dev/peps/pep-0008/) using [ruff’s formatter](https://docs.astral.sh/ruff/formatter/), and they’ll invoke [`ruff`](https://docs.astral.sh/ruff/linter/) (and various plugins), [`pylint`](https://github.com/PyCQA/pylint) and [`mypy`](https://github.com/python/mypy) to check for Python code flakes and lint and for correct types. You can adjust the settings for these tools in the `pyproject.toml` configuration file.
150150
- The [commit message hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#_committing_workflow_hooks) enforces [conventional commit messages](https://www.conventionalcommits.org/) and that, in turn, enables a _semantic release_ of this package on the Github side: upon merging changes into the `release` branch, the [release action](https://github.com/jenstroeger/python-package-template/blob/main/.github/workflows/release.yaml) uses the [Commitizen tool](https://commitizen-tools.github.io/commitizen/) to produce a [changelog](https://en.wikipedia.org/wiki/Changelog) and it computes the next version of this package and publishes a release — all based on the commit messages of a release.
151151
- Using a [pre-push hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#_other_client_hooks) this package is also set up to run [`pytest`](https://github.com/pytest-dev/pytest); in addition, the [`coverage`](https://github.com/nedbat/coveragepy) plugin makes sure that _all_ of your package’s code is covered by unit tests and [Hypothesis](https://hypothesis.works/) and [Faker](https://github.com/joke2k/faker) are already installed to help with generating test case payloads.
152152
- The [`actionlint`](https://github.com/Mateusz-Grzelinski/actionlint-py) hook is set up to lint GitHub Actions workflows. If [`shellcheck`](https://github.com/koalaman/shellcheck) is installed on the system, `actionlint` runs `shellcheck` to lint the `run` steps in GitHub Actions. Note that `shellcheck` is available on [Ubuntu GitHub Actions runners](https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2204-Readme.md) by default.
@@ -157,13 +157,13 @@ You can also run these hooks manually, which comes in very handy during daily de
157157
make check-code
158158
```
159159

160-
runs all the code checks (i.e. `bandit`, `flake8`, `pylint`, `mypy`, `actionlint`), whereas
160+
runs all the code checks (i.e. `ruff`, `pylint`, `mypy`, `actionlint`), whereas
161161

162162
```bash
163163
make check
164164
```
165165

166-
runs _all_ installed git hooks over your code. For more control over the code checks, the Makefile also implements the `check-bandit`, `check-flake8`, `check-lint`, `check-mypy`, and `check-actionlint` goals.
166+
runs _all_ installed git hooks over your code. For more control over the code checks, the Makefile also implements the `check-ruff`, `check-lint`, `check-mypy`, and `check-actionlint` goals.
167167

168168
## Testing
169169

pyproject.toml

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,6 @@ Documentation = "https://github.com/jenstroeger/python-package-template/wiki"
8080
Issues = "https://github.com/jenstroeger/python-package-template/issues"
8181

8282

83-
# https://bandit.readthedocs.io/en/latest/config.html
84-
# Skip test B101 because of issue https://github.com/PyCQA/bandit/issues/457
85-
[tool.bandit]
86-
tests = []
87-
skips = ["B101"]
88-
89-
90-
# https://github.com/psf/black#configuration
91-
[tool.black]
92-
line-length = 120
93-
94-
9583
# https://github.com/commitizen-tools/commitizen
9684
# https://commitizen-tools.github.io/commitizen/bump/
9785
[tool.commitizen]
@@ -134,15 +122,6 @@ exclude = [
134122
]
135123

136124

137-
# https://pycqa.github.io/isort/
138-
[tool.isort]
139-
profile = "black"
140-
multi_line_output = 3
141-
line_length = 120
142-
skip_gitignore = true
143-
filter_files = true
144-
145-
146125
# https://mypy.readthedocs.io/en/stable/config_file.html#using-a-pyproject-toml
147126
[tool.mypy]
148127
# mypy_path =
@@ -175,6 +154,7 @@ ignore_missing_imports = true
175154

176155

177156
# https://pylint.pycqa.org/en/latest/user_guide/configuration/index.html
157+
# https://github.com/astral-sh/ruff/issues/970
178158
[tool.pylint.main]
179159
fail-under = 10.0
180160
load-plugins = [
@@ -274,3 +254,59 @@ markers = [
274254
"integration: more complex application-level integration tests.",
275255
"performance: performance tests.",
276256
]
257+
258+
259+
# https://docs.astral.sh/ruff/formatter/
260+
# https://docs.astral.sh/ruff/linter/
261+
[tool.ruff]
262+
line-length = 120
263+
264+
[tool.ruff.format]
265+
docstring-code-format = true
266+
docstring-code-line-length = 88
267+
268+
# https://docs.astral.sh/ruff/configuration/
269+
# https://docs.astral.sh/ruff/rules/
270+
[tool.ruff.lint]
271+
exclude = ["docs/*"]
272+
select = [
273+
"A", # flake8-builtins
274+
"B", # flake8-bugbear
275+
# "FBT", # flake8-boolean-trap
276+
"C4", # flake8-comprehensions
277+
"D", # pydocstyle
278+
"DTZ", # flake8-datetimez
279+
"DOC", # pydoclint
280+
"E", # pycodestyle
281+
"F", # pyflakes
282+
"FURB", # refurb
283+
"I", # isort
284+
"ICN", # flake8-import-conventions
285+
"LOG", # flake8-logging
286+
"N", # pep8-naming
287+
"PIE", # flake8-pie
288+
"PT", # flake8-pytest-style
289+
"PYI", # flake8-pyi
290+
"RUF", # ruff-specific rules
291+
"S", # flake8-bandit
292+
"SIM", # flake8-simplify
293+
"SLOT", # flake8-slots
294+
"T20", # flake8-print
295+
"UP", # pyupgrade
296+
]
297+
ignore = [
298+
"D105", # D105: Missing docstring in magic method
299+
"E203", # E203: whitespace before ‘,’, ‘;’, or ‘:’ (not Black compliant)
300+
"E501", # E501: line too long (managed better by Bugbear's B950)
301+
]
302+
303+
[tool.ruff.lint.flake8-pytest-style]
304+
fixture-parentheses = true
305+
306+
[tool.ruff.lint.per-file-ignores]
307+
"tests/*" = [
308+
"S101", # S101 Use of `assert` detected
309+
]
310+
311+
[tool.ruff.lint.pydocstyle]
312+
convention = "numpy"

tests/integration/test_something.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
# Copyright (c) 2021-2026 CODEOWNERS
44
# This code is licensed under MIT license, see LICENSE.md for details.
55

6-
# https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess
7-
import subprocess # nosec B404
6+
import subprocess
87

98
import pytest
109

@@ -13,7 +12,6 @@
1312
def test_package() -> None:
1413
"""Test the Something command."""
1514
# For testing we disable this warning here:
16-
# https://bandit.readthedocs.io/en/latest/plugins/b603_subprocess_without_shell_equals_true.html
17-
# https://bandit.readthedocs.io/en/latest/plugins/b607_start_process_with_partial_path.html
18-
completed = subprocess.run(["something"], check=True, shell=False) # nosec B603, B607
15+
# https://docs.astral.sh/ruff/rules/start-process-with-partial-path/
16+
completed = subprocess.run(["something"], check=True, shell=False) # noqa: S607
1917
assert completed.returncode == 0

0 commit comments

Comments
 (0)