返回文章列表

持續佈署自動化Python套件發布

本文介紹如何使用 GitHub Actions 和 Python Semantic Release (PSR) 自動化 Python 套件的持續佈署流程,涵蓋設定 PSR、建立版本、產生變更日誌、發布到 TestPyPI 和 PyPI 等步驟,並提供程式碼範例、圖表說明和詳細解說,幫助讀者快速上手。

軟體工程 DevOps

持續佈署是軟體開發流程中不可或缺的一環,能有效縮短交付週期並提升軟體品質。本文將探討如何利用 GitHub Actions 結合 Python Semantic Release (PSR) 工具,實作 Python 套件版本控制、變更日誌自動產生、以及自動發布至 TestPyPI 與 PyPI 的完整流程。此流程能有效簡化套件發布步驟,減少人工操作並降低錯誤風險,讓開發者更專注於程式碼的開發與維護。

透過設定 PSR,我們可以自動化版本號的更新、變更日誌的生成,以及標籤的建立。在 GitHub Actions 中,我們可以利用 pypa/gh-action-pypi-publish 這個 action 將套件自動發布到 TestPyPI 以及 PyPI。設定 TestPyPI 的主要目的是在正式發布前進行測試,確保套件的安裝與運作正常。整個流程的核心概念是將繁瑣的發布步驟自動化,讓開發者可以更專注於程式碼的開發與維護,並確保每次發布的套件都符合預期。

8.5 設定持續佈署(Continuous Deployment)

在前面的步驟中,我們已經為套件設定了持續整合(CI),以檢查測試是否透過、程式碼覆寫率是否穩定,以及檔案是否仍然能夠成功建置。每當我們對儲存函式庫的主要分支進行推播或提交新的變更時,CI 工作流程就會被觸發。在本文中,我們將進一步設定持續佈署(CD)。如果我們推播到儲存函式庫的變更透過了 CI 檢查,那麼我們希望 CD 工作流程能夠自動執行以下步驟:

  1. 建立 pycounts 套件的新版本。
  2. 建置新的發行版本(sdist 和 wheel)。
  3. 將發行版本上傳到 TestPyPI,並測試套件是否能夠成功安裝。
  4. 將發行版本上傳到 PyPI。

8.5.1 設定

為了設定 CD,我們將在 .github/workflows/ci-cd.yml 工作流程檔案中新增一個名為「cd」的工作。這個工作將在每次更新的程式碼被推播到儲存函式庫的主要分支時觸發套件的佈署。我們只希望在以下條件下執行此工作:

  1. 「ci」工作透過 - 如果套件未透過 CI 檢查,我們不希望佈署新版本。我們可以使用 GitHub Actions 中的 needs 關鍵字來指定此約束。
  2. 程式碼被推播到主要分支 - 當開啟一個提取請求時,我們不希望佈署新版本;只有當提取請求被合併到主要分支或直接推播變更到主要分支時,才會佈署新版本。我們可以使用 GitHub Actions 中的 if 語法來指定此約束。
name: ci-cd
on: [push, pull_request]
jobs:
  ci:
    # ...
    # CI 步驟
    # ...
  cd:
    # 只有當 "ci" 工作透過時才執行此工作
    needs: ci
    # 只有當新工作被推播到 "main" 分支時才執行此工作
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

現在,我們可以設定 CD 工作流程。在 GitHub Actions 中,每個工作都在一個全新的執行器上執行,因此我們需要從頭開始設定。完成設定後,我們的 CD 工作流程將有效地包含我們在第 7 章手動執行的所有步驟。

設定步驟:

  1. 指定作業系統。我們將再次使用 Ubuntu,語法為 runs-on: ubuntu-latest
  2. 安裝 Python(action: actions/setup-python@v2)。
  3. 簽出我們的儲存函式庫,以便能夠存取其內容(action: actions/checkout@v2)。
  4. 安裝 poetry(action: snok/install-poetry@v1)。
  5. 使用 poetry 安裝 pycounts(command: poetry install)。
  6. 建立 pycounts 的新版本(command: semantic-release publish,這使用了 Python Semantic Release (PSR) 工具)。
  7. 將新版本上傳到 TestPyPI(action: pypa/gh-action-pypi-publish@release/v1)。
  8. 測試新版本的套件是否能夠從 TestPyPI 成功安裝(command: pip install)。
  9. 將新版本上傳到 PyPI(action: pypa/gh-action-pypi-publish@release/v1)。

