本案例展示如何使用 Hatch 這個 PyPA 推薦的現代 Python 專案管理工具,完成從專案建立到發布的完整流程。

先備知識

問題背景

Hatch 是什麼?

Hatch 是由 PyPA(Python Packaging Authority)成員開發維護的現代 Python 專案管理工具,整合了:

1Hatch 功能整合:
2├── 專案腳手架(hatch new)
3├── 環境管理(類似 tox + virtualenv)
4├── 版本管理(自動更新版本號)
5├── 建構系統(hatchling)
6└── 發布工具(hatch publish)

為什麼選擇 Hatch?

優勢說明
標準優先完全遵循 PEP 517/518/621 標準
一站式工具不需要額外安裝 tox、virtualenv、bump2version
快速建構hatchling 建構速度優於 setuptools
環境矩陣內建多 Python 版本測試支援
腳本系統定義可重用的專案腳本

完整工作流

第一步:安裝 Hatch

1# 使用 pip 安裝
2pip install hatch
3
4# 或使用 pipx(推薦,隔離安裝)
5pipx install hatch
6
7# 驗證安裝
8hatch --version

第二步:建立新專案

1# 建立新專案
2hatch new my-awesome-lib
3
4# 互動式建立(可自訂選項)
5hatch new my-awesome-lib --init
6
7# 建立應用程式專案(非函式庫)
8hatch new --cli my-cli-app

預設專案結構

 1my-awesome-lib/
 2├── src/
 3│   └── my_awesome_lib/
 4│       ├── __init__.py
 5│       └── __about__.py      # 版本資訊
 6├── tests/
 7│   └── __init__.py
 8├── pyproject.toml
 9├── README.md
10└── LICENSE.txt

生成的 pyproject.toml

 1[build-system]
 2requires = ["hatchling"]
 3build-backend = "hatchling.build"
 4
 5[project]
 6name = "my-awesome-lib"
 7dynamic = ["version"]
 8description = ''
 9readme = "README.md"
10requires-python = ">=3.8"
11license = "MIT"
12keywords = []
13authors = [
14  { name = "Your Name", email = "you@example.com" },
15]
16classifiers = [
17  "Development Status :: 4 - Beta",
18  "Programming Language :: Python",
19  "Programming Language :: Python :: 3.8",
20  "Programming Language :: Python :: 3.9",
21  "Programming Language :: Python :: 3.10",
22  "Programming Language :: Python :: 3.11",
23  "Programming Language :: Python :: 3.12",
24]
25dependencies = []
26
27[project.urls]
28Documentation = "https://github.com/yourname/my-awesome-lib#readme"
29Issues = "https://github.com/yourname/my-awesome-lib/issues"
30Source = "https://github.com/yourname/my-awesome-lib"
31
32[tool.hatch.version]
33path = "src/my_awesome_lib/__about__.py"

第三步:環境管理(hatch env)

定義環境

 1# pyproject.toml
 2
 3# 預設環境
 4[tool.hatch.envs.default]
 5dependencies = [
 6  "pytest",
 7  "pytest-cov",
 8]
 9
10[tool.hatch.envs.default.scripts]
11test = "pytest {args:tests}"
12test-cov = "pytest --cov=my_awesome_lib --cov-report=term-missing {args:tests}"
13
14# Lint 環境
15[tool.hatch.envs.lint]
16dependencies = [
17  "ruff>=0.4",
18  "mypy>=1.0",
19]
20
21[tool.hatch.envs.lint.scripts]
22check = [
23  "ruff check src tests",
24  "ruff format --check src tests",
25]
26fix = [
27  "ruff check --fix src tests",
28  "ruff format src tests",
29]
30typing = "mypy src"
31all = ["check", "typing"]
32
33# 文件環境
34[tool.hatch.envs.docs]
35dependencies = [
36  "mkdocs>=1.5",
37  "mkdocs-material>=9.0",
38]
39
40[tool.hatch.envs.docs.scripts]
41build = "mkdocs build"
42serve = "mkdocs serve"

