案例:使用 Poetry 完整工作流
案例:使用 Poetry 完整工作流
本案例展示如何使用 Poetry 管理現代 Python 專案的完整生命週期,從專案建立到套件發布。
先備知識
- 建構系統比較
- Python 虛擬環境基礎
- 套件相依性概念
問題背景
現代專案的挑戰
傳統的 Python 專案管理面臨以下問題:
1傳統工作流程的痛點:
2├── 相依性管理
3│ ├── requirements.txt 無法鎖定間接相依性
4│ ├── pip freeze 產生的版本可能過於嚴格
5│ └── 開發與生產環境相依性混在一起
6├── 虛擬環境
7│ ├── 需要手動建立和管理
8│ ├── 不同專案的環境容易混淆
9│ └── 缺乏與專案的關聯性
10├── 建構與發布
11│ ├── setup.py 設定複雜
12│ ├── 發布流程需要多個工具
13│ └── 版本管理不一致
14└── 團隊協作
15 ├── 環境難以重現
16 ├── 「在我電腦上可以跑」問題
17 └── 新成員上手困難為什麼選擇 Poetry?
| 特性 | pip + venv | Poetry |
|---|---|---|
| 相依性鎖定 | 手動(pip freeze) | 自動(poetry.lock) |
| 間接相依性追蹤 | 無 | 完整追蹤 |
| 虛擬環境管理 | 手動 | 自動整合 |
| 建構系統 | 需要額外工具 | 內建 |
| 發布流程 | 需要 twine | 內建 |
| 相依性群組 | 無原生支援 | 完整支援 |
解決方案
安裝 Poetry
1# 官方推薦的安裝方式(獨立安裝)
2curl -sSL https://install.python-poetry.org | python3 -
3
4# 或使用 pipx(推薦用於 CLI 工具)
5pipx install poetry
6
7# 確認安裝
8poetry --version設定 Poetry
1# 在專案目錄中建立虛擬環境(推薦)
2poetry config virtualenvs.in-project true
3
4# 查看所有設定
5poetry config --list工作流一:建立新專案
使用 poetry new(完整專案結構)
1# 建立新專案
2poetry new my-awesome-lib
3
4# 產生的目錄結構
5my-awesome-lib/
6├── pyproject.toml
7├── README.md
8├── my_awesome_lib/
9│ └── __init__.py
10└── tests/
11 └── __init__.py 1# 使用 src layout(推薦用於函式庫)
2poetry new --src my-awesome-lib
3
4# 產生的目錄結構
5my-awesome-lib/
6├── pyproject.toml
7├── README.md
8├── src/
9│ └── my_awesome_lib/
10│ └── __init__.py
11└── tests/
12 └── __init__.py使用 poetry init(現有專案)
1# 在現有目錄初始化
2cd existing-project
3poetry init
4
5# 互動式問答
6# - Package name
7# - Version
8# - Description
9# - Author
10# - License
11# - Python version
12# - Dependencies互動式初始化範例
1This command will guide you through creating your pyproject.toml config.
2
3Package name [existing-project]: my-package
4Version [0.1.0]: 1.0.0
5Description []: A useful Python package
6Author [Your Name <you@example.com>, n to skip]:
7License []: MIT
8Compatible Python versions [^3.10]: ^3.10
9
10Would you like to define your main dependencies interactively? (yes/no) [yes]
11Search for package to add (or leave blank to continue): requests
12...工作流二:管理相依性
新增相依性
1# 新增生產相依性
2poetry add requests
3poetry add "httpx>=0.24"
4
5# 新增開發相依性
6poetry add pytest --group dev
7poetry add ruff mypy --group dev
8
9# 新增文件相依性
10poetry add mkdocs mkdocs-material --group docs
11
12# 新增可選相依性(extras)
13poetry add pyyaml --optionalpyproject.toml 的變化
1[tool.poetry.dependencies]
2python = "^3.10"
3requests = "^2.31.0"
4httpx = ">=0.24"
5
6[tool.poetry.group.dev.dependencies]
7pytest = "^8.0.0"
8ruff = "^0.4.0"
9mypy = "^1.10.0"
10
11[tool.poetry.group.docs.dependencies]
12mkdocs = "^1.5.0"
13mkdocs-material = "^9.5.0"
14
15[tool.poetry.extras]
16yaml = ["pyyaml"]移除相依性
1# 移除生產相依性
2poetry remove requests
3
4# 移除開發相依性
5poetry remove pytest --group dev更新相依性
1# 更新所有相依性
2poetry update
3
4# 更新特定套件
5poetry update requests
6
7# 檢視可更新的套件
8poetry show --outdated
9
10# 檢視相依性樹
11poetry show --tree相依性樹範例輸出
1requests 2.31.0 Python HTTP for Humans.
2├── certifi >=2017.4.17
3├── charset-normalizer >=2,<4
4├── idna >=2.5,<4
5└── urllib3 >=1.21.1,<3工作流三:虛擬環境管理
自動環境管理
1# 安裝所有相依性(自動建立虛擬環境)
2poetry install
3
4# 僅安裝生產相依性
5poetry install --only main
6
7# 安裝包含特定群組
8poetry install --with dev,docs
9
10# 排除特定群組
11poetry install --without docs
12
13# 安裝 extras
14poetry install --extras yaml
15poetry install --all-extras環境操作
1# 進入虛擬環境 shell
2poetry shell
3
4# 在虛擬環境中執行命令(不進入 shell)
5poetry run python script.py
6poetry run pytest
7poetry run python -c "import my_package; print(my_package.__version__)"
8
9# 顯示環境資訊
10poetry env info
11
12# 顯示環境路徑
13poetry env info --path
14
15# 列出所有環境
16poetry env list
17
18# 切換 Python 版本
19poetry env use python3.11
20poetry env use 3.12
21
22# 刪除環境
23poetry env remove python3.11
24poetry env remove --all環境資訊範例輸出
1Virtualenv
2Python: 3.11.6
3Implementation: CPython
4Path: /path/to/project/.venv
5Executable: /path/to/project/.venv/bin/python
6Valid: True
7
8Base
9Platform: darwin
10OS: posix
11Python: 3.11.6
12Path: /opt/homebrew/Cellar/python@3.11/3.11.6/Frameworks/Python.framework/Versions/3.11
13Executable: /opt/homebrew/Cellar/python@3.11/3.11.6/Frameworks/Python.framework/Versions/3.11/bin/python3.11工作流四:建構與發布
建構套件
1# 建構 sdist 和 wheel
2poetry build
3
4# 僅建構 wheel
5poetry build --format wheel
6
7# 建構結果
8dist/
9├── my_package-1.0.0-py3-none-any.whl
10└── my_package-1.0.0.tar.gz發布到 PyPI
1# 設定 PyPI token
2poetry config pypi-token.pypi pypi-XXXXXXXXXXXX
3
4# 發布到 PyPI
5poetry publish
6
7# 建構並發布(一步完成)
8poetry publish --build
9
10# 發布到 TestPyPI
11poetry config repositories.testpypi https://test.pypi.org/legacy/
12poetry config pypi-token.testpypi pypi-XXXXXXXXXXXX
13poetry publish --repository testpypi版本管理
1# 查看當前版本
2poetry version
3
4# 升級版本
5poetry version patch # 1.0.0 -> 1.0.1
6poetry version minor # 1.0.0 -> 1.1.0
7poetry version major # 1.0.0 -> 2.0.0
8
9# 設定特定版本
10poetry version 2.0.0
11
12# 預發布版本
13poetry version prepatch # 1.0.0 -> 1.0.1a0
14poetry version preminor # 1.0.0 -> 1.1.0a0
15poetry version premajor # 1.0.0 -> 2.0.0a0完整 pyproject.toml 範例
1[build-system]
2requires = ["poetry-core>=2.0"]
3build-backend = "poetry.core.masonry.api"
4
5[project]
6name = "my-awesome-lib"
7version = "1.0.0"
8description = "A feature-rich Python library"
9readme = "README.md"
10license = "MIT"
11requires-python = ">=3.10"
12authors = [
13 { name = "Your Name", email = "you@example.com" }
14]
15keywords = ["python", "library", "utilities"]
16classifiers = [
17 "Development Status :: 4 - Beta",
18 "Intended Audience :: Developers",
19 "License :: OSI Approved :: MIT License",
20 "Operating System :: OS Independent",
21 "Programming Language :: Python :: 3",
22 "Programming Language :: Python :: 3.10",
23 "Programming Language :: Python :: 3.11",
24 "Programming Language :: Python :: 3.12",
25 "Programming Language :: Python :: 3.13",
26 "Typing :: Typed",
27]
28
29[project.urls]
30Homepage = "https://github.com/yourname/my-awesome-lib"
31Documentation = "https://my-awesome-lib.readthedocs.io"
32Repository = "https://github.com/yourname/my-awesome-lib.git"
33Changelog = "https://github.com/yourname/my-awesome-lib/blob/main/CHANGELOG.md"
34
35[project.scripts]
36my-cli = "my_awesome_lib.cli:main"
37
38[project.optional-dependencies]
39yaml = ["pyyaml>=6.0"]
40all = ["my-awesome-lib[yaml]"]
41
42# ===== Poetry 特定設定 =====
43
44[tool.poetry]
45packages = [{ include = "my_awesome_lib", from = "src" }]
46
47[tool.poetry.dependencies]
48python = "^3.10"
49requests = "^2.31"
50click = "^8.1"
51
52[tool.poetry.group.dev.dependencies]
53pytest = "^8.0"
54pytest-cov = "^4.1"
55pytest-asyncio = "^0.23"
56mypy = "^1.10"
57ruff = "^0.4"
58
59[tool.poetry.group.docs]
60optional = true
61
62[tool.poetry.group.docs.dependencies]
63mkdocs = "^1.5"
64mkdocs-material = "^9.5"
65
66# ===== 工具設定 =====
67
68[tool.ruff]
69src = ["src"]
70line-length = 88
71target-version = "py310"
72
73[tool.ruff.lint]
74select = ["E", "W", "F", "I", "B", "C4", "UP"]
75
76[tool.mypy]
77python_version = "3.10"
78strict = true
79
80[tool.pytest.ini_options]
81testpaths = ["tests"]
82addopts = "-v --tb=short"與其他工具的比較
Poetry vs pip
| 操作 | pip | Poetry |
|---|---|---|
| 安裝相依性 | pip install -r requirements.txt | poetry install |
| 新增相依性 | 手動編輯 requirements.txt | poetry add package |
| 鎖定版本 | pip freeze > requirements.txt | 自動更新 poetry.lock |
| 建立環境 | python -m venv .venv | 自動建立 |
| 執行命令 | source .venv/bin/activate && python | poetry run python |
| 建構套件 | python -m build | poetry build |
| 發布套件 | twine upload dist/* | poetry publish |
Poetry vs setuptools
| 面向 | setuptools | Poetry |
|---|---|---|
| 設定格式 | pyproject.toml + 可能需要 setup.py | 僅 pyproject.toml |
| 相依性管理 | 需要額外工具 | 內建完整支援 |
| 環境管理 | 無 | 內建 |
| 學習曲線 | 較陡(歷史包袱) | 較平緩 |
| C 擴展支援 | 完整 | 不支援 |
| 生態系統 | 最廣泛 | 持續成長 |
Poetry vs Hatch
| 面向 | Hatch | Poetry |
|---|---|---|
| 設計理念 | PEP 標準優先 | 使用者體驗優先 |
| 相依性鎖定 | 無內建 | 核心功能 |
| 環境管理 | 多環境(類似 tox) | 單一虛擬環境 |
| 腳本系統 | 完整 | 基本 |
| 建構後端 | hatchling | poetry-core |
| 適用場景 | 開源函式庫 | 應用程式、內部工具 |
實用技巧
技巧一:善用 Lock 檔案
1# poetry.lock 的重要性
2# - 記錄所有相依性的精確版本(包含間接相依性)
3# - 確保團隊成員、CI/CD 使用相同版本
4# - 應該提交到版本控制
5
6# 根據 lock 檔案安裝(不更新)
7poetry install --no-update
8
9# 驗證 lock 檔案與 pyproject.toml 一致
10poetry check --lock
11
12# 匯出為 requirements.txt(部署用)
13poetry export -f requirements.txt -o requirements.txt
14poetry export -f requirements.txt --with dev -o requirements-dev.txt
15poetry export --without-hashes -o requirements.txt技巧二:善用相依性群組
1# 開發相依性
2[tool.poetry.group.dev.dependencies]
3pytest = "^8.0"
4ruff = "^0.4"
5
6# 可選群組(預設不安裝)
7[tool.poetry.group.docs]
8optional = true
9
10[tool.poetry.group.docs.dependencies]
11mkdocs = "^1.5"
12
13# CI 專用群組
14[tool.poetry.group.ci]
15optional = true
16
17[tool.poetry.group.ci.dependencies]
18pytest-cov = "^4.1"
19codecov = "^2.1"1# 安裝特定群組
2poetry install --with docs
3poetry install --with ci
4
5# CI 環境中的安裝
6poetry install --only main,ci技巧三:善用 Extras
1[project.optional-dependencies]
2# 功能性 extras
3yaml = ["pyyaml>=6.0"]
4async = ["httpx>=0.24", "aiofiles>=23.0"]
5
6# 完整安裝
7all = ["my-package[yaml,async]"]1# 使用者安裝方式
2pip install my-package # 基本功能
3pip install "my-package[yaml]" # 包含 YAML 支援
4pip install "my-package[all]" # 所有功能技巧四:本地相依性和 Git 相依性
1[tool.poetry.dependencies]
2# 本地路徑相依性
3my-local-lib = { path = "../my-local-lib", develop = true }
4
5# Git 相依性
6my-git-lib = { git = "https://github.com/user/repo.git" }
7my-git-lib = { git = "https://github.com/user/repo.git", branch = "develop" }
8my-git-lib = { git = "https://github.com/user/repo.git", tag = "v1.0.0" }
9my-git-lib = { git = "https://github.com/user/repo.git", rev = "abc123" }技巧五:平台特定相依性
1[tool.poetry.dependencies]
2# 僅 Windows
3pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
4
5# 僅 Linux
6uvloop = { version = "^0.19", markers = "sys_platform == 'linux'" }
7
8# Python 版本限制
9typing-extensions = { version = "^4.0", python = "<3.11" }技巧六:Poetry 腳本
1[tool.poetry.scripts]
2my-cli = "my_package.cli:main"
3my-tool = "my_package.tools:run" 1# src/my_package/cli.py
2import click
3
4@click.command()
5@click.option("--name", default="World", help="Name to greet")
6def main(name: str) -> None:
7 """Greet someone."""
8 click.echo(f"Hello, {name}!")
9
10if __name__ == "__main__":
11 main()1# 安裝後即可使用
2my-cli --name Python
3# 輸出:Hello, Python!常見問題與解決
問題一:相依性解析衝突
1# 錯誤訊息
2SolverProblemError: ...
3
4# 解決方法
5# 1. 檢視衝突詳情
6poetry show --tree
7
8# 2. 放寬版本限制
9poetry add "package>=1.0,<3.0"
10
11# 3. 強制更新 lock 檔案
12poetry lock --no-update問題二:虛擬環境問題
1# 重建虛擬環境
2poetry env remove --all
3poetry install
4
5# 指定 Python 版本
6poetry env use /usr/bin/python3.11問題三:CI/CD 快取
1# GitHub Actions 範例
2- name: Install Poetry
3 uses: snok/install-poetry@v1
4 with:
5 virtualenvs-create: true
6 virtualenvs-in-project: true
7
8- name: Load cached venv
9 uses: actions/cache@v4
10 with:
11 path: .venv
12 key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
13
14- name: Install dependencies
15 if: steps.cache.outputs.cache-hit != 'true'
16 run: poetry install --no-interaction設計權衡
| 面向 | 優點 | 缺點 |
|---|---|---|
| 相依性鎖定 | 環境可重現、團隊一致 | lock 檔案衝突、更新需謹慎 |
| 一體化工具 | 學習成本低、工作流統一 | 與其他工具整合需調整 |
| 虛擬環境整合 | 自動管理、不易混淆 | 自訂環境位置需設定 |
| 建構與發布 | 流程簡化 | 不支援 C 擴展 |
練習
基礎練習:建立 Poetry 專案
目標:使用 Poetry 建立一個簡單的專案
- 使用
poetry new --src my-utils建立專案 - 新增
requests作為生產相依性 - 新增
pytest和ruff作為開發相依性 - 執行
poetry install並驗證環境
進階練習:設定相依性群組
目標:建立完整的相依性管理結構
- 建立
dev、docs、ci三個群組 - 將
docs和ci設為可選群組 - 練習使用
--with和--without選項
挑戰題:完整發布流程
目標:將專案發布到 TestPyPI
- 設定 TestPyPI repository
- 使用
poetry version管理版本 - 執行
poetry build建構套件 - 執行
poetry publish --repository testpypi發布
延伸閱讀
返回:案例研究