步驟 1 至 5 與我們之前為 CI 工作流程設定的步驟相同,因此我們可以直接複製並貼上到我們的「cd」工作中。

name: ci-cd
on: [push, pull_request]
jobs:
  ci:
    # ...
    # CI 步驟
    # ...
  cd:
    # 只有當 "ci" 工作透過時才執行此工作
    needs: ci
    # 只有當新工作被推播到 "main" 分支時才執行此工作
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    # 設定作業系統
    runs-on: ubuntu-latest
    # 定義工作步驟
    steps:
      - name: 設定 Python 3.9
        uses: actions/setup-python@v2
        with:
          python-version: '3.9'
      - name: 簽出儲存函式庫
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: 安裝 poetry
        uses: snok/install-poetry@v1
      - name: 安裝套件
        run: poetry install

自動建立新套件版本

在第 7 章中,我們瞭解到建立套件新版本需要經過幾個關鍵步驟:

  1. 在 CHANGELOG.md 中記錄變更內容。
  2. 更新套件的版本號。
  3. 在 GitHub 上標記新版本。
  4. 建置新的 sdist 和 wheel。

Python Semantic Release (PSR) 工具可以根據提交訊息中的關鍵字自動更新套件的版本號。此外,PSR 還可以執行上述所有步驟。要組態 PSR 以執行這些步驟,我們需要在 pyproject.toml 中的 [tool.semantic_release] 表格中新增幾個鍵值。

[tool.semantic_release]
version = "semantic-release version"
commit = "semantic-release commit"
tag = "semantic-release tag"

詳細解說:

  1. fetch-depth: 0 的作用是讓 PSR 能夠存取儲存函式庫的完整提交歷史,以確定如何更新套件的版本號。如果沒有這個引數,PSR 就只能存取最新的提交訊息。
  2. 使用 PSR 可以簡化建立新版本的流程,並且確保版本號的更新是根據實際的變更內容。
  3. pyproject.toml 中組態 PSR 可以讓工具自動執行多個步驟,從而減少手動操作的錯誤。

透過這些設定和工具,我們可以實作自動化的持續佈署,大大提高開發效率和套件發布的可靠性。

程式碼範例與詳細解說:

# 使用 Python Semantic Release (PSR) 自動更新版本號的範例程式碼

import semantic_release

def main():
    # 設定 PSR 的組態
    config = semantic_release.Config()
    
    # 使用 PSR 建立新版本
    new_version = semantic_release.version(config)
    
    # 列印新版本號
    print(f"New version: {new_version}")

if __name__ == "__main__":
    main()

詳細解說:

  1. 匯入 PSR 模組:首先,我們需要匯入 semantic_release 模組,這是 PSR 的核心功能所在。
  2. 設定 PSR 組態:在 main 函式中,我們建立了一個 semantic_release.Config 物件,這個物件包含了 PSR 的組態資訊。
  3. 建立新版本:呼叫 semantic_release.version(config) 函式,根據組態和提交歷史,建立新的版本號。
  4. 列印新版本號:最後,將新的版本號列印出來,以確認更新結果。

這個範例展示瞭如何使用 PSR 自動更新套件的版本號,並且提供了詳細的解說,幫助讀者理解每一步驟的作用和邏輯。

圖表說明:

@startuml
skinparam backgroundColor #FEFEFE

title 持續佈署自動化Python套件發布

|開發者|
start
:提交程式碼;
:推送到 Git;

|CI 系統|
:觸發建置;
:執行單元測試;
:程式碼品質檢查;

if (測試通過?) then (是)
    :建置容器映像;
    :推送到 Registry;
else (否)
    :通知開發者;
    stop
endif

|CD 系統|
:部署到測試環境;
:執行整合測試;

if (驗證通過?) then (是)
    :部署到生產環境;
    :健康檢查;
    :完成部署;
else (否)
    :回滾變更;
endif

stop

@enduml

詳細解說:

  1. 流程圖:這張圖表展示了使用 PSR 自動更新版本號的流程,從開始到列印新版本號的每一步驟都清晰地標示出來。
  2. 節點說明
    • A[開始]:表示流程的起始點。
    • B[設定 PSR 組態]:表示設定 PSR 的組態資訊。
    • C[建立新版本]:表示使用 PSR 建立新的版本號。
    • D[列印新版本號]:表示將新的版本號列印出來。

透過這張圖表,讀者可以清楚地瞭解使用 PSR 自動更新版本號的整個流程。

8.5 設定持續佈署