使用環境

 1# 顯示所有環境
 2hatch env show
 3
 4# 執行預設環境的腳本
 5hatch run test
 6hatch run test-cov
 7
 8# 執行特定環境的腳本
 9hatch run lint:check
10hatch run lint:fix
11hatch run lint:typing
12
13# 進入環境 shell
14hatch shell
15
16# 進入特定環境
17hatch shell lint
18
19# 移除環境
20hatch env remove
21hatch env remove lint
22
23# 清除所有環境
24hatch env prune

第四步:版本管理(hatch version)

設定版本來源

方法 A:從檔案讀取版本
1[tool.hatch.version]
2path = "src/my_awesome_lib/__about__.py"
1# src/my_awesome_lib/__about__.py
2__version__ = "0.1.0"
方法 B:從 Git 標籤讀取版本(推薦用於開源專案)
1[build-system]
2requires = ["hatchling", "hatch-vcs"]
3build-backend = "hatchling.build"
4
5[tool.hatch.version]
6source = "vcs"
7
8[tool.hatch.build.hooks.vcs]
9version-file = "src/my_awesome_lib/_version.py"

版本操作

 1# 顯示當前版本
 2hatch version
 3
 4# 設定特定版本
 5hatch version 1.0.0
 6
 7# 語意化版本升級
 8hatch version patch   # 0.1.0 → 0.1.1
 9hatch version minor   # 0.1.1 → 0.2.0
10hatch version major   # 0.2.0 → 1.0.0
11
12# 預發布版本
13hatch version alpha   # 1.0.0 → 1.0.1a0
14hatch version beta    # 1.0.1a0 → 1.0.1b0
15hatch version rc      # 1.0.1b0 → 1.0.1rc0
16hatch version release # 1.0.1rc0 → 1.0.1
17
18# 開發版本
19hatch version dev     # 1.0.0 → 1.0.1.dev0

第五步:建構與發布

建構套件

 1# 建構 wheel 和 sdist
 2hatch build
 3
 4# 只建構 wheel
 5hatch build --target wheel
 6
 7# 只建構 sdist
 8hatch build --target sdist
 9
10# 清除建構產物後重建
11hatch build --clean
建構產物
1dist/
2├── my_awesome_lib-0.1.0-py3-none-any.whl
3└── my_awesome_lib-0.1.0.tar.gz

發布套件

1# 發布到 PyPI(需要設定認證)
2hatch publish
3
4# 發布到 TestPyPI
5hatch publish --repo test
6
7# 指定發布的檔案
8hatch publish dist/my_awesome_lib-0.1.0-py3-none-any.whl
設定 PyPI 認證
1# 設定 PyPI token
2hatch config set pypi.auth.username __token__
3hatch config set pypi.auth.password pypi-xxxxx
4
5# 或使用環境變數
6export HATCH_INDEX_USER=__token__
7export HATCH_INDEX_AUTH=pypi-xxxxx

pyproject.toml 的 Hatch 特定設定

[tool.hatch.build] 建構設定

 1[tool.hatch.build]
 2# 包含的檔案(支援 glob)
 3include = [
 4  "src/my_awesome_lib",
 5  "README.md",
 6]
 7
 8# 排除的檔案
 9exclude = [
10  "*.pyc",
11  "__pycache__",
12  ".git",
13]
14
15# 是否可重現建構
16reproducible = true
17
18# 開發模式設定
19dev-mode-dirs = ["src"]
20
21[tool.hatch.build.targets.sdist]
22# 原始碼發布設定
23include = [
24  "/src",
25  "/tests",
26  "/README.md",
27  "/LICENSE.txt",
28]
29
30[tool.hatch.build.targets.wheel]
31# Wheel 發布設定
32packages = ["src/my_awesome_lib"]
33
34# 只包含特定平台
35# only-include = ["my_awesome_lib"]

[tool.hatch.envs] 環境設定

 1[tool.hatch.envs.default]
 2# 相依性
 3dependencies = ["pytest"]
 4
 5# 額外安裝的 features
 6features = ["yaml"]
 7
 8# 環境變數
 9[tool.hatch.envs.default.env-vars]
