GitHub Actionsでpush時にPythonコードを自動整形&自動コミット
はじめに
この記事は ZOZO #5 Advent Calendar 2022 7日目の記事になります。
やること3行
- GitHubにPythonのコードをpushすると自動整形&コミットするCIを作成
- CIについて細かく解説
- GitHub Actionsで実際に動作
前提
- GitHubのリポジトリでコード管理している
- CI/CDはPull Requestで動作する
自動整形&コミットするGitHub Actionsの設定
GitHub Actionsの設定は以下の通りです。
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
を設定することで、個別に対象フォルダを指定できます。
...省略...
jobs:
formatter:
name: formatter
runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./src
strategy:
matrix:
...省略...
結果
以下のコードに対して、GitHub Actionsを実際に実行してみます。
Pull Request作成後、GitHub Actionsが実行されたことを確認しました。
コード
適当に過去に自分が書いた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の並びや、関数前後の空行、演算子前後のスペース、ダブルクオーテーションの利用などが主に修正された形です。
+ 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"}
Discussion