Python Poetry 与 pyproject
大约 11 分钟约 3269 字
Python Poetry 与 pyproject
简介
Poetry 是 Python 现代化的项目管理和依赖管理工具,基于 pyproject.toml 标准(PEP 517/518)统一管理依赖、虚拟环境、构建和发布。相比 pip + requirements.txt + setup.py 的传统方案,Poetry 提供了锁文件、依赖解析和一键发布等一站式能力。
特点
实现
pyproject.toml 完整配置
# pyproject.toml - 项目配置文件
[tool.poetry]
name = "my-service"
version = "1.2.0"
description = "订单处理微服务"
authors = ["张三 <zhang@example.com>"]
readme = "README.md"
license = "MIT"
packages = [{include = "src"}]
[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.109.0"
uvicorn = {version = "^0.27.0", extras = ["standard"]}
pydantic = "^2.5.0"
pydantic-settings = "^2.1.0"
sqlalchemy = "^2.0.0"
asyncpg = "^0.29.0"
redis = {version = "^5.0.0", extras = ["hiredis"]}
httpx = "^0.26.0"
celery = "^5.3.0"
structlog = "^24.1.0"
[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
pytest-cov = "^4.1.0"
pytest-asyncio = "^0.23.0"
pytest-mock = "^3.12.0"
mypy = "^1.8.0"
ruff = "^0.2.0"
black = "^24.1.0"
isort = "^5.13.0"
pre-commit = "^3.6.0"
[tool.poetry.group.test.dependencies]
faker = "^22.0.0"
factory-boy = "^3.3.0"
pytest-benchmark = "^4.0.0"
[tool.poetry.group.docs.dependencies]
mkdocs = "^1.5.0"
mkdocs-material = "^9.5.0"
[tool.poetry.scripts]
my-service = "src.main:main"
my-cli = "src.cli:app"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
# --- 工具配置 ---
[tool.ruff]
line-length = 100
target-version = "py310"
src = ["src", "tests"]
[tool.ruff.lint]
select = ["E", "F", "I", "N", "W", "UP"]
[tool.black]
line-length = 100
target-version = ["py310"]
[tool.mypy]
python_version = "3.10"
strict = true
warn_return_any = true
disallow_untyped_defs = true
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --tb=short --strict-markers"
markers = [
"slow: 慢速测试",
"integration: 集成测试",
]Poetry 常用工作流命令
# === 项目初始化 ===
poetry new my-project # 创建新项目(标准结构)
poetry init # 在现有目录中初始化
# === 依赖管理 ===
poetry add fastapi # 添加生产依赖
poetry add --group dev pytest # 添加开发依赖
poetry add --group test faker # 添加测试依赖
poetry remove requests # 移除依赖
poetry add "httpx>=0.24,<1.0" # 指定版本范围
poetry add git+https://github.com/user/repo.git # Git 依赖
poetry add ./local-package # 本地路径依赖
# === 环境管理 ===
poetry install # 安装所有依赖(含 dev)
poetry install --without dev # 不安装 dev 依赖
poetry install --only main # 只安装生产依赖
poetry update # 更新所有依赖
poetry update fastapi # 更新指定依赖
poetry show # 列出所有已安装的依赖
poetry show --tree # 以树形结构展示依赖关系
poetry show --outdated # 显示过时的依赖
# === 运行命令 ===
poetry run python main.py # 在虚拟环境中运行
poetry run pytest # 运行测试
poetry run ruff check . # 代码检查
poetry shell # 激活虚拟环境 shell
# === 构建与发布 ===
poetry build # 构建 sdist 和 wheel
poetry publish # 发布到 PyPI
poetry publish -r private # 发布到私有仓库
# === 版本管理 ===
poetry version patch # 1.2.0 -> 1.2.1
poetry version minor # 1.2.0 -> 1.3.0
poetry version major # 1.2.0 -> 2.0.0
poetry version prerelease # 1.2.0 -> 1.2.1a0
# === 环境信息 ===
poetry env info # 显示当前虚拟环境信息
poetry env list # 列出所有虚拟环境
poetry env use python3.11 # 切换 Python 版本多环境配置与私有仓库
# 配置私有 PyPI 仓库
poetry config repositories.private https://pypi.example.com/simple/
# 配置认证信息
poetry config http-basic.private username password
poetry config pypi-token.pypi my-token
# 从私有仓库安装
poetry add --source private internal-lib
# pyproject.toml 中配置源# pyproject.toml 中添加私有源
[[tool.poetry.source]]
name = "private"
url = "https://pypi.example.com/simple/"
priority = "supplemental"
# 使用国内镜像加速
[[tool.poetry.source]]
name = "aliyun"
url = "https://mirrors.aliyun.com/pypi/simple/"
priority = "primary"CI/CD 集成配置
# .github/workflows/test.yml - GitHub Actions 集成
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Install Poetry
run: pipx install poetry
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: poetry
- name: Install dependencies
run: poetry install --with dev,test
- name: Run linter
run: poetry run ruff check .
- name: Run type check
run: poetry run mypy src
- name: Run tests
run: poetry run pytest --cov=src --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
deploy:
needs: test
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and publish
run: |
pipx install poetry
poetry build
poetry publish -u __token__ -p ${{ secrets.PYPI_TOKEN }}# Dockerfile - 多阶段构建
FROM python:3.11-slim AS builder
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install poetry && \
poetry config virtualenvs.in-project true && \
poetry install --only main --no-root
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /app/.venv /app/.venv
COPY src/ src/
ENV PATH="/app/.venv/bin:$PATH"
CMD ["python", "-m", "src.main"]依赖冲突排查与解决
# 查看依赖树,定位冲突
poetry show --tree
# 查看过时的依赖
poetry show --outdated
# 使用 pip 的依赖分析工具
poetry add --group dev pipdeptree
poetry run pipdeptree --reverse --packages fastapi
# 锁文件校验
poetry check # 验证 pyproject.toml 配置
poetry lock --check # 验证 poetry.lock 与 pyproject.toml 一致
# 强制重新解析依赖(谨慎使用)
poetry lock --no-update # 仅更新 lock 文件
poetry update # 更新所有依赖到最新兼容版本# pyproject.toml 中处理常见依赖冲突
[tool.poetry.dependencies]
python = "^3.10"
# 当两个包需要不同版本的同一依赖时
# 使用 Python 的版本约束语法解决
numpy = "^1.24.0" # 兼容大部分科学计算库
pandas = "^2.0.0" # 需要 numpy >= 1.22.4
scikit-learn = "^1.3.0" # 需要 numpy >= 1.17.3, scipy >= 1.5.0
# 依赖排除(exclude)
requests = {version = "^2.31.0", extras = ["socks"]}
# 指定精确版本(最后手段)
problematic-lib = "2.1.0" # 精确版本,不做兼容性解析Monorepo 多包管理
# 根目录 pyproject.toml
[tool.poetry]
name = "my-monorepo"
version = "0.1.0"
[tool.poetry.dependencies]
python = "^3.11"
[tool.poetry.group.dev.dependencies]
# 根级别的开发工具
ruff = "^0.2.0"
pytest = "^8.0.0"# packages/core/pyproject.toml
[tool.poetry]
name = "my-core"
version = "1.0.0"
[tool.poetry.dependencies]
python = "^3.11"
pydantic = "^2.5.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"# packages/api/pyproject.toml
[tool.poetry]
name = "my-api"
version = "1.0.0"
[tool.poetry.dependencies]
python = "^3.11"
my-core = {path = "../core", develop = true}
fastapi = "^0.109.0"# 安装所有子包(开发模式)
cd packages/core && poetry install
cd ../api && poetry install # 自动安装 core 的开发版本
# 常用 workspace 命令
poetry run pytest # 在 api 包中运行测试
poetry show --tree # 查看当前包的依赖树插件与钩子配置
# pyproject.toml - 常用 Poetry 插件配置
[tool.poetry.plugins."poetry.application.plugin"]
# 自定义 Poetry 命令插件
# poetry-export 插件:导出 requirements.txt
# pip install poetry-plugin-export
# poetry export -f requirements.txt --output requirements.txt --without-hashes
# poetry export -f requirements.txt --output requirements-prod.txt --only main# 安装 Poetry 插件
poetry self add poetry-plugin-export # 导出 requirements.txt
poetry self add poetry-plugin-shell # 增强虚拟环境 shell
# 查看已安装的插件
poetry self show plugins
# 常用自检命令
poetry env info # 查看虚拟环境信息
poetry run python --version # 确认 Python 版本
poetry run which python # 确认使用的 Python 路径依赖安全审计
# 使用 pip-audit 审计依赖漏洞
poetry add --group dev pip-audit
poetry run pip-audit
# 使用 safety 审计
poetry add --group dev safety
poetry run safety check --full-report
# 使用 bandit 审计代码安全
poetry add --group dev bandit
poetry run bandit -r src/ -ll# pyproject.toml 中配置安全工具
[tool.bandit]
targets = ["src"]
skips = ["B101", "B601"] # 跳过特定规则
[tool.pip-audit]
# pip-audit 配置项目目录规范
my-project/
pyproject.toml # 项目配置(唯一入口)
poetry.lock # 依赖锁定文件(提交到 Git)
README.md # 项目说明
.env.example # 环境变量模板
.pre-commit-config.yaml # 预提交钩子配置
src/
my_package/
__init__.py
main.py
models.py
services/
api/
tests/
__init__.py
conftest.py
test_models.py
test_services.py
docs/
scripts/
migrate.py
seed_data.py优点
缺点
总结
Poetry 是当前 Python 项目管理的推荐方案,通过 pyproject.toml 和 poetry.lock 实现依赖管理的确定性和可复现性。新建项目应直接使用 Poetry,老项目可逐步迁移。CI/CD 中配合 --only main 参数确保生产镜像不包含开发依赖。
关键知识点
- pyproject.toml 是 Python 项目的现代标准配置文件(PEP 517/518)
- poetry.lock 必须提交到代码仓库,确保团队和 CI 环境一致性
- poetry add 自动更新 pyproject.toml 和 poetry.lock
- 依赖分组(group)替代传统的 requirements-dev.txt
项目落地视角
- 新项目直接使用 Poetry 初始化,不再使用 setup.py + requirements.txt
- CI/CD 流水线使用 poetry install --no-root --only main 安装最小依赖
- 定期运行 poetry update 更新依赖版本,在测试通过后提交 lock 文件
- 统一配置 pyproject.toml 中的工具链(ruff、mypy、pytest)
常见误区
- 将 poetry.lock 加入 .gitignore,导致不同环境依赖不一致
- 在生产镜像中安装全部依赖(含 dev/test),增加镜像体积和安全风险
- 手动编辑 poetry.lock 文件而非通过 poetry add/update 管理
- 忽略 poetry.toml(本地配置)和 pyproject.toml 的区别
进阶路线
- 学习 PDM、uv 等新一代包管理工具的对比和选型
- 研究 monorepo 场景下的 Poetry 多包管理
- 了解 Python 打包生态的演进(PEP 621、PEP 660)
- 探索 Dependabot/Renovate 自动依赖更新与 Poetry 的集成
适用场景
- 新项目初始化和依赖管理
- 团队协作需要确保开发、CI、生产环境依赖一致
- 需要发布到 PyPI 或私有仓库的 Python 包
落地建议
- 团队统一使用 Poetry,编写项目初始化文档
- pyproject.toml 中配置好所有工具链,新成员 clone 后一条命令即可开发
- CI 中使用 poetry check 验证 pyproject.toml 配置正确性
排错清单
- 运行 poetry check 检查 pyproject.toml 配置是否正确
- 确认 poetry.lock 与 pyproject.toml 是否同步
- 排查依赖冲突:poetry show --tree 查看依赖树
复盘问题
- 你的项目依赖管理是否统一?是否有混用 pip 和 poetry 的情况?
- 依赖更新频率如何?是否定期更新并运行测试?
- 生产镜像的依赖是否最小化?是否包含不必要的开发工具?
Poetry 与 pip 的对比
Poetry vs pip+requirements.txt 对比:
| 维度 | pip + requirements.txt | Poetry |
|------|----------------------|--------|
| 依赖声明 | 手动维护 requirements.txt | pyproject.toml 自动管理 |
| 锁文件 | 无(或手动 pip freeze) | poetry.lock 自动生成 |
| 依赖分组 | 需要多个文件(dev/prod) | 原生支持 group |
| 虚拟环境 | 手动创建和管理 | 自动创建和管理 |
| 依赖解析 | 不做完整解析,可能冲突 | 完整解析,保证兼容 |
| 构建发布 | 需要 setup.py + twine | 内置 build + publish |
| 版本管理 | 手动修改 | poetry version 命令 |
| 插件生态 | 无 | 支持插件扩展 |
何时选择 Poetry:
- 新项目优先选择 Poetry
- 团队协作需要确保环境一致性
- 需要发布到 PyPI 的库项目
- 微服务项目需要严格的依赖管理
何时仍然使用 pip:
- CI/CD 中快速安装(pip install -r requirements.txt 更快)
- 简单脚本不需要复杂依赖管理
- Docker 镜像中配合 poetry export 使用Poetry 常见问题与排错
# 问题1:依赖解析失败
# 症状:poetry add 卡住或报错 "SolverProblemError"
# 原因:依赖之间存在版本冲突
# 解决方案:
# 1. 查看哪个包引起冲突
poetry add <package> --verbose
# 2. 尝试指定更宽松的版本约束
poetry add "package>=1.0,<3.0"
# 3. 如果确实无法解析,可以尝试使用 pip 兼容模式
poetry config solver.lazy-first true
# 问题2:虚拟环境找不到
# 症状:poetry run python 找不到正确版本
# 解决方案:
poetry env list # 查看所有环境
poetry env use python3.11 # 切换到指定版本
poetry env remove python3.10 # 移除旧环境
poetry install # 重新安装依赖
# 问题3:锁文件冲突(Git merge 冲突)
# 解决方案:
git checkout --theirs poetry.lock # 使用对方的 lock
poetry lock --no-update # 重新生成
# 问题4:安装速度慢
# 解决方案:
# 1. 配置国内镜像
# 2. 使用并行安装
poetry config installer.max-workers 10
# 问题5:清理缓存
poetry cache clear pypi --all # 清除 PyPI 缓存
poetry cache clear --all . # 清除所有缓存从 requirements.txt 迁移到 Poetry
# 迁移步骤
# 1. 在现有项目目录初始化
cd existing-project
poetry init --no-interaction
# 2. 批量添加现有依赖
# 方式 A:逐个添加(推荐,Poetry 会自动解析)
while IFS= read -r line; do
# 跳过注释和空行
[[ "$line" =~ ^#.*$ ]] && continue
[[ -z "$line" ]] && continue
poetry add "$line"
done < requirements.txt
# 方式 B:使用 poetry import(需要插件支持)
# poetry add $(cat requirements.txt | grep -v '^#' | xargs)
# 3. 单独添加开发依赖
poetry add --group dev pytest black ruff mypy
# 4. 验证迁移结果
poetry install # 确认能正常安装
poetry run pytest # 确认测试通过
poetry check # 检查配置正确性
# 5. 提交 pyproject.toml 和 poetry.lock
git add pyproject.toml poetry.lock
git commit -m "迁移到 Poetry 依赖管理"
# 6. (可选)保留 requirements.txt 供 CI 使用
poetry export -f requirements.txt --output requirements.txt --without-hashespyproject.toml 高级配置
# pyproject.toml 高级配置参考
[tool.poetry]
name = "advanced-project"
version = "2.0.0"
description = "高级项目配置示例"
authors = ["张三 <zhang@example.com>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/example/project"
documentation = "https://docs.example.com"
keywords = ["web", "api", "fastapi"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Topic :: Software Development :: Libraries",
]
# 包发现配置
packages = [
{ include = "src/my_package" },
{ include = "src/extra", from = "lib" },
]
# 排除文件
exclude = [
"tests/**",
"docs/**",
"scripts/**",
]
# 包含非 Python 文件
include = [
{ path = "src/my_package/data/*.json", format = ["sdist", "wheel"] },
]
# 环境标记 — 条件依赖
[tool.poetry.dependencies]
python = "^3.10"
pywin32 = {version = "^306", markers = "sys_platform == 'win32'"}
uvloop = {version = "^0.19", markers = "sys_platform != 'win32'"}
# 可选依赖(extras)
[tool.poetry.extras]
database = ["sqlalchemy", "asyncpg"]
redis = ["redis"]
full = ["sqlalchemy", "asyncpg", "redis", "celery"]
# 脚本入口点
[tool.poetry.scripts]
my-server = "src.main:run_server"
my-worker = "src.worker:run_worker"
my-cli = "src.cli:app"
# Ruff 配置(替代 flake8 + isort)
[tool.ruff]
line-length = 100
target-version = "py310"
src = ["src", "tests"]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"B", # flake8-bugbear
"SIM", # flake8-simplify
"TCH", # flake8-type-checking
]
ignore = ["E501", "B008"]
[tool.ruff.lint.isort]
known-first-party = ["my_package"]
# Pre-commit 配置
[tool.pre-commit]
# 对应 .pre-commit-config.yaml 中的配置