10PYTHONPATH = "src"
11LOG_LEVEL = "DEBUG"
12
13# 腳本定義
14[tool.hatch.envs.default.scripts]
15test = "pytest {args}"
16
17# 平台特定設定
18[tool.hatch.envs.default.overrides]
19platform.windows.scripts = [
20  'test = "pytest --no-header {args}"',
21]

[tool.hatch.version] 版本設定

 1# 從檔案讀取
 2[tool.hatch.version]
 3path = "src/my_awesome_lib/__about__.py"
 4pattern = "^__version__ = ['\"](/python-advanced/07-packaging/case-studies/hatch-workflow/?P<version>[^'\"]+)['\"]"
 5
 6# 從 VCS 讀取
 7[tool.hatch.version]
 8source = "vcs"
 9raw-options = { local_scheme = "no-local-version" }
10
11[tool.hatch.build.hooks.vcs]
12version-file = "src/my_awesome_lib/_version.py"

[tool.hatch.metadata] 元資料設定

 1[tool.hatch.metadata]
 2# 允許直接依賴(通常應該避免)
 3allow-direct-references = false
 4
 5# 動態讀取 README
 6[tool.hatch.metadata.hooks.fancy-pypi-readme]
 7content-type = "text/markdown"
 8
 9[[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]]
10path = "README.md"

與 Poetry 的比較

設計理念差異

 1Hatch:
 2├── 遵循 PEP 標準優先
 3├── 環境管理內建
 4├── 不提供依賴鎖定
 5├── 建構系統(hatchling)可獨立使用
 6└── 設定完全在 [tool.hatch]
 7
 8Poetry:
 9├── 自有生態系統
10├── 強調依賴鎖定(poetry.lock)
11├── 虛擬環境管理
12├── poetry.core 可獨立使用
13└── 混合 [project] 和 [tool.poetry]

功能對照

功能HatchPoetry
依賴鎖定不支援poetry.lock
環境管理內建矩陣支援單一環境
PEP 621完全支援Poetry 2.0 支援
腳本系統強大(環境分離)基本
版本管理內建 bump需外掛或手動
插件系統支援支援

pyproject.toml 比較

Hatch 風格

 1[build-system]
 2requires = ["hatchling"]
 3build-backend = "hatchling.build"
 4
 5[project]
 6name = "my-package"
 7version = "1.0.0"
 8dependencies = ["requests>=2.28"]
 9
10[project.optional-dependencies]
11dev = ["pytest>=8.0"]
12
13[tool.hatch.envs.default]
14features = ["dev"]

Poetry 風格(2.0)

 1[build-system]
 2requires = ["poetry-core>=2.0"]
 3build-backend = "poetry.core.masonry.api"
 4
 5[project]
 6name = "my-package"
 7version = "1.0.0"
 8dependencies = ["requests>=2.28"]
 9
10[tool.poetry.group.dev.dependencies]
11pytest = "^8.0"

選擇建議

 1選擇 Hatch:
 2├── 開發 Python 函式庫
 3├── 需要多環境測試
 4├── 偏好標準優先
 5└── 不需要依賴鎖定
 6
 7選擇 Poetry:
 8├── 開發應用程式
 9├── 需要嚴格的依賴鎖定
10├── 團隊習慣 Poetry 工作流
11└── 需要與現有 Poetry 專案整合

實用技巧

多環境測試矩陣

 1# 定義多 Python 版本測試
 2[tool.hatch.envs.test]
 3dependencies = ["pytest", "pytest-cov"]
 4
 5[[tool.hatch.envs.test.matrix]]
 6python = ["3.9", "3.10", "3.11", "3.12", "3.13"]
 7
 8[tool.hatch.envs.test.scripts]
 9run = "pytest {args:tests}"
10cov = "pytest --cov=my_awesome_lib {args:tests}"
1# 在所有矩陣環境執行測試
2hatch run test:run
3
4# 在特定版本執行
5hatch run +py=3.12 test:run
6
7# 顯示矩陣環境
8hatch env show --ascii

複合腳本

 1[tool.hatch.envs.default.scripts]
 2# 單一命令
 3test = "pytest {args:tests}"
 4
 5# 多命令(依序執行)
 6ci = [
 7  "ruff check src tests",
 8  "pytest --cov",
 9  "mypy src",
10]
11
12# 呼叫其他腳本
13all = ["lint:check", "test:cov"]
14
15# 帶預設參數
16lint = "ruff check {args:.}"

環境繼承

 1# 基礎環境
 2[tool.hatch.envs.base]
 3dependencies = ["pytest"]
 4
 5# 繼承基礎環境
 6[tool.hatch.envs.coverage]
 7template = "base"
 8dependencies = ["pytest-cov"]
 9
10[tool.hatch.envs.coverage.scripts]
11run = "pytest --cov {args:tests}"

條件依賴

 1[tool.hatch.envs.default]
 2dependencies = [
 3  "pytest",
 4]
 5
 6# 根據平台新增依賴
 7[tool.hatch.envs.default.overrides]
 8platform.linux.dependencies = ["pytest-xdist"]
 9platform.darwin.dependencies = ["pytest-xdist"]
10
11# 根據 Python 版本
12python.3.8.dependencies = ["typing-extensions"]

自訂建構 Hook

1[tool.hatch.build.hooks.custom]
2# 建構前執行
3path = "hatch_build.py"
1# hatch_build.py
2from hatchling.builders.hooks.plugin.interface import BuildHookInterface
3
4class CustomBuildHook(BuildHookInterface):
5    def initialize(self, version, build_data):
6        # 建構前的自訂邏輯
7        print(f"Building version {version}")

完整範例:CLI 應用程式

 1# pyproject.toml
 2[build-system]
 3requires = ["hatchling"]
 4build-backend = "hatchling.build"
 5
 6[project]
 7name = "my-cli"
 8dynamic = ["version"]
 9description = "A useful CLI tool"
10readme = "README.md"
11requires-python = ">=3.10"
12license = "MIT"
13dependencies = [
14  "click>=8.0",
15  "rich>=13.0",
16]
17
18[project.scripts]
19my-cli = "my_cli.main:app"
20
21[project.optional-dependencies]
22yaml = ["PyYAML>=6.0"]
23
24[tool.hatch.version]
25path = "src/my_cli/__about__.py"
26
27[tool.hatch.build.targets.wheel]
28packages = ["src/my_cli"]
29
30# ===== 環境設定 =====
31
32[tool.hatch.envs.default]
33dependencies = [
34  "pytest>=8.0",
35  "pytest-cov>=4.0",
36]
37
38[tool.hatch.envs.default.scripts]
39test = "pytest {args:tests}"
40cov = "pytest --cov=my_cli --cov-report=term-missing {args:tests}"
41
42[tool.hatch.envs.lint]
43dependencies = [
44  "ruff>=0.4",
45  "mypy>=1.0",
46]
47
48[tool.hatch.envs.lint.scripts]
49check = ["ruff check src tests", "ruff format --check src tests"]
50fix = ["ruff check --fix src tests", "ruff format src tests"]
51typing = "mypy src"
52all = ["check", "typing"]
53
54[[tool.hatch.envs.test.matrix]]
55python = ["3.10", "3.11", "3.12", "3.13"]
56
57# ===== 工具設定 =====
58
59[tool.ruff]
60src = ["src"]
61line-length = 88
62
63[tool.ruff.lint]
64select = ["E", "W", "F", "I", "B", "UP"]
65
66[tool.mypy]
67python_version = "3.10"
68strict = true
69
70[tool.pytest.ini_options]
71testpaths = ["tests"]
72addopts = "-v"

發布檢查清單

 1發布前檢查:
 2├── [ ] hatch run lint:all 通過
 3├── [ ] hatch run test:run 在所有 Python 版本通過
 4├── [ ] hatch version 更新版本號
 5├── [ ] 更新 CHANGELOG.md
 6├── [ ] hatch build 建構成功
 7├── [ ] 在虛擬環境測試安裝:pip install dist/*.whl
 8├── [ ] hatch publish --repo test 發布到 TestPyPI
 9├── [ ] 從 TestPyPI 測試安裝
10└── [ ] hatch publish 發布到 PyPI

延伸閱讀


返回:案例研究