🙄
Pythonプロジェクトでpre-commitを使ったコード品質管理
Pythonプロジェクトでコードの品質を保つために、どのようなツールを使っていますか?
手動でフォーマッターやリンターを実行するのは面倒で、忘れてしまうこともありますよね。
今回は、pre-commit
を使った自動的なコード品質管理の方法を紹介します。Gitコミット前に自動でチェックが実行されるため、品質の高いコードを維持できます。
🚨 問題:手動でのコード品質管理の課題
従来のコード品質管理では、以下のような課題がありました:
- フォーマッターの実行忘れ: 手動で実行するため、忘れがち
- リンターの見落とし: エラーがあってもコミットしてしまう
- チーム間の統一性: 人によって実行するツールが違う
- 時間の無駄: コミット後にCIでエラーが出て修正が必要
- 設定の散在: 各ツールの設定ファイルがバラバラ
🆕 解決策:pre-commitによる自動化
pre-commit
を使うことで、Gitコミット前に自動的にコード品質チェックが実行されます。
pre-commitとは
- Gitフックを簡単に管理するツール
- コミット前に自動でチェックを実行
- 複数のツールを統一的に管理
- 設定が簡単で、チーム全体で共有可能
🛠 方法1:基本的なpre-commitのセットアップ
まずは、Poetryプロジェクトにpre-commitを導入する方法を紹介します。
1. pre-commitのインストール
# Poetryプロジェクトでpre-commitを追加
poetry add --group dev pre-commit
# または直接インストール
pip install pre-commit
2. .pre-commit-config.yamlの作成
プロジェクトのルートに.pre-commit-config.yaml
ファイルを作成します。
repos:
# コードフォーマッター
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
language_version: python3
# import文の整理
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.12.0
hooks:
- id: isort
args: ["--profile", "black"]
# 高速なリンター
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.4.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# 型チェック(時間がかかる場合は後で実行)
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.0
hooks:
- id: mypy
additional_dependencies: [types-requests]
3. pre-commitフックのインストール
# Gitフックをインストール
poetry run pre-commit install
# 既存のファイルに対して実行
poetry run pre-commit run --all-files
🔄 方法2:pyproject.tomlとの連携
pre-commitで使用するツールの設定をpyproject.toml
に集約します。
1. 完全なpyproject.toml設定
[tool.poetry]
name = "my-python-project"
version = "0.1.0"
description = "A Python project with pre-commit"
authors = ["Your Name <your.email@example.com>"]
[tool.poetry.dependencies]
python = "^3.8"
requests = "^2.31.0"
[tool.poetry.group.dev.dependencies]
pre-commit = "^3.5.0"
black = "^24.3.0"
isort = "^5.12.0"
ruff = "^0.4.0"
mypy = "^1.5.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
# Black設定
[tool.black]
line-length = 88
target-version = ['py38']
include = '\.pyi?$'
extend-exclude = '''
/(
# directories
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| build
| dist
)/
'''
# isort設定
[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 88
known_first_party = ["my_python_project"]
# Ruff設定
[tool.ruff]
line-length = 88
target-version = "py38"
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"UP", # pyupgrade
]
ignore = [
"E203", # whitespace before ':'
"W503", # line break before binary operator
"B008", # do not perform function calls in argument defaults
]
[tool.ruff.per-file-ignores]
"__init__.py" = ["F401"]
# MyPy設定
[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
[[tool.mypy.overrides]]
module = [
"requests.*",
"urllib3.*",
]
ignore_missing_imports = true
2. 最適化された.pre-commit-config.yaml
repos:
# コードフォーマッター
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
language_version: python3
# import文の整理
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.12.0
hooks:
- id: isort
args: ["--profile", "black"]
# 高速なリンター(black/isortと競合しない設定)
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.4.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
# 型チェック(時間がかかるため、必要に応じて)
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.0
hooks:
- id: mypy
additional_dependencies: [types-requests]
# 型チェックが重い場合は、手動で実行することも可能
# stages: [manual]
📊 方法3:ツールの比較と選択
Pythonのコード品質ツールには様々な選択肢があります。プロジェクトの規模や要件に応じて選択しましょう。
ツール | 目的 | 速度 | 設定の複雑さ | 推奨度 |
---|---|---|---|---|
black | コード整形 | 高速 | 簡単 | ⭐⭐⭐⭐⭐ |
isort | import整理 | 高速 | 簡単 | ⭐⭐⭐⭐⭐ |
ruff | リント | 超高速 | 中程度 | ⭐⭐⭐⭐⭐ |
flake8 | リント | 中速 | 簡単 | ⭐⭐⭐⭐ |
mypy | 型チェック | 低速 | 複雑 | ⭐⭐⭐⭐ |
pylint | 高機能リント | 低速 | 複雑 | ⭐⭐⭐ |
最小構成(推奨)
repos:
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.4.0
hooks:
- id: ruff
args: [--fix]
完全構成(大規模プロジェクト)
repos:
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.12.0
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.4.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.0
hooks:
- id: mypy
additional_dependencies: [types-requests]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
✅ 方法4:実際の運用事例
実際のプロジェクトでの運用例を紹介します。
1. 基本的なPythonアプリケーション
# src/my_app/main.py
from typing import List, Optional
import requests
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: Optional[str] = None
class UserService:
def __init__(self, api_url: str) -> None:
self.api_url = api_url
def get_users(self) -> List[User]:
"""ユーザー一覧を取得する"""
response = requests.get(f"{self.api_url}/users")
response.raise_for_status()
users_data = response.json()
return [User(**user_data) for user_data in users_data]
def create_user(self, name: str, email: Optional[str] = None) -> User:
"""新しいユーザーを作成する"""
user_data = {"name": name}
if email:
user_data["email"] = email
response = requests.post(f"{self.api_url}/users", json=user_data)
response.raise_for_status()
return User(**response.json())
2. テストコード
# tests/test_main.py
import pytest
from unittest.mock import Mock, patch
from my_app.main import User, UserService
def test_user_creation():
"""ユーザー作成のテスト"""
user = User(id=1, name="Test User", email="test@example.com")
assert user.id == 1
assert user.name == "Test User"
assert user.email == "test@example.com"
def test_user_service_get_users():
"""UserService.get_usersのテスト"""
mock_response = Mock()
mock_response.json.return_value = [
{"id": 1, "name": "User 1"},
{"id": 2, "name": "User 2"},
]
with patch("requests.get", return_value=mock_response):
service = UserService("http://api.example.com")
users = service.get_users()
assert len(users) == 2
assert users[0].name == "User 1"
assert users[1].name == "User 2"
3. 開発ワークフロー
# 1. 新しい機能ブランチを作成
git checkout -b feature/user-management
# 2. コードを書く
# ... コードを編集 ...
# 3. コミットを試行(pre-commitが自動実行される)
git add .
git commit -m "Add user management feature"
# 4. もしpre-commitでエラーが出た場合
# 自動修正されるか、手動で修正して再度コミット
# 5. 手動でpre-commitを実行することも可能
poetry run pre-commit run --all-files
# 6. 特定のフックのみ実行
poetry run pre-commit run black --all-files
poetry run pre-commit run ruff --all-files
🔧 方法5:トラブルシューティング
よくある問題とその解決方法を紹介します。
1. pre-commitが遅い場合
# .pre-commit-config.yaml
repos:
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
# 特定のファイルのみ対象にする
files: \.py$
# 並列実行を有効にする
parallel: true
2. 型チェックが重い場合
# mypyを手動実行に変更
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.5.0
hooks:
- id: mypy
stages: [manual] # 手動実行のみ
3. 特定のファイルを除外する
# .pre-commit-config.yaml
repos:
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
exclude: ^(migrations|generated_files)/
4. CI/CDでの実行
# .github/workflows/pre-commit.yml
name: Pre-commit
on: [push, pull_request]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
- name: Install dependencies
run: poetry install
- name: Run pre-commit
run: poetry run pre-commit run --all-files
🧭 おわりに
pre-commitを使うことで、Pythonプロジェクトのコード品質管理が格段に楽になります。
導入のメリット
- 自動化: 手動での実行が不要
- 一貫性: チーム全体で同じ品質基準
- 効率性: コミット前の早期エラー発見
- 学習効果: 良いコードの書き方を自然に身につける
推奨する導入順序
- 最小構成から始める: black + ruff
- 段階的に追加: isort → mypy
- チームに共有: .pre-commit-config.yamlをコミット
- CI/CDと連携: GitHub Actionsなどで自動実行
次のステップ
- 既存プロジェクトにpre-commitを導入
- チームメンバー全員にセットアップ方法を共有
- CI/CDパイプラインでの自動実行を設定
- 定期的にツールの設定を見直し
pre-commitは最初の設定に少し時間がかかりますが、一度導入すれば開発効率が大幅に向上します。ぜひ試してみてください!
Discussion