在持續整合與佈署的過程中,自動化發布新版本的套件至 PyPI 是至關重要的步驟。本文將介紹如何使用 Python Semantic Release(PSR)工具來自動化版本控制、變更日誌更新、以及將套件發布至 TestPyPI 和 PyPI。

設定 Python Semantic Release

首先,我們需要在 pyproject.toml 中設定 PSR。以下是一個範例設定:

[tool.semantic_release]
version_variable = "pyproject.toml:version"  # 版本號位置
branch = "main"  # 發布的分支
changelog_file = "CHANGELOG.md"  # 變更日誌檔案
build_command = "poetry build"  # 建置發行版本的命令
dist_path = "dist/"  # 發行版本的存放路徑
upload_to_release = true  # 自動在 GitHub 上建立發布
upload_to_pypi = false  # 關閉自動上傳至 PyPI
remove_dist = false  # 不移除發行版本
patch_without_tag = true  # 即使沒有觸發標籤也建立新的修補版本

設定內容解析:

  • version_variable:指定版本號的位置。
  • branch:指定從哪個分支進行發布。
  • changelog_file:指定變更日誌檔案的位置。
  • build_command:指定建置新發行版本的命令。
  • dist_path:指定建置完成後的發行版本存放路徑。
  • upload_to_release:設定是否自動在 GitHub 上建立發布。
  • upload_to_pypi:設定是否自動上傳至 PyPI(此處設為 false,以便先上傳至 TestPyPI 測試)。
  • remove_dist:設定是否在上傳後移除發行版本(此處設為 false,以便手動上傳至 TestPyPI 和 PyPI)。
  • patch_without_tag:設定即使沒有觸發標籤(如 “fix” 或 “feat”)也建立新的修補版本。

在 CD 工作流程中使用 PSR

設定好 PSR 後,我們需要在持續佈署(CD)工作流程中加入 PSR 的步驟。首先,需要提供 PSR 一個 GitHub 存取權杖(GH_TOKEN),以便其能夠讀寫倉函式庫中的檔案。GitHub 在每次工作流程執行時會自動建立一個名為 GITHUB_TOKEN 的權杖。我們可以透過 env 鍵和 ${{ secrets.GITHUB_TOKEN }} 語法將此權杖傳遞給 PSR。

接著,需要組態執行者的 Git 認證資訊,包括使用者名稱和電子郵件地址。這樣,工作流程才能夠對倉函式庫進行更改。

- name: 使用 Python Semantic Release 準備發布
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    git config user.name github-actions
    git config user.email [email protected]
    poetry run semantic-release publish

程式碼解析:

  1. 使用 git config 設定執行者的使用者名稱和電子郵件地址,以便能夠提交變更。
  2. 使用 poetry run semantic-release publish 命令來執行 PSR,更新版本號、變更日誌、建立新的發布,並建置新的發行版本。

上傳至 TestPyPI 和 PyPI

PSR 會建置新的發行版本,接下來需要將這些版本上傳至 TestPyPI 以進行測試。我們可以使用 pypa/gh-action-pypi-publish@release/v1 動作來完成這一步驟。首先,需要在 TestPyPI 上建立一個 API 權杖,並將其新增為 GitHub 倉函式庫中的一個名為 TEST_PYPI_API_TOKEN 的秘密。

- name: 發布至 TestPyPI
  uses: pypa/gh-action-pypi-publish@release/v1
  with:
    user: __token__
    password: ${{ secrets.TEST_PYPI_API_TOKEN }}
    repository_url: https://test.pypi.org/legacy/

程式碼解析:

  1. 使用 pypa/gh-action-pypi-publish@release/v1 動作來上傳發行版本至 TestPyPI。
  2. 指定 user__token__,並使用 password 鍵來傳遞 TestPyPI 的 API 權杖。
  3. 指定 repository_url 為 TestPyPI 的 URL。

接著,需要測試能否從 TestPyPI 正確安裝套件:

- name: 從 TestPyPI 安裝測試
  run: |
    pip install \
    --index-url https://test.pypi.org/simple/ \
    --extra-index-url https://pypi.org/simple \
    pycounts

程式碼解析:

  1. 使用 pip install 命令從 TestPyPI 安裝套件。
  2. 指定 --index-url 為 TestPyPI 的索引 URL,並使用 --extra-index-url 指定 PyPI 的索引 URL,以確保相依套件能夠正確安裝。

最後,將套件發布至 PyPI。同樣地,需要在 PyPI 上建立一個 API 權杖,並將其新增為 GitHub 倉函式庫中的一個名為 PYPI_API_TOKEN 的秘密。

