From 295cf2a8771e3e205075de9d86cfa13056c39f4a Mon Sep 17 00:00:00 2001 From: Jens Troeger Date: Thu, 3 Aug 2023 06:50:47 +1000 Subject: [PATCH 1/5] feat: introduce the Ruff pre-commit hook to replace a bunch of other code formatters and linter hooks --- .flake8 | 52 ------------------------ .pre-commit-config.yaml | 61 +++-------------------------- Makefile | 12 +++--- pyproject.toml | 40 +++++++++++++++++++ tests/integration/test_something.py | 8 ++-- 5 files changed, 54 insertions(+), 119 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 8f61d111..00000000 --- a/.flake8 +++ /dev/null @@ -1,52 +0,0 @@ -# Unfortunately, Flake8 does not support pyproject.toml configuration. -# https://github.com/PyCQA/flake8/issues/234 -# -# More details regarding Flake8 and Black interplay: -# https://github.com/psf/black/blob/main/docs/guides/using_black_with_other_tools.md#flake8 -[flake8] - -# Enable a few additional checks. -# -# https://github.com/PyCQA/flake8-bugbear#how-to-enable-opinionated-warnings -# B9: Bugbear's extended opinionated checks -# -# https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes -# W504: line break after binary operator (Black compliant) -extend-select = B9, W504 - -# Disable several warnings that don't play nice with PEP8 or Black, -# or that are a bit of a nuisance in general. -# -# http://www.pydocstyle.org/en/latest/error_codes.html -# D105: Missing docstring in magic method -# -# https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes -# E203: whitespace before ‘,’, ‘;’, or ‘:’ (not Black compliant) -# E501: line too long (managed better by Bugbear's B950) -# W503: line break before binary operator (not Black compliant) -# -# https://github.com/peterjc/flake8-rst-docstrings#configuration -# RST307: Error in "XXX" directive -ignore = D105, E203, E501, RST307, W503 -per-file-ignores = - -# More assorted goodness. -max-line-length = 120 -show-source = true - -# Ensure that Flake8 warnings are silenced correctly: -# https://github.com/plinss/flake8-noqa#options -noqa-require-code = true - -# Ensure that Sphinx extensions of .rst are recognized: -# https://github.com/peterjc/flake8-rst-docstrings#configuration -rst-roles = class, func, ref -rst-directives = envvar, exception -rst-substitutions = version - -# Ensure that Sphinx docstrings use Numpy format for docstrings: -# https://github.com/PyCQA/flake8-docstrings -# -# For details on the Numpy format: -# https://www.sphinx-doc.org/en/master/usage/extensions/example_numpy.html -docstring-convention = numpy diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f0d551d..dc482e03 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,50 +21,14 @@ repos: name: Check conventional commit message stages: [commit-msg] -# Sort imports. -- repo: https://github.com/pycqa/isort - rev: dac090ce4d9ee313d086e2e89ab1acb8c2664fa1 # frozen: 9.0.0a3 +# Ruff formats and lints code. +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: 3b3f7c3f57fe9925356faf5fe6230835138be230 # frozen: v0.15.17 hooks: - - id: isort - name: Sort import statements - args: [--settings-path, pyproject.toml] - stages: [pre-commit] - -# Add Black code formatters. -- repo: https://github.com/ambv/black - rev: c6755bb741b6481d6b3d3bb563c83fa060db96c9 # frozen: 26.3.1 - hooks: - - id: black - name: Format code + - id: ruff-format args: [--config, pyproject.toml] -- repo: https://github.com/asottile/blacken-docs - rev: dda8db18cfc68df532abf33b185ecd12d5b7b326 # frozen: 1.20.0 - hooks: - - id: blacken-docs - name: Format code in docstrings - args: [--line-length, '120'] - additional_dependencies: [black==26.3.1] - -# Upgrade and rewrite Python idioms. -- repo: https://github.com/asottile/pyupgrade - rev: 75992aaa40730136014f34227e0135f63fc951b4 # frozen: v3.21.2 - hooks: - - id: pyupgrade - name: Upgrade code idioms - files: ^src/package/|^tests/ - args: [--py310-plus] - -# Similar to pylint, with a few more/different checks. For more available -# extensions: https://github.com/DmytroLitvinov/awesome-flake8-extensions -- repo: https://github.com/pycqa/flake8 - rev: d93590f5be797aabb60e3b09f2f52dddb02f349f # frozen: 7.3.0 - hooks: - - id: flake8 - name: Check flake8 issues - files: ^src/package/|^tests/ - types: [text, python] - 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] - args: [--config, .flake8] + - id: ruff-check + args: [--config, pyproject.toml, --fix, --exit-non-zero-on-fix] # Run Pylint from the local repo to make sure venv packages # specified in pyproject.toml are available. @@ -89,17 +53,6 @@ repos: types: [text, python] args: [--explicit-package-bases, --config-file, pyproject.toml] -# Check for potential security issues. -- repo: https://github.com/PyCQA/bandit - rev: 92ae8b82fb422a639f0ed8d99e96cea769594e08 # frozen: 1.9.4 - hooks: - - id: bandit - name: Check for security issues - args: [--configfile, pyproject.toml] - files: ^src/package/|^tests/ - types: [text, python] - additional_dependencies: ['bandit[toml]'] - # Enable a whole bunch of useful helper hooks, too. # See https://pre-commit.com/hooks.html for more hooks. - repo: https://github.com/pre-commit/pre-commit-hooks @@ -146,8 +99,6 @@ repos: - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks rev: 4380fbb73a154b5f5624794c1c78d9719ccc860f # frozen: v2.16.0 hooks: - - id: pretty-format-ini - args: [--autofix] - id: pretty-format-yaml args: [--autofix] # Commenting this out because https://github.com/pappasam/toml-sort/issues/11 diff --git a/Makefile b/Makefile index b1827425..25941f54 100644 --- a/Makefile +++ b/Makefile @@ -161,12 +161,10 @@ audit: python -m pip_audit --skip-editable --desc on --fix --dry-run # Run some or all checks over the package code base. -.PHONY: check check-code check-bandit check-flake8 check-lint check-mypy check-actionlint -check-code: check-bandit check-flake8 check-lint check-mypy check-actionlint -check-bandit: - pre-commit run bandit --all-files -check-flake8: - pre-commit run flake8 --all-files +.PHONY: check check-code check-ruff check-lint check-mypy check-actionlint +check-code: check-ruff check-lint check-mypy check-actionlint +check-ruff: + pre-commit run ruff --all-files check-lint: pre-commit run pylint --all-files check-mypy: @@ -259,7 +257,7 @@ dist-clean: rm -fr dist/* rm -f requirements.txt clean: dist-clean - rm -fr .coverage .hypothesis/ .mypy_cache/ .pytest_cache/ + rm -fr .coverage .hypothesis/ .mypy_cache/ .pytest_cache/ .ruff_cache/ rm -fr docs/_build/ # Remove code caches, or the entire virtual environment. diff --git a/pyproject.toml b/pyproject.toml index 06fb46bb..d1a1115b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,6 +175,7 @@ ignore_missing_imports = true # https://pylint.pycqa.org/en/latest/user_guide/configuration/index.html +# https://github.com/astral-sh/ruff/issues/970 [tool.pylint.main] fail-under = 10.0 load-plugins = [ @@ -274,3 +275,42 @@ markers = [ "integration: more complex application-level integration tests.", "performance: performance tests.", ] + + +# https://docs.astral.sh/ruff/formatter/ +# https://docs.astral.sh/ruff/linter/ +[tool.ruff] +line-length = 120 + +[tool.ruff.format] +docstring-code-format = true +docstring-code-line-length = 88 + +# https://docs.astral.sh/ruff/configuration/ +# https://docs.astral.sh/ruff/rules/ +[tool.ruff.lint] +exclude = ["docs/*"] +select = [ + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "B", # flake8-bugbear + "A", # flake8-builtins + "C4", # flake8-comprehensions + "LOG", # flake8-logging + "T20", # flake8-print + "PYI", # flake8-pyi + "PT", # flake8-pytest-style + "N", # pep8-naming + "S", # flake8-bandit +] +ignore = [ + "E203", # E203: whitespace before ‘,’, ‘;’, or ‘:’ (not Black compliant) + "E501", # E501: line too long (managed better by Bugbear's B950) +] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = [ + "S101", # S101 Use of `assert` detected +] diff --git a/tests/integration/test_something.py b/tests/integration/test_something.py index acd464f6..b3bebaf1 100644 --- a/tests/integration/test_something.py +++ b/tests/integration/test_something.py @@ -3,8 +3,7 @@ # Copyright (c) 2021-2026 CODEOWNERS # This code is licensed under MIT license, see LICENSE.md for details. -# https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess -import subprocess # nosec B404 +import subprocess import pytest @@ -13,7 +12,6 @@ def test_package() -> None: """Test the Something command.""" # For testing we disable this warning here: - # https://bandit.readthedocs.io/en/latest/plugins/b603_subprocess_without_shell_equals_true.html - # https://bandit.readthedocs.io/en/latest/plugins/b607_start_process_with_partial_path.html - completed = subprocess.run(["something"], check=True, shell=False) # nosec B603, B607 + # https://docs.astral.sh/ruff/rules/start-process-with-partial-path/ + completed = subprocess.run(["something"], check=True, shell=False) # noqa: S607 assert completed.returncode == 0 From 2fff2c983d9d71270d97b29034ce7d0f55117cd9 Mon Sep 17 00:00:00 2001 From: Jens Troeger Date: Tue, 16 Jun 2026 18:39:51 +1000 Subject: [PATCH 2/5] chore: remove obsolete configurations --- pyproject.toml | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d1a1115b..c49c2b6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,18 +80,6 @@ Documentation = "https://github.com/jenstroeger/python-package-template/wiki" Issues = "https://github.com/jenstroeger/python-package-template/issues" -# https://bandit.readthedocs.io/en/latest/config.html -# Skip test B101 because of issue https://github.com/PyCQA/bandit/issues/457 -[tool.bandit] -tests = [] -skips = ["B101"] - - -# https://github.com/psf/black#configuration -[tool.black] -line-length = 120 - - # https://github.com/commitizen-tools/commitizen # https://commitizen-tools.github.io/commitizen/bump/ [tool.commitizen] @@ -134,15 +122,6 @@ exclude = [ ] -# https://pycqa.github.io/isort/ -[tool.isort] -profile = "black" -multi_line_output = 3 -line_length = 120 -skip_gitignore = true -filter_files = true - - # https://mypy.readthedocs.io/en/stable/config_file.html#using-a-pyproject-toml [tool.mypy] # mypy_path = From 5601c1adc7e70ade6235c4bc0301b502eedaef65 Mon Sep 17 00:00:00 2001 From: Jens Troeger Date: Thu, 18 Jun 2026 12:11:43 +1000 Subject: [PATCH 3/5] chore: sort ruff checks alphabetically, add configs --- pyproject.toml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c49c2b6d..4e794de6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -270,25 +270,28 @@ docstring-code-line-length = 88 [tool.ruff.lint] exclude = ["docs/*"] select = [ + "A", # flake8-builtins + "B", # flake8-bugbear + "C4", # flake8-comprehensions "E", # pycodestyle "F", # pyflakes "I", # isort - "UP", # pyupgrade - "B", # flake8-bugbear - "A", # flake8-builtins - "C4", # flake8-comprehensions "LOG", # flake8-logging - "T20", # flake8-print - "PYI", # flake8-pyi - "PT", # flake8-pytest-style "N", # pep8-naming + "PT", # flake8-pytest-style + "PYI", # flake8-pyi "S", # flake8-bandit + "T20", # flake8-print + "UP", # pyupgrade ] ignore = [ "E203", # E203: whitespace before ‘,’, ‘;’, or ‘:’ (not Black compliant) "E501", # E501: line too long (managed better by Bugbear's B950) ] +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = true + [tool.ruff.lint.per-file-ignores] "tests/*" = [ "S101", # S101 Use of `assert` detected From 9960545b9d838b9e92af47eb214fb00cb157f857 Mon Sep 17 00:00:00 2001 From: Jens Troeger Date: Mon, 22 Jun 2026 22:58:03 +1000 Subject: [PATCH 4/5] chore: add D, DOC, PIE, RUF checkers as well --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 4e794de6..f1e90874 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -273,18 +273,23 @@ select = [ "A", # flake8-builtins "B", # flake8-bugbear "C4", # flake8-comprehensions + "D", # pydocstyle + "DOC", # pydoclint "E", # pycodestyle "F", # pyflakes "I", # isort "LOG", # flake8-logging "N", # pep8-naming + "PIE", # flake8-pie "PT", # flake8-pytest-style "PYI", # flake8-pyi + "RUF", # ruff-specific rules "S", # flake8-bandit "T20", # flake8-print "UP", # pyupgrade ] ignore = [ + "D105", # D105: Missing docstring in magic method "E203", # E203: whitespace before ‘,’, ‘;’, or ‘:’ (not Black compliant) "E501", # E501: line too long (managed better by Bugbear's B950) ] @@ -296,3 +301,6 @@ fixture-parentheses = true "tests/*" = [ "S101", # S101 Use of `assert` detected ] + +[tool.ruff.lint.pydocstyle] +convention = "numpy" From b1d7d1e968d99b6f87413abb74616a4be1b29fb9 Mon Sep 17 00:00:00 2001 From: Jens Troeger Date: Mon, 22 Jun 2026 23:14:16 +1000 Subject: [PATCH 5/5] chore: update README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ca945ebf..08275fb4 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ The badges above give you an idea of what this project template provides. It’s ### Typing -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/)). +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/)). ### Quality assurance @@ -146,7 +146,7 @@ make upgrade Using the pre-commit tool and its `.pre-commit-config.yaml` configuration, the following git hooks are active in this repository: -- 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. +- 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. - 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. - 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. - 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 make check-code ``` -runs all the code checks (i.e. `bandit`, `flake8`, `pylint`, `mypy`, `actionlint`), whereas +runs all the code checks (i.e. `ruff`, `pylint`, `mypy`, `actionlint`), whereas ```bash make check ``` -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. +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. ## Testing