🪝

Ruff + pre-commitでコミット時にコード品質を保ちたい

2023/11/19に公開

リモートブランチのコードの品質を保ちたい

最近、コードレビューする時にこんな悩みがありました。

  • 戻り値のアノテーションが漏れているな…
  • docString書き忘れている…
  • あれ?そもそもこれ書いた人のフォーマッター・リンター効いていないかも…?

個人的な意見として
リモートブランチにプッシュされるコードはリンターやフォーマッターを正常に通過したものであってほしいと思っています。

正常に通過したものであれば、本来のコードレビューの目的である
コードの書きっぷりに注力できると思いました。

色々調べた結果、pre-commitを使えば解決できそうだとわかったので
検証含めて記事に残します。

使用技術

pre-commitは
コードがリポジトリにコミットされる前に特定のチェックやタスクを実行してくれる優れものです。
https://pre-commit.com/

これを使用し、コミット時にリンター・フォーマッターをかけ
ミスがあればコミット出来ない仕組みにします。

Pythonのリンター・フォーマッターは色々ありますが
今回はRuffmypyを採用しました。

インストール

pre-commit

pipかbrewでインストール

pip install pre-commit

# もしくは
brew install pre-commit

Ruff

pipでインストール

pip install ruff

mypy

pipでインストール

pip install mypy

Tips. VsCodeで開発しているなら…

VsCodeで開発している場合、Ruffとmypyは拡張機能で対応出来ます。

Name ID URL
Ruff charliermarsh.ruff https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff
Mypy ms-python.mypy-type-checker https://marketplace.visualstudio.com/items?itemName=ms-python.mypy-type-checker

設定ファイル

インストールしたらコミット時の検証ができるように
設定ファイルを準備します。

準備するファイルは以下の通り

ファイル名 説明
.pre-commit-config.yaml pre-commitの設定ファイル。コミット時の検証方法を記述。
pyproject.toml Ruff,mypyの設定を記述。コミット検証時はこの設定を通して確認が行われます。
.vscode/settings.json Ruff,mypyの設定を記述。(VsCode使用する場合)

これらはプロジェクトルート配下に配置します。

.
├── .pre-commit-config.yaml
├── pyproject.toml
└── .vscode
    └── settings.json

.pre-commit-config.yaml

コミット時の検証内容を記述します。
各キーの意味はこちらを参照


repos:
# Ruff
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.1.6
    hooks:
      # Run the linter.
      - id: ruff
        name: ruff
        description: "Run 'ruff' for extremely fast Python linting"
        entry: ruff check --force-exclude
        language: python
        types_or: [python, pyi]
        # --fix: enable lint fixes
        args: [--fix]
        require_serial: true
        additional_dependencies: []
        minimum_pre_commit_version: "2.9.2"
      # Run the formatter.
      - id: ruff-format
        name: ruff-format
        description: "Run 'ruff format' for extremely fast Python formatting"
        entry: ruff format --force-exclude
        language: python
        types_or: [python, pyi]
        args: []
        require_serial: true
        additional_dependencies: []
        minimum_pre_commit_version: "2.9.2"
# mypy
  -   repo: https://github.com/pre-commit/mirrors-mypy
      rev: v1.7.0
      hooks:
      # Run the mypy.
      - id: mypy
        name: mypy
        description: "Run 'mypy' for Python linting"
        entry: mypy
        language: python
        args: [--strict, --ignore-missing-imports]
        require_serial: true
        # Add types package list
        additional_dependencies: []
        minimum_pre_commit_version: '2.9.2'

Ruffのフォーマッターはv0.1.2で以降でしか動かないので
バージョン指定にご注意ください。

pyproject.toml

Ruff・mypyの具体的な設定を記述します。
下記は私の環境の設定例です。

[tool.mypy]
strict = true
ignore_missing_imports = true

