🐍

GitHub Actionsでpush時にPythonコードを自動整形&自動コミット

2022/12/07に公開

はじめに

この記事は ZOZO #5 Advent Calendar 2022 7日目の記事になります。

やること3行

  • GitHubにPythonのコードをpushすると自動整形&コミットするCIを作成
  • CIについて細かく解説
  • GitHub Actionsで実際に動作

前提

  • GitHubのリポジトリでコード管理している
  • CI/CDはPull Requestで動作する

自動整形&コミットするGitHub Actionsの設定

GitHub Actionsの設定は以下の通りです。

sample.yml
name: Format code

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  formatter:
    name: formatter
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.11.0]
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          ref: ${{ github.head_ref }}
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install Dependencies
        run: |
          python -m pip install --upgrade pip
          pip install autoflake black isort
      - name: autoflake
        run: autoflake -r .
      - name: black
        run: black .
      - name: isort
        run: isort .
      - name: Auto Commit
        uses: stefanzweifel/git-auto-commit-action@v4
        with:
          commit_message: Apply Code Formatter Change

以下から詳細を説明します。

ワークフローのトリガー

ここでは、ワークフローのトリガーとなるイベントを設定しています。

on:
  pull_request:
    types: [opened, synchronize, reopened]

今回はPull RequestでCIを動作させたかったので、pull_requestを指定しています。
また、typesを指定して、より細かい条件を設定しています。

  • synchronize: Pull Requestが更新されたとき
  • opend&reopened: Pull Requestがオープン、再オープンしたとき

ジョブの設定

次に、CIのジョブを設定しています。

jobs:
  formatter:
    name: formatter
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.11.0]

formatterは実行するジョブのキーで、name: formatterはGitHub UIに表示するジョブの名前を表します。

runs-onはジョブを実行するマシンの種類を定義しています。今回はubuntu-latestを指定しています。

strategy.matrixでは、ジョブ内で利用する変数を定義しています。ここでは変数python-versionにPythonnのバージョンを表す"3.11.0"を設定しています。

この変数は後ほど使います。

ステップ

ジョブで実行したいタスクをstepsでタスクごとに設定していきます。

Pull RequestのHEADブランチにチェックアウト

    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          ref: ${{ github.head_ref }}

タスク名は、name: Checkoutのように定義します。

ジョブで利用するリポジトリをチェックアウトするには actions/checkout というアクションが提供されています。usesキーワードを使いアクションを実行することができます。

withキーワードでアクションに対して設定を入力できます。

このままだと、Pull RequestのHEADブランチと異なるブランチにチェックアウトしてしまうので、withキーワードにref: ${{ github.head_ref }}を設定しています。

${{github.head_ref}}はワークフローを実行するブランチを意味しています。

Python環境の構築

次のタスクは、Pythonの実行環境を構築するためにactions/setup-pythonアクションを利用して、セットアップを行っています。

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}

withキーワードで先程strategy.matrixで定義した変数を利用している。

Pythonコードの自動整形

次に、runキーワードでコマンドを実行していきます。

      - name: Install Dependencies
        run: |
          python -m pip install --upgrade pip
          pip install autoflake black isort
      - name: autoflake
        run: autoflake -r .
      - name: black
        run: black .
      - name: isort
        run: isort .

ここでは、pip install autoflake black isortで自動整形に必要なパッケージをインストールしています。

  • autoflake: 未使用のインポートや未使用の変数を削除する
  • black: PEP8に則ってPythonのコードを自動整形する
  • isort: PEP8で推奨される並び順に則ったimportのソートをしてくれる

変更をコミット

前のタスクで整形したコードをコミットします。

      - name: Auto Commit
        uses: stefanzweifel/git-auto-commit-action@v4
        with:
          commit_message: Apply Code Formatter Change

GitHub Actionsでコミットする方法はいくつかありますが、今回はstefanzweifel/git-auto-commit-actionアクションを利用しています。

また、withキーワードでコミットメッセージを指定しています。

整形対象のフォルダを指定

対象のフォルダを指定したい指定したい場合、例えば、srcフォルダ直下にしかPythonコードがないような場合は、以下のようにworking-directoryを設定することで、個別に対象フォルダを指定できます。

sample.yml
...省略...

jobs:
  formatter:
    name: formatter
    runs-on: ubuntu-latest
+   defaults:
+     run:
+       working-directory: ./src
    strategy:
      matrix:
...省略...

結果

以下のコードに対して、GitHub Actionsを実際に実行してみます。

Pull Request作成後、GitHub Actionsが実行されたことを確認しました。
https://github.com/sattosan/sample_auto_format_python/pull/1

コード

適当に過去に自分が書いたPythonコードを用意しました。

import config
import spacy
import utils
from tqdm import tqdm
import collections
import time



# 日本語辞書の読み込み
nlp = spacy.load('ja_ginza')
# 品詞カウント用
pos_counter = collections.Counter()
# 品詞の出現回数をカウント
def count_pos_of_token(keyword):
    try:
        token_pos_pair = ''
        # 形態素解析
        nlp_keyword=nlp(keyword)
        for sent in nlp_keyword.sents:
            for token in sent:
                token_pos_pair+=f'{token.text}:{token.pos_}|'
                pos_counter[token.pos_]+=1
        return {'keyword': keyword, 'token_pos': token_pos_pair}
    except Exception:
        return {'keyword': keyword, 'token_pos': 'None'}

整形後のコード

整形後、以下のような差分がコミットされました。

importの並びや、関数前後の空行、演算子前後のスペース、ダブルクオーテーションの利用などが主に修正された形です。

python
+ import collections
+ import time
+
  import config
  import spacy
  import utils
  from tqdm import tqdm
- import collections
- import time
-
-

  # 日本語辞書の読み込み
- nlp = spacy.load('ja_ginza')
+ nlp = spacy.load("ja_ginza")
  # 品詞カウント用
  pos_counter = collections.Counter()
  # 品詞の出現回数をカウント
  def count_pos_of_token(keyword):
      try:
-         token_pos_pair = ''
+         token_pos_pair = ""
          # 形態素解析
-         nlp_keyword=nlp(keyword)
+         nlp_keyword = nlp(keyword)
          for sent in nlp_keyword.sents:
              for token in sent:
-                 token_pos_pair+=f'{token.text}:{token.pos_}|'
-                 pos_counter[token.pos_]+=1
-         return {'keyword': keyword, 'token_pos': token_pos_pair}
+                 token_pos_pair += f"{token.text}:{token.pos_}|"
+                 pos_counter[token.pos_] += 1
+         return {"keyword": keyword, "token_pos": token_pos_pair}
      except Exception:
-         return {'keyword': keyword, 'token_pos': 'None'}
+         return {"keyword": keyword, "token_pos": "None"}

https://github.com/sattosan/sample_auto_format_python/pull/1/commits/ab568c1c860453b72457512651932779737f1bb5

参考サイト

株式会社ZOZO

Discussion