Ruffとpre-commitで安全なPython開発

に公開

はじめに

Ruffは、Pythonコードの静的解析とフォーマットを高速に行うツールです。
pre-commitは、Gitのコミット前に自動でコードのチェックや整形を実行するツールです。

この2つを組み合わせることで、コミット前に自動で静的解析とフォーマットを実行できるようになります。
GitHub ActionsなどのCIで実行するよりも、早い段階で問題を検出・修正できるため、AIエージェント開発にも適しています。

事前準備

uvのインストール

こちらを参照してください
https://docs.astral.sh/uv/getting-started/installation/

uvの初期化

uv init

pyproject.toml, main.pyなどが作成されます

Ruffによる静的解析とフォーマット

Ruffのインストール

Ruffを開発用依存として追加します

uv add --dev ruff

Ruffの動作確認のため、main.pyを以下のように編集します

main.py
import os  # 未使用のインポート

def main():
    print("Hello from pre-commit-demo!")
    x    =    1  # 余分なスペース
    y=2          # 演算子の周りにスペースがない
    z = 3        # 未使用の変数
    print(f"{x}, {y}")


if __name__ == "__main__":
    main()

Ruffの静的解析

ruff checkコマンドで静的解析を行います

uv run ruff check
output
main.py:1:1: I001 [*] Import block is un-sorted or un-formatted
  |
1 | import os  # 未使用のインポート
  | ^^^^^^^^^ I001
2 |
3 | def main():
  |
  = help: Organize imports

main.py:1:8: F401 [*] `os` imported but unused
  |
1 | import os  # 未使用のインポート
  |        ^^ F401
2 |
3 | def main():
  |
  = help: Remove unused import: `os`

main.py:7:5: F841 Local variable `z` is assigned to but never used
  |
5 |     x    =    1  # 余分なスペース
6 |     y=2          # 演算子の周りにスペースがない
7 |     z = 3        # 未使用の変数
  |     ^ F841
8 |     print(f"{x}, {y}")
  |
  = help: Remove assignment to unused variable `z`

Found 3 errors.
[*] 2 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).

I001F401, F841を検知しました

--fixオプションをつけると、自動修正も行います

uv run ruff check --fix
output
main.py:6:5: F841 Local variable `z` is assigned to but never used
  |
4 |     x    =    1  # 余分なスペース
5 |     y=2          # 演算子の周りにスペースがない
6 |     z = 3        # 未使用の変数
  |     ^ F841
7 |     print(f"{x}, {y}")
  |
  = help: Remove assignment to unused variable `z`

Found 2 errors (1 fixed, 1 remaining).
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).

I001F401が修正されましたが、まだF841は検知されています

diff main.py
-import os  # 未使用のインポート
 
 def main():
     print("Hello from pre-commit-demo!")

--unsafe-fixesオプションを付けると、安全でない修正も行えます

uv run ruff check --fix --unsafe-fixes
output
Found 1 error (1 fixed, 0 remaining).

F841も修正されました

diff main.py
     print("Hello from pre-commit-demo!")
     x    =    1  # 余分なスペース
     y=2          # 演算子の周りにスペースがない
-    z = 3        # 未使用の変数
     print(f"{x}, {y}")

どのルールが安全か・修正するか、は変更することができます
https://docs.astral.sh/ruff/linter/#fix-safety

Ruffのフォーマット

ruff formatコマンドでコードを整形できます

uv run ruff format
output
1 file reformatted

余計なスペースがフォーマットされました

diff main.py
-
 def main():
     print("Hello from pre-commit-demo!")
-    x    =    1  # 余分なスペース
-    y=2          # 演算子の周りにスペースがない
+    x = 1  # 余分なスペース
+    y = 2  # 演算子の周りにスペースがない
     print(f"{x}, {y}")

pre-commitによるコミット前実行

pre-commitのインストール

uv add --dev pre-commit

pre-commitの設定ファイルを作成します
先ほどのruff check --fixruff-formatを設定します

.pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
  # Ruff version.
  rev: v0.12.4
  hooks:
    # Run the linter.
    - id: ruff-check
      args: [ --fix ]
    # Run the formatter.
    - id: ruff-format

pre-commit installでGitフックに紐づけてcommit前に実行できるようにします

uv run pre-commit install
output
pre-commit installed at .git/hooks/pre-commit

pre-commitの動作確認のため、main.pyを以下のものに戻しておきます

main.py
import os  # 未使用のインポート

def main():
    print("Hello from pre-commit-demo!")
    x    =    1  # 余分なスペース
    y=2          # 演算子の周りにスペースがない
    z = 3        # 未使用の変数
    print(f"{x}, {y}")


if __name__ == "__main__":
    main()

pre-commitの動作確認

実際にコミットをして、pre-commitの実行を確認します

git add main.py && git commit -m "test: ruffの動作確認のためにPEP 8違反のコードを追加"
output
ruff check...............................................................Failed
- hook id: ruff-check
- exit code: 1
- files were modified by this hook

main.py:6:5: F841 Local variable `z` is assigned to but never used
  |
4 |     x    =    1  # 余分なスペース
5 |     y=2          # 演算子の周りにスペースがない
6 |     z = 3        # 未使用の変数
  |     ^ F841
7 |     print(f"{x}, {y}")
  |
  = help: Remove assignment to unused variable `z`

Found 2 errors (1 fixed, 1 remaining).
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).

ruff format..............................................................Failed
- hook id: ruff-format
- files were modified by this hook

1 file reformatted

コミットは通らず、Ruffの静的解析とフォーマットが自動実行されました

diff main.py
-import os  # 未使用のインポート
-
 def main():
     print("Hello from pre-commit-demo!")
-    x    =    1  # 余分なスペース
-    y=2          # 演算子の周りにスペースがない
-    z = 3        # 未使用の変数
+    x = 1  # 余分なスペース
+    y = 2  # 演算子の周りにスペースがない
+    z = 3  # 未使用の変数
     print(f"{x}, {y}")

再コミットをして通ることを確認します
今回は、未使用の変数zを手動削除してから実行します

git add main.py && git commit -m "test: ruffの動作後に再コミット"
output
ruff check...............................................................Passed
ruff format..............................................................Passed
[test/ruff ce9044d] test: ruffの動作後に再コミット
 1 file changed, 3 insertions(+)

コミットが通りました

main.py
def main():
    print("Hello from pre-commit-demo!")
    x = 1  # 余分なスペース
    y = 2  # 演算子の周りにスペースがない
    print(f"{x}, {y}")


if __name__ == "__main__":
    main()

参考文献

https://docs.astral.sh/ruff/
https://pre-commit.com/

Discussion