teaser

自作のライブラリをリリースするときに、ローカルでビルドして、TestPyPIにアップロードして確認して、PyPIにアップロードして、最後にタグ切ってリリースして・・・ 面倒なのでGitHub Actionsでデプロイできるようにしました。

この記事で目指すこと

リモートにタグをpushした際に、自動でPyPIにパッケージをアップロードし、GitHubにリリースを作成する。

ローカルからtagをpushすると、、、

git tag vX.X.X
git push origin vX.X.X

以下順番で自動的にデプロイされる

  1. testPyPIへのアップロード
  2. PyPIへのアップロード
  3. GitHub上にリリースを追加しソースコードを配布

下記画像のようなワークフローを作成します。

Screenshot 2024-08-09 at 17.18.42.png

手順

準備

注意: パッケージングツールの設定ファイルとして setup.py ではなく、pyproject.toml を使用します。PEP-518により、モダンなPythonプロジェクトではpyproject.tomlの使用が推奨されています。

Pythonのパッケージを用意してPyPIとTestPyPIのプロジェクトとしてアップロードします。 すでに自作のライブラリがPyPI上にある場合は飛ばしてください。

下記のチュートリアルに従って、サンプルパッケージをTestPyPIとPyPIにアップロードします。 https://packaging.python.org/ja/latest/tutorials/packaging-projects/#creating-a-license

GitHub Actionsでワークフローファイルの作成

リポジトリ> Actions> set up a workflow yourself> 任意の名前でyamlファイル(今回はmain.yaml)を作成しcommit。空ファイルでOKです。

PyPIでPublishingの設定

従来、GitHub ActionsからPyPIにアップロードをするためにはPyPIでAPIトークンを作成し、GitHub側でそのトークンをSecretとして保持する必要がありました。

しかしPublishingを使用することで、事前に設定してある特定のサービス(および特定のユーザー、リポジトリなど)との接続の際に、一時的なトークンを発行、認証までを自動で行うことができます!

設定の簡単ですし、トークンの有効期限が短いのでセキュリティ的にもPublishingを使用する方が良さそうです。

PyPIのプロジェクトページからPublishing> Add a new publisherを選択します。

https://pypi.org/manage/project/[プロジェクト名]/settings/publishing/

以下画像のように記入し、Addします。

Screenshot 2024-08-09 at 17.54.30.png

TestPyPIを開き、同じようにPublisherを作成します。 Environment nameはPyPIとは別のものにします。

Screenshot 2024-08-09 at 18.00.54.png

GitHub Actionsでワークフローの設定

先ほど作成したyamlにワークフローを書いていきます。

1. タグがpushされたときにワークフローを開始する

name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI

on:
  push:
    tags:
      - 'v*.*.*'

2. ビルド

ビルドするためにはpyproject.tomlがリポジトリ上に存在する必要がありますので.gitignoreに含めないでください。

jobs:
  build:
    name: Build distribution 📦
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4 # コードを取得
    - name: Set up Python
      uses: actions/setup-python@v5 # Python環境のセットアップ
      with:
        python-version: "3.x"
    - name: Install pypa/build # ビルドツールをインストール
      run: >-
        python3 -m
        pip install
        build
        --user
    - name: Build a binary wheel and a source tarball
      run: python3 -m build # パッケージをビルド
    - name: Store the distribution packages # ビルド成果物をpython-package-distributionsという名前でdistディレクトリに一時的に保存
      uses: actions/upload-artifact@v4
      with:
        name: python-package-distributions
        path: dist/

3. TestPyPIへの公開

  publish-to-testpypi:
    name: Publish Python 🐍 distribution 📦 to TestPyPI
    needs:
    - build # ビルドジョブが完了した場合ジョブを開始する
    runs-on: ubuntu-latest

    environment:
      name: testpypi # Publisherに設定したenvironment nameを入力
      url: https://test.pypi.org/p/example-package-hanaosan0318 # プロジェクトURL

    permissions:
      id-token: write  # Publishingの権限を付与

    steps:
    - name: Download all the dists # 先ほど保存したビルド成果物をダウンロード
      uses: actions/download-artifact@v4
      with:
        name: python-package-distributions
        path: dist/
    - name: Publish distribution 📦 to TestPyPI # TestPyPIへ公開
      uses: pypa/gh-action-pypi-publish@release/v1
      with:
        repository-url: https://test.pypi.org/legacy/

4. PyPIへの公開

  publish-to-pypi:
    name: >-
      Publish Python 🐍 distribution 📦 to PyPI
    needs:
    - publish-to-testpypi # TestPyPIへの公開が完了した場合ジョブを開始する
    runs-on: ubuntu-latest
    environment:
      name: pypi # Publisherに設定したenvironment nameを入力
      url: https://pypi.org/p/example-package-hanaosan0318 # プロジェクトURL
    permissions:
      id-token: write

    steps:
    - name: Download all the dists
      uses: actions/download-artifact@v4
      with:
        name: python-package-distributions
        path: dist/
    - name: Publish distribution 📦 to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1

5. リリースの作成

  github-release:
    name: >-
      Create GitHub Release with source code
    needs:
    - publish-to-pypi # PyPIへの公開が完了した場合ジョブを開始
    runs-on: ubuntu-latest
  
    permissions:
      contents: write # GitHubリリースを作成するための権限
  
    steps:
    - name: Checkout code
      uses: actions/checkout@v4 # コードを取得
  
    - name: Create GitHub Release
      env:
        GITHUB_TOKEN: $ # ワークフローが実行されるたびに自動的に生成される一時的なトークン
      run: >-
        gh release create
        '$'
        --repo '$'
        --notes "Release for version $"

これでワークフローを定義することができました!

yamlファイルの全体は以下リンクから見られます。

https://github.com/hanaosan/ci-cd-practice-python-library/blob/main/.github/workflows/main.yml

デプロイしてみる

pyproject.tomlのversionを今回リリースバージョンに修正してpushしておきます。

[project]
name = "example_package_hanaosan0318"
version = "2.0.3" # リリースバージョンに変更
authors = [
  { name="Example Author", email="author@example.com" },
]

タグを作成しpushします。

git tag v2.0.3
git push origin v2.0.3

PyPIとTestPyPIにパッケージがアップロードされています。

Screenshot 2024-08-09 at 18.43.32.png

またリリースが作成され、ソースコードがzipとtar.gzで配布されていることが確認できるはずです。

Screenshot 2024-08-09 at 18.44.15.png

これでリモートにタグをpushした際に、自動でPyPIにパッケージをアップロードし、GitHubにリリースを作成することができました。

参考