專案從「能跑能測」到「能被別人穩定安裝使用」,差的就是發佈這一步。今天把三件事一次補齊:
延伸脈絡: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)。
你只有兩種好選擇:放在 pyproject.toml 或 從原始碼單一檔案導出。請不要讓版本同時出現在三個地方。
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 修訂
如果你需要在執行時於 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 = MAJOR.MINOR.PATCH
預發佈(選擇性):1.2.0a1
、1.2.0b1
、1.2.0rc1
用在釋出候選與社群測試。CI 只要偵測 tag 含 a|b|rc
,就推送到「測試 index(例如 TestPyPI)」。
一鍵建置:
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 流程與快取策略。
先在 PyPI 或 TestPyPI 申請 API Token,存到環境變數(搭配平台的 Secret 機制):
PYPI_TOKEN
:發佈到 PyPITEST_PYPI_TOKEN
:發佈到 TestPyPI# TestPyPI(預發佈或試跑)
hatch publish --repo testpypi --user __token__ --auth $TEST_PYPI_TOKEN
# PyPI 正式
hatch publish --user __token__ --auth $PYPI_TOKEN
Hatch 內部會處理上傳簽名與 metadata 驗證,省去手動 twine 的繁瑣。
採用「Keep a Changelog」的分類,讓讀者秒懂變更性質:
範例骨架:
# 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 下的型別註記相容性
feat: ...
、fix: ...
、perf:
、refactor:
、docs:
)
feat
→ 多半對應 MINOR
fix
→ 多半對應 PATCH
feat!
或 refactor!
→ 代表 BREAKING,對應 MAJOR
[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
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 讓每次變更都有清楚脈絡。
做到這三件事,你的專案就正式從「能跑」進化成「能被可靠地使用」。