[tool.ruff]
select = [
    "F", # pyflakes
    "E", # pycodestyle
    "W", # pycodestyle warnings
    "I", # isort
    "D", # pydocstyle
]
ignore = []
# 1行の最大文字数
line-length = 88

extend-ignore = [
    "D105", # undocumented-magic-method
    "D107", # undocumented-public-init
    "D205", # blank-line-after-summary
    "D415" # ends-in-punctuation
]

[tool.ruff.lint.pydocstyle]
# docstringはgoogle style
convention = "google"

[tool.ruff.per-file-ignores]
# 個別設定
# __init__.pyは未使用インポートを許容
"__init__.py" = ["F401"]

各設定は公式ドキュメントを参考にすると良いと思います

■ Ruff

■ mypy

mypyは設定項目が多いのでコマンドラインから逆引きで見るのが良いかも

.vscode/settings.json

vscodeの設定です。

{
  // ルーラー (参考)
  "editor.rulers": [80, 88],

  "[python]": {
    "editor.codeActionsOnSave": {
      // isortの並び替えをRuffで実行
      "source.organizeImports": true
    },
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.formatOnSave": true
  },
  // pipでインストールした場合は下記を項目指定。
  // "ruff.path": ["${workspaceFolder}/.venv/bin/ruff"],
  // "mypy-type-checker.path": ["${workspaceFolder}/.venv/bin/mypy"]
}

pre-commitを使うために...

pre-commitは設定ファイルを置いただけでは機能しません。
コミット時に検証させるには以下コマンドを実行します。

pre-commit install
# pre-commit installed at .git/hooks/pre-commit

これでOKです。

検証

では早速検証してみましょう。

一例として、Ruffのリンターの機能確認をしてみます。
以下スクリプトを準備します。

"""サンプル"""
import os
from datetime import datetime

print(datetime.now())

上記の場合、import osが使用されていないのにインポートされています。

このファイルをコミットしてみます

git add main.py

git commit -m "add: main.py"
[INFO] Stashing unstaged files to ${HOME}/.cache/pre-commit/patch1700351265-106434.
ruff.....................................................................Failed
- hook id: ruff
- files were modified by this hook

Found 1 error (1 fixed, 0 remaining).

ruff-format..............................................................Passed
mypy.....................................................................Passed
[INFO] Restored changes from ${HOME}/.cache/pre-commit/patch1700351265-106434.

エラーが起きてコミットできませんでした。

Ruffはリンター引数に--fixを追加すると、正しいコードに出来そうときは
修正したコードを作成してくれます。

.pre-commit-config.yaml下記部分です。

- id: ruff
    # 他の項目...
    # --fix: enable lint fixes
    args: [--fix]
diff --git a/main.py b/main.py
index 7f26ecb..b548b8c 100644
--- a/main.py
+++ b/main.py
@@ -1,5 +1,4 @@
 """Top"""
-import os
 from datetime import datetime
 
 print(datetime.now())

先程ステージにあげたファイルとリンターを通して出てきたファイルの差分が確認出来ました。
差分のファイルをステージしてコミットしてみます。

$git add main.py
$git commit -m "add: main.py"
[INFO] Stashing unstaged files to ${HOME}/.cache/pre-commit/patch1700351634-107442.
ruff.....................................................................Passed
ruff-format..............................................................Passed
mypy.....................................................................Passed
[INFO] Restored changes from ${HOME}/.cache/pre-commit/patch1700351634-107442.
[main 63cab02] add: main.py
 1 file changed, 4 insertions(+

リンターを無事パスし、コミットに成功。
main.pyはコード品質が保たれたコードになりました。

人力ではなくシステム側からコード品質を保たせよう

pre-commitを使用して、コミット時にコード品質を保つ方法を紹介しました。
リモートブランチをキレイに保てそうで、開発モチベーションがあがりそうです。

紹介したリンター・フォーマッターの他に
スペルチェックもあると良いと思いました。

参考になれば幸いです。

参考サイト一覧

Discussion