- name: 發布至 PyPI
  uses: pypa/gh-action-pypi-publish@release/v1
  with:
    user: __token__
    password: ${{ secrets.PYPI_API_TOKEN }}

程式碼解析:

  1. 使用相同的 pypa/gh-action-pypi-publish@release/v1 動作來上傳發行版本至 PyPI。
  2. 指定 user__token__,並使用 password 鍵來傳遞 PyPI 的 API 權杖。

透過上述步驟,我們成功地設定了持續佈署流程,包括自動化版本控制、變更日誌更新、以及將套件發布至 TestPyPI 和 PyPI,大大簡化了套件的發布流程並提高了可靠性。

8.5.4 測試持續佈署(Testing Continuous Deployment)

現在,我們已經設定好了持續佈署(CD)工作流程!最終的 .github/workflows/ci-cd.yml 檔案如下所示:

name: ci-cd
on: [push, pull_request]
jobs:
  ci:
    # ...
    # CI 步驟與之前相同
    # ...
  cd:
    # 只有當 "ci" 工作成功時才執行此工作
    needs: ci
    # 只有當新的程式碼推播到 "main" 分支時才執行此工作
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    # 設定作業系統
    runs-on: ubuntu-latest
    # 定義工作步驟
    steps:
      - name: 設定 Python 3.9
        uses: actions/setup-python@v2
        with:
          python-version: 3.9
      - name: 簽出儲存函式庫
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: 安裝 poetry
        uses: snok/install-poetry@v1
      - name: 安裝套件
        run: poetry install
      - name: 使用 Python Semantic Release 準備發布
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          git config user.name github-actions
          git config user.email [email protected]
          poetry run semantic-release publish
      - name: 發布到 TestPyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user: __token__
          password: ${{ secrets.TEST_PYPI_API_TOKEN }}
          repository_url: https://test.pypi.org/legacy/
      - name: 從 TestPyPI 安裝測試
        run: |
          pip install \
            --index-url https://test.pypi.org/simple/ \
            --extra-index-url https://pypi.org/simple \
            pycounts
      - name: 發布到 PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user: __token__
          password: ${{ secrets.PYPI_API_TOKEN }}

內容解密:

  1. 工作流程觸發條件:該工作流程在推播(push)和提取請求(pull_request)時觸發。
  2. cicd 工作ci 工作執行持續整合的步驟,而 cd 工作則在 ci 工作成功後執行,並且只有在推播到 main 分支時才會執行。
  3. cd 工作步驟
    • 設定 Python 3.9 環境。
    • 簽出儲存函式庫程式碼。
    • 安裝 poetry 並使用它安裝專案依賴。
    • 使用 semantic-release 自動準備發布,包括更新版本號、變更日誌和標籤。
    • 將新版本發布到 TestPyPI 和 PyPI。

現在,我們準備測試完整的 CI/CD 工作流程。提交新的工作流程檔案和 pyproject.toml 檔案(在新增 PSR 組態選項時更改了該檔案)到版本控制,並將其推播到 GitHub。這將觸發 CI/CD 工作流程,因為我們將其組態為在有人推播到儲存函式庫的 main 分支時執行。為了示例,我們將在提交訊息中包含 feat 關鍵字,以觸發 PSR 對我們的套件進行小版本發布。

$ git add .github/workflows/ci-cd.yml pyproject.toml
$ git commit -m "feat: add CI/CD workflow"
$ git push

在 GitHub 上的 pycounts 儲存函式庫中,點選 “Actions” 標籤頁,我們應該會看到新的工作流程執行,如圖 8.9 所示。

內容解密:

  • 圖 8.9:顯示了 GitHub 上持續佈署工作流程的執行情況。
  • 圖 8.10:顯示了 cicd 兩個工作成功執行的結果。
  • 圖 8.11:顯示了 PSR 解析提交訊息後,將套件版本從 0.2.0 更新到 0.3.0。
  • 圖 8.12 和 圖 8.13:顯示了 PSR 自動更新的變更日誌和標籤的發布版本。
  • 圖 8.14:顯示了新版本套件成功佈署到 PyPI。
重點整理:
  1. CI/CD 的重要性:自動化測試、構建和佈署,提高開發效率和軟體品質。
  2. 工作流程設計:根據專案需求設計合適的工作流程,包括觸發條件、工作內容等。
  3. 工具選擇:選擇適合的工具,如 GitHub Actions、semantic-release 等,來實作自動化流程。