Ruffとpre-commitで安全なPython開発
はじめに
Ruffは、Pythonコードの静的解析とフォーマットを高速に行うツールです。
pre-commitは、Gitのコミット前に自動でコードのチェックや整形を実行するツールです。
この2つを組み合わせることで、コミット前に自動で静的解析とフォーマットを実行できるようになります。
GitHub ActionsなどのCIで実行するよりも、早い段階で問題を検出・修正できるため、AIエージェント開発にも適しています。
事前準備
uvのインストール
こちらを参照してください
uvの初期化
uv init
pyproject.toml, main.pyなどが作成されます
Ruffによる静的解析とフォーマット
Ruffのインストール
Ruffを開発用依存として追加します
uv add --dev ruff
Ruffの動作確認のため、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
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).
I001とF401, F841を検知しました
--fixオプションをつけると、自動修正も行います
uv run ruff check --fix
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).
I001とF401が修正されましたが、まだF841は検知されています
-import os # 未使用のインポート
def main():
print("Hello from pre-commit-demo!")
--unsafe-fixesオプションを付けると、安全でない修正も行えます
uv run ruff check --fix --unsafe-fixes
Found 1 error (1 fixed, 0 remaining).
F841も修正されました
print("Hello from pre-commit-demo!")
x = 1 # 余分なスペース
y=2 # 演算子の周りにスペースがない
- z = 3 # 未使用の変数
print(f"{x}, {y}")
どのルールが安全か・修正するか、は変更することができます
Ruffのフォーマット
ruff formatコマンドでコードを整形できます
uv run ruff format
1 file reformatted
余計なスペースがフォーマットされました
-
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 --fixとruff-formatを設定します
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
pre-commit installed at .git/hooks/pre-commit
pre-commitの動作確認のため、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違反のコードを追加"
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の静的解析とフォーマットが自動実行されました
-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の動作後に再コミット"
ruff check...............................................................Passed
ruff format..............................................................Passed
[test/ruff ce9044d] test: ruffの動作後に再コミット
1 file changed, 3 insertions(+)
コミットが通りました
def main():
print("Hello from pre-commit-demo!")
x = 1 # 余分なスペース
y = 2 # 演算子の周りにスペースがない
print(f"{x}, {y}")
if __name__ == "__main__":
main()
参考文献
Discussion