iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0

專案從「能跑能測」到「能被別人穩定安裝使用」,差的就是發佈這一步。今天把三件事一次補齊:

  1. 用 Hatch 建置與發佈套件(wheel、sdist)
  2. 以 SemVer 管理版本與升版節奏
  3. 維護高品質的 Changelog,讓使用者與同事知道每次發生什麼事

延伸脈絡:pyproject.toml 與 Hatch)、可重現環境與鎖檔、一鍵化工作流。以上都已鋪路,今天只把最後一哩路走完。


一、發佈前置:專案必備清單

最小可發佈專案,請確認這些檔案與欄位齊備:

my_project/
├─ pyproject.toml
├─ README.md              # 長描述(PyPI 會顯示)
├─ LICENSE
├─ CHANGELOG.md
├─ src/my_project/
│  ├─ __init__.py
│  └─ ...
└─ tests/

pyproject.toml 最小骨架(PEP 621,Hatchling)

[build-system]
requires = ["hatchling>=1.25"]
build-backend = "hatchling.build"

[project]
name = "my-project"
description = "Do one thing well."
readme = "README.md"
requires-python = ">=3.10"
license = { file = "LICENSE" }
authors = [{ name = "Your Name", email = "you@example.com" }]
keywords = ["foo", "bar"]
classifiers = [
  "Programming Language :: Python :: 3",
  "License :: OSI Approved :: MIT License"
]
# 版本來源:三選一(見下一節)
version = "0.1.0"

[project.urls]
Homepage = "https://github.com/you/my-project"
Issues = "https://github.com/you/my-project/issues"

小提醒:readme 指向的檔名與語法要能被 PyPI 正確渲染(通常 Markdown OK)。


二、單一可信的版本來源(SSOT)

你只有兩種好選擇:放在 pyproject.toml從原始碼單一檔案導出。請不要讓版本同時出現在三個地方。

A. 直接在 pyproject.toml 設定

最簡單也最常見:

[project]
version = "0.1.0"

搭配 Hatch 的版本指令:

hatch version       # 顯示目前版本
hatch version 0.2.0 # 設定新版本
hatch version minor # 自動 +1 次版(0.1.0 → 0.2.0)
hatch version patch # 自動 +1 修訂

B. 由原始碼檔案提供(single source)

如果你需要在執行時於 my_project.__version__ 取得版本,可以把版本寫在單一檔案,並讓 Hatchling 讀取:

[project]
dynamic = ["version"]

[tool.hatch.version]
path = "src/my_project/version.py"

# src/my_project/version.py
__version__ = "0.1.0"

一樣可用 hatch version 指令自動改這個檔案。

補充:之後如果改用 VCS 標籤(如 git tag)驅動版本,也能用 Hatchling 的版本外掛切換。但先把「單一來源」做好才是正道。


三、SemVer 與預發佈標記

SemVer = MAJOR.MINOR.PATCH

  • BREAKING 變更或移除 API → MAJOR
  • 新增相容功能 → MINOR
  • 修正 bug、內部最佳化、不影響 API → PATCH

預發佈(選擇性):1.2.0a11.2.0b11.2.0rc1

用在釋出候選與社群測試。CI 只要偵測 tag 含 a|b|rc,就推送到「測試 index(例如 TestPyPI)」。


四、建置產物:wheel 與 sdist

  • wheel:已編譯/打包的可安裝檔,安裝最快,首選。
  • sdist:原始碼分佈,某些使用者或環境需要從原始碼建置時會用到。

一鍵建置

hatch build            # 產出 dist/*.whl 與 dist/*.tar.gz
hatch build -t wheel   # 只建 wheel
hatch build -t sdist   # 只建 sdist

建置前請在乾淨環境驗證:

uv venv && source .venv/bin/activate     # 或用你喜歡的虛擬環境流
uv pip install --no-cache-dir dist/*.whl # 從產物安裝,別偷吃本地路徑
python -c "import my_project; print(my_project.__version__)"

為了可重現與速度,CI 仍建議沿用你在 Day 7 建立的 uv 流程與快取策略。


五、發佈到(Test)PyPI

憑證與索引設定

先在 PyPI 或 TestPyPI 申請 API Token,存到環境變數(搭配平台的 Secret 機制):

  • PYPI_TOKEN:發佈到 PyPI
  • TEST_PYPI_TOKEN:發佈到 TestPyPI

用 Hatch 發佈

# TestPyPI(預發佈或試跑)
hatch publish --repo testpypi --user __token__ --auth $TEST_PYPI_TOKEN

# PyPI 正式
hatch publish --user __token__ --auth $PYPI_TOKEN

Hatch 內部會處理上傳簽名與 metadata 驗證,省去手動 twine 的繁瑣。


六、Changelog:不是流水帳,而是可溝通的史記

採用「Keep a Changelog」的分類,讓讀者秒懂變更性質:

  • Added 新功能
  • Changed 變更或行為調整(非破壞)
  • Deprecated 仍可用但不鼓勵
  • Removed 破壞性移除(要對應 MAJOR)
  • Fixed 修 bug
  • Security 安全性修補

範例骨架

# Changelog

## [Unreleased]

### Added
- 支援 `--dry-run` 旗標

### Fixed
- 修正 Windows 路徑分隔符

## [0.2.0] - 2025-10-06
### Added
- 新增 `FooClient.retry()`,預設指數退避

### Changed
- `FooClient` 預設逾時從 5s 調整為 10s

## [0.1.1] - 2025-09-30
### Fixed
- 修正在 Py3.12 下的型別註記相容性

與版本自動化的搭配

  • Conventional Commitsfeat: ...fix: ...perf:refactor:docs:
    • feat → 多半對應 MINOR
    • fix → 多半對應 PATCH
    • feat!refactor! → 代表 BREAKING,對應 MAJOR
  • 你可以在 CI 做一層「自動升版建議」與「草稿 changelog」,但最後仍建議人工確認分類與文字,避免變更性質被誤判。

七、一鍵化工作流(本地與 CI)

Hatch scripts(本地)

[tool.hatch.envs.release]
dependencies = ["build", "twine"]  # hatch publish 會帶內建流程,留作備援
scripts.build = "hatch build"
scripts.check = [
  "ruff .",
  "mypy src/",
  "pytest -q",
]
scripts.tag = "git tag v{project:version} && git push --tags"
scripts.publish-test = "hatch publish --repo testpypi --user __token__ --auth $TEST_PYPI_TOKEN"
scripts.publish = "hatch publish --user __token__ --auth $PYPI_TOKEN"

常用順序:

hatch run release:check
hatch version minor          # 或 patch / 1.0.0
hatch run release:build
hatch run release:publish-test
# 驗證 OK 後
hatch run release:publish
hatch run release:tag

GitHub Actions(CI 範例)

name: release
on:
  push:
    tags:
      - "v*.*.*"

jobs:
  build-publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: astral-sh/setup-uv@v3
        with: { enable-cache: true }

      - name: Build
        run: |
          uv pip install hatchling
          hatch build

      - name: Publish to PyPI
        env:
          HATCH_INDEX_USER: __token__
          HATCH_INDEX_AUTH: ${{ secrets.PYPI_TOKEN }}
        run: |
          hatch publish

策略:只在打 tag 時發佈;分支合併則跑測試與品質門檻。


結語

發佈不是按個上傳鍵,而是把「版本管理、產物品質、可追溯紀錄」一次做到位。

Hatch 負責把建置與發佈變成標準化按鈕;SemVer 把「升到第幾版」從玄學變成公約;Changelog 讓每次變更都有清楚脈絡。

做到這三件事,你的專案就正式從「能跑」進化成「能被可靠地使用」。


上一篇
Day 21 -背景作業選型:Celery / Dramatiq / asyncio 任務
下一篇
Day 23 -容器化最佳實務:多階段 Dockerfile 與非 root 執行
系列文
30 天 Python 專案工坊:環境、結構、測試到部署全打通23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言