Pythonで安全にコーディングしようとすると、リンタ (ex. flake8) やフォーマッタ (ex. black) 、型チェッカ (ex. mypy) など、コミット前に実行するコマンドが増えていきます。
今回は、コミット時にコマンドを自動的にフックするPythonのツールとしてpre-commitを紹介します。
- 公式サイト: https://pre-commit.com/
- GitHub: https://github.com/pre-commit/pre-commit
- PyPI: https://pypi.org/project/pre-commit/
インストール
pre-commitはpipでインストールできます。
$ python --version Python 3.8.5 $ pip install pre-commit $ pre-commit --version pre-commit 2.7.1 $ pre-commit help usage: pre-commit [-h] [-V] {autoupdate,clean,hook-impl,gc,init-templatedir,install,install-hooks,migrate-config,run,sample-config,try-repo,uninstall,help} ... ...
設定
次に設定ファイルを追加します。sample-configサブコマンドで生成できます。
- ファイル名は
.pre-commit-config.yaml
にする必要があります
$ pre-commit sample-config > .pre-commit-config.yaml
.pre-commit-config.yamlは以下のような内容となってます。
処理を記述したPythonファイルがリモートレポジトリ (repo
) にアップロードされており、コミット時には hooks
以下に id
で列挙された処理を順に実行する、という設定となります。
repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files
https://github.com/pre-commit/pre-commit-hooks 以外にも、ツールの製作者がpre-commit用のフックを用意していることもあります。例えばblackを実行する場合は以下のように記述します (blackのREADMEを参照) 。
- フックはレポジトリ直下の
.pre-commit-hooks.yaml
に定義されています (id
の値もこのファイルで確認できます) - 主要なフックのリストは https://pre-commit.com/hooks.html で列挙されています
- もちろん自分で作ることもできます (参考)
repos: - repo: https://github.com/pre-commit/pre-commit-hooks ... - repo: https://github.com/psf/black rev: 19.10b0 hooks: - id: black language_version: python3
フックの実行
サンプルとして、flake8とblackをフックに登録した設定ファイルを用意します。
- repo: https://github.com/psf/black rev: 19.10b0 hooks: - id: black language_version: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.4.0 hooks: - id: flake8
pre-commitでフックする場合、設定ファイルを作成後にinstallを実行します。これでスクリプトが生成されていますので、設定ファイルの変更都度実行する必要があります。
$ pre-commit install pre-commit installed at .git/hooks/pre-commit $ cat .git/hooks/pre-commit #!/usr/bin/env python3.8 # File generated by pre-commit: https://pre-commit.com # ID: 138fd403232d2ddd5efb44317e38bf03 # ... 省略 ... if sys.platform == 'win32': # https://bugs.python.org/issue19124 import subprocess if sys.version_info < (3, 7): # https://bugs.python.org/issue25942 raise SystemExit(subprocess.Popen(CMD).wait()) else: raise SystemExit(subprocess.call(CMD)) else: os.execvp(CMD[0], CMD)
試しに以下のmain.pyファイルをコミットしてみます。
def print_hi(name, age): profile = f'{name} ({str(age)})' print(f'Hi, {name}') if __name__ == '__main__': print_hi('ohke', 32)
これでコミットしてみますが、エラーとなって失敗します。flake8ではprofileを使っていないことを指摘され、blackではリフォーマットされていることがわかります。
- コードを変更したファイルのみがチェックの対象となります
$ git commit -m "test" Flake8...................................................................Failed - hook id: flake8 - exit code: 1 main.py:2:5: F841 local variable 'profile' is assigned to but never used black....................................................................Failed - hook id: black - files were modified by this hook reformatted main.py All done! ✨ 🍰 ✨ 1 file reformatted.
コードを修正して改めてコミットすると、無事にflake8とblackにパスして、コミットに成功します。
$ git commit -m "test" Flake8...................................................................Passed black....................................................................Passed [master (root-commit) 9ec7bc8] test 1 file changed, 7 insertions(+) create mode 100644 main.py
まとめ
今回はpre-commitについて紹介しました。
pre-commit自体はPythonのツールですが、フックは他の言語でも実装可能ですので、適用範囲は広いツールかと思います。