nikkie-ftnextの日記

イベントレポートや読書メモを発信

GitHub ActionsでPythonのコードを整える

はじめに

聞いて聞いて!

isortって「I sort」(私が並べる)なんだよ! nikkieです。

タイトルの通り、GitHub ActionsでPythonのコードにblackisortをかけてコミットを作りGitHubリポジトリにプッシュする方法を調べました。

目次

背景

GitHubリポジトリで発生したイベントを契機に処理を実行できるGitHub Actions、便利ですよね。
私がよく設定するのはテストコードの実行なのですが、ふと「PEP 8に反したコードであれば、GitHub Actionsの処理の中で、しれっと直してしまえばいいのではないか」と思い付きました。

1人でPythonを書いている分にはPEP 8に反したコードをGitHubにプッシュすることはめったにありません1
複数人の開発シーンになると、PEP 8に反したコードがリポジトリにプッシュされることも(私の経験上)起こりえます(我々愚かな人類の"気をつける"には限界がありますからね)。
このあたりは、保存時にblackisortで整形するように全員のエディタを設定したり、pre-commitを導入したりといった仕組み化で解決できると思いますが、今回はCI側でしれっと整形するというアイデアを実装してみました。

CIツールの中でも手軽だと感じているGitHub Actionsでまず試してみました。
やり方(コマンド)を展開することで、GitHub Actions以外のCIツールでも設定できると思います。

結論:設定ファイルの例

このActionの動きは次のようになります:

  1. リポジトリにプッシュされたコードがisortで整形する必要があれば、整形してコミットを作りプッシュする
  2. また、blackで整形する必要があれば、整形してコミットを作りプッシュする

f:id:nikkie-ftnext:20220103231320p:plain

「整形する必要があれば、整形してコミットを作りプッシュする」の実装

blackの場合を例にすると、実装は次のようになります。
※単体でも動くように取り出したので、結論のyamlと比べると余計な処理が入っています

black --check --diff . || black -v .
git config --global user.name $GITHUB_ACTOR
git config --global user.email $GITHUB_ACTOR@users.noreply.github.com
git commit -am "[style] Format Python code with black in GitHub Actions"
git push

順番に見ていきましょう。

bash|| (OR) 演算子

「blackで整形する必要がある場合は整形する」は||演算子で表せます。

A、Bをコマンドとしたとき、A || B

  • コマンドAの終了コードが0のとき、コマンドBは実行されない(短絡評価
  • コマンドAの終了コードが0以外のとき、コマンドBを実行

という動作をします2

「blackで整形する必要があるか」の確認は、--checkオプションで実装できます。

  • 整形する必要なし:終了コードは0
  • 整形する必要あり:終了コードは0以外

以上から、A || Bを以下のように書けばいいですね。

  • コマンドAblack --check .
  • コマンドBblack .

整形したらどうなるかを示すために--checkと一緒に--diffオプションも指定しています。

整形しないときもコミットを作ってプッシュすると実装

「blackで整形する必要がある場合は、整形だけでなくコミットを作りプッシュまで行う」ように実装する案も浮かびます。
ですが、今回は整形する必要の有無によらず、コミットを作りプッシュするコマンドを実行します。

これは以下の理由によります:

  • ワーキングツリーがclean(ファイルに差分がない)のときにgit commitしてもエラー終了しない
  • リモートリポジトリに先行するコミットがないためになにもgit pushできなくてもエラー終了しない

分岐はblack --check --diff . || black -v .の部分だけと最小限にし、整形してもしていなくても常にコミットを作りプッシュするようにしました。

この実装には、以下のIssueのコメントを参考にしています。
https://github.com/actions/toolkit/issues/908#issuecomment-919609272

you can use black --check . as a condition

git remote set-urlは要らない?

GitHub Actionsでコミットを作り、プッシュする方法を調べると、git remote set-urlというコマンドも登場します。

git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY

この secrets.GITHUB_TOKEN に何を設定するかがよく分からず3調べていたところ、actions/checkout@v2の効能により、このコマンドが不要になることが分かりました。

Version 2 of checkout resolves the detached HEAD state issue and simplifies pushing to origin.

git configで設定する情報

コミットを作るのに必要なuserのnameとemailを設定します。

環境変数GITHUB_ACTORには、ワークフローを開始した人かアプリの名前4が入るそうです。
これを使うと、nikkieがコミットをプッシュしてワークフローを起動したとき、ワークフローが作るコミットもnikkieが作ったことになります

また、email@users.noreply.github.comの部分ですが、メールアドレスをプライベートにする仕組みでした。
GitHub上で有効にし、さらにローカルのリポジトリにも設定すると、メールアドレスを隠せるそうです5(今回初めて知りました!)

git configを1つのstepに切り出す

isortで整形するstepでもblackで整形するstepでも、コミット前にgit configで設定するのはDRYじゃないですよね。
これはGitHub Actionsの性質を使ってDRYにできました!(ここも「聞いて聞いて!」)

1つのジョブで実行中のすべてのステップは同じランナー上で実行されるので、ステップ間でファイルシステムを通じて情報をやりとりすることができます。(Software Design 2020年10月号 (Kindle の位置No.1934-1935))

git configコマンドで一度だけuserのnameとemailを設定すれば、その情報はファイルシステムに書き込まれているので、後続のステップですぐにgit commitできます。

blackisortの引数の指定ーpyproject.toml、便利!

blackisortに渡す引数もありますよね。

  • black -l 79 .(1行あたりの文字数を指定)
  • isort --profile black .(blackと競合しないように指定)

整形の有無のチェックでも整形時も引数を指定するのはDRYじゃないと感じます。

これをDRYにする方法としてpyproject.tomlに引数を書くという方法がありました!

[tool.black]
line-length = 79

[tool.isort]
profile = "black"

リポジトリルートのpyproject.tomlを見てblackisortが動くので、YAMLファイルのコマンドの記載がスッキリしました。

終わりに(次回予告?)

色々調べながら「Pythonのコードを整える」Actionをゼロから組み上げました!
ですが、GitHub Actionsでコードを整えるニーズって色んな人が感じていそうですよね。
実際、Actionを公開している方もいます。

  • psf/black@stable6
  • isort/isort-action

こういったActionを組合せても実現できたのですが、1箇所どうしても使いづらい点が解消できず、ゼロから組み上げることにしました。
試行錯誤の過程はまたの機会にアウトプットします。


  1. エディタの保存をトリガーにblackisortで整形するように設定しています。VSCodeの場合:https://code.visualstudio.com/docs/python/editing#_formatting

  2. シェルについては Shell Tools and Scripting · the missing semester of your cs education が最近のオススメです。

  3. Automatic token authentication - GitHub Docsによると設定は不要のようです(どうりで情報が出てこなかったわけだ)

  4. 「The name of the person or app that initiated the workflow.」ref: https://docs.github.com/ja/actions/learn-github-actions/environment-variables#default-environment-variables

  5. Setting your commit email address - GitHub Docs

  6. Blackのドキュメントで案内されています。GitHub Actions integration — Black 21.12b0 documentation