少し前、ChatGPTにPythonプロジェクトで必要な構成を質問した際、pre-commitの導入を推奨されたことがあり、気になっていたので、今回試してみようと考えました。
pre-commitを導入して試すのは、次のGitHubリポジトリです。
https://github.com/ojichiku/public-python-samples

このリポジトリは、サブフォルダに複数のツールやプロジェクトを管理しているモノレポ構成になっており、pre-commitを導入しようとした場合、いろいろな障害が発生して意外と時間がかかりました。

public-python-samples/
└── samples
  ├── csvfilter-cli          # CSVフィルターツール
  ├── file-renamer-cli       # ファイルリネームツール
  ├── logfilter-cli          # CSVフィルターツール
  └── password-generator-cli # パスワード生成ツール

この記事では、実際に経験した手順をもとに、pre-commitとuv、pytestを組み合わせた環境構築方法を紹介します。
この記事の内容は2025年12月に確認しています。

この記事で分かること

  • pre-commitとは?
  • pre-commitを使う際の注意点
  • pre-commitの設定
  • pre-commitの実行・トラブルシューティング
  • コミット時のトラブルシューティング

動作環境

今回、私が使った環境は次の通りです。

  • Windows WSL
  • Python3.13
  • GitHubリポジトリ
  • uv

pre-commitとは何か

pre-commitは、Gitリポジトリでコミットする前、自動的に処理を実行する仕組みであり、コード品質を高めることができます。
自動的に実行できる処理として、Pythonのコード整形やLintチェック、pytestによるテスト実行などがあります。

pre-commitの基本や公式の設定方法を確認したい場合は、次の公式サイトが参考になります。
英語ですが構成が分かりやすく、フックの一覧やベストプラクティスがまとまっています。

pre-commit公式サイト
https://pre-commit.com/

GitHubリポジトリ
https://github.com/pre-commit/pre-commit

pre-commitを使う際の注意点

モノレポ構成では、次のようにサブフォルダごとに独立したPythonプロジェクトを置きます。

public-python-samples/
└── samples
  ├── csvfilter-cli          # CSVフィルターツール
  ├── file-renamer-cli       # ファイルリネームツール
  ├── logfilter-cli          # CSVフィルターツール
  └── password-generator-cli # パスワード生成ツール

最初はサブディレクトリごとにpre-commitを入れればよいと思っていましたが、ChatGPTに相談したところ、pre-commitはGitフックを利用する仕組みのため、リポジトリ単位で動作すると回答がありました。
そのため、pre-commitはリポジトリのルートディレクトリにまとめて設定することにしました。

pre-commitの設定

リポジトリのルートディレクトリにpre-commitを入れるため、次のコマンドでPythonプロジェクトを作成します。

uv init
uv add pre-commit pytest

次のようにmain.py、pyproject.tomlが生成されるので、main.pyを削除します。

次にルートディレクトリに.pre-commit-config.yamlを作成して次のように設定します。
この設定では、blackやruffで整形とLintを行い、pytestによるテスト実行を行います。

repos:
  # --- Black (formatter) ---
  - repo: https://github.com/psf/black
    rev: 24.4.2
    hooks:
      - id: black
        language_version: python3

  # --- Ruff (linter & code fixer) ---
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.6.1
    hooks:
      - id: ruff
        args: ["--fix"]

  # --- Local pytest hook ---
  - repo: local
    hooks:
      - id: pytest
        name: Run pytest
        entry: uv run pytest
        language: system
        types: [python]
        pass_filenames: false

GitHubリポジトリ上のファイルは次のリンクを見てください。
https://github.com/ojichiku/public-python-samples/blob/main/.pre-commit-config.yaml

設定後は次のコマンドでGitフックに登録しました。

uv run pre-commit install

これでコミット時にblack、ruff、pytestが自動で動作する環境の準備は完了です。

pre-commitの実行

pre-commitはPythonファイルの変更があった場合に自動で実行される仕組みです。
設定ファイルだけ変更したときなど、自動実行されない場合は強制的に動かす必要があります。
強制的に動かす場合、次のコマンドで実行します。

uv run pre-commit run --all-files

実行したところ、次のModuleNotFoundErrorが発生したので、ChatGPTに原因と解決策の提示を依頼しました。

==================================== ERRORS ====================================
___________ ERROR collecting samples/csvfilter-cli/tests/test_cli.py ___________
ImportError while importing test module '/home/xxxx/project/public-python-samples/samples/csvfilter-cli/tests/test_cli.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
../../.local/share/uv/python/cpython-3.13.9-linux-x86_64-gnu/lib/python3.13/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
samples/csvfilter-cli/tests/test_cli.py:7: in <module>
    from csvfilter_cli import cli
E   ModuleNotFoundError: No module named 'csvfilter_cli'

ChatGPTから次のような回答がありました。

  • サブフォルダにPythonを格納する構成の場合、pytestがパスを見つけられず、テストが失敗する
  • ルートディレクトリのpyproject.tomlにPYTHONPATHを追加する必要がある。

そこで、次のようにpyproject.tomlを設定を追加したところ、ModuleNotFoundErrorは解消しました。

[tool.pytest.ini_options]
pythonpath = [
  "samples/csvfilter-cli/src",
  "samples/file-renamer-cli/src",
  "samples/logfilter-cli/src",
  "samples/password-generator-cli",
]
testpaths = [
  "samples/csvfilter-cli/tests",
  "samples/file-renamer-cli/tests",
  "samples/logfilter-cli/tests",
]

ModuleNotFoundErrorは解消したので、pre-commitを強制的に実行しところ、次のようにエラーが発生してテストに失敗しました。

==================================== ERRORS ====================================
___________ ERROR collecting samples/logfilter-cli/tests/test_cli.py ___________
import file mismatch:
imported module 'test_cli' has this __file__ attribute:
  /home/xxxx/project/public-python-samples/samples/csvfilter-cli/tests/test_cli.py
which is not the same as the test file we want to collect:
  /home/xxxx/project/public-python-samples/samples/logfilter-cli/tests/test_cli.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules

ChatGPTに聞いたところ、test_cli.pyのように複数のサブプロジェクトで同名のテストファイルが存在すると、pytestが誤ったモジュールを読み込むことが原因であると回答がありました。
そのため、テストファイル名にtest_プロジェクト名_xxx.pyのようにユニークにすることで回避しました。

エラー修正後、pre-commitを強制的に実行したところ、次のようにすべて正常に通りました。

コミット時の問題

pre-commitは正常に通ったのですが、コミット時に次のエラーが発生しました。

setlocale: LC_ALL: cannot change locale (en_US.UTF-8)

今まで発生しなかったエラーのため、ChatGPTに相談してみました。
ChatGPTよりWSLでen_US.UTF-8がないことが原因であり、次のコマンドを実行するように提案がありました。

sudo locale-gen en_US.UTF-8
sudo update-locale

このコマンドを実行することで問題は解消し、無事にコミットができました。

まとめ

Pythonのモノレポ環境にpre-commitを導入するときは、設定の置き場所やpytestの動作に注意が必要です。
今回の手順では、pre-commitをルートディレクトリで管理し、pytestのimport問題やテスト名衝突を解消することで環境をつくることができました。
モノレポ構成は便利ですが、設定が複雑になりやすく、環境を作るのに少し時間がかかりました。
pre-commitで自動チェックができるので、今後、コード品質を向上させた開発ができると思います。