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のツールですが、フックは他の言語でも実装可能ですので、適用範囲は広いツールかと思います。