登录
首页 >  文章 >  python教程

Python 打包发布实战:别把运行依赖和开发依赖混在一起

来源:Python 博主原创

时间:2026-06-04 14:58:47 479浏览 收藏

Python 项目一开始常常只有一个 requirements.txt。等服务变成内部 SDK、命令行工具、后台任务包以后,问题就来了:运行环境装进 pytest 和 black,构建机和线上版本不一致,私有源 token 散落在脚本里,发布出来的 wheel 在另一台机器上装不上。

这篇文章不讲“怎么上传 PyPI”的入门流程,而是按生产发布事故来写:如何用 pyproject.toml 收口元数据和构建后端,如何拆运行依赖、开发依赖和构建依赖,为什么构建隔离能帮你发现隐性依赖,以及上线前怎么检查 wheel、sdist、锁文件和私有仓库凭据。

Python 打包发布治理思维导图
治理思路:元数据、依赖分组、构建隔离、锁定文件、发布凭据和回滚路径要一起看。

业务场景:内部 SDK 发布后线上装不上

假设团队维护一个订单 SDK,FastAPI 服务、Celery worker、自动化脚本都会依赖它。最初项目里只有一个 requirements.txt,里面混着运行依赖、测试工具、格式化工具和构建工具。

requests
pydantic
pytest
black
setuptools
twine
uvicorn

本地开发没问题,CI 也能跑。但发布到私有仓库后,线上安装时发现依赖过重,有些机器还因为构建依赖缺失失败。根因很简单:我们没有区分“包运行需要什么”“开发测试需要什么”“构建这个包需要什么”。

第一步:把项目元数据放进 pyproject.toml

现代 Python 包应该让 pyproject.toml 成为入口。它不是某个工具的私货,而是 Python 打包生态的统一配置载体。最小可用结构通常包含 [project][build-system]

[project]
name = "order-sdk"
version = "0.8.3"
requires-python = ">=3.11"
dependencies = [
  "requests>=2.32",
  "pydantic>=2.7",
]

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

dependencies 只放运行时真正需要的包。pytest、ruff、mypy、twine、build 这类工具不该被线上服务跟着安装。依赖越清楚,供应链扫描、镜像体积、冷启动和故障排查都会简单不少。

Python 从 pyproject 到可发布包流水线
发布流水线:解析 pyproject、安装测试依赖、构建隔离环境、生成 wheel/sdist、检查后上传。

第二步:开发依赖用 dependency-groups 分出来

如果你的工具链支持 dependency-groups,可以把测试、格式化、文档依赖拆开。这样 CI 可以按需安装,线上包只声明运行依赖。

[dependency-groups]
test = [
  "pytest>=8.2",
  "pytest-cov>=5",
]
lint = [
  "ruff>=0.5",
  "mypy>=1.10",
]
release = [
  "build>=1.2",
  "twine>=5",
]

我的习惯是:运行依赖写进 [project].dependencies,测试依赖进 test,代码质量工具进 lint,发布工具进 release。不要因为“CI 方便”就把所有东西塞回一个文件。

第三步:用构建隔离暴露隐性依赖

最怕的构建问题,是本地能构建,干净环境不能构建。原因通常是你机器上已经装过某个包,构建脚本偷偷依赖它,但 [build-system].requires 没声明。

python -m pip install --upgrade build
python -m build

python -m build 会在隔离环境中构建 sdist 和 wheel。它能逼你把构建依赖写清楚,也能避免宿主环境污染构建结果。CI 里我会从空环境开始构建,而不是复用开发虚拟环境。

Python 依赖分组和打包风险写法对照
代码审查重点:运行依赖、测试依赖、构建依赖混放,会让发布和线上安装都不可预测。

第四步:发布前检查 wheel 和 sdist

很多包不是上传失败,而是上传成功以后才发现缺文件、版本号错、元数据错。发布前至少做三件事:

python -m build
python -m twine check dist/*
python -m pip install --force-reinstall dist/order_sdk-0.8.3-py3-none-any.whl

如果包里有模板、配置样例、迁移文件、类型声明文件,别只看源码目录,要实际安装 wheel 后跑一次最小用例。wheel 能安装,不代表文件齐;文件齐,也不代表导入路径正确。

锁文件不是发布包元数据的替代品

锁文件的价值是让应用部署可复现,而不是让库包把所有下游版本钉死。内部应用可以锁定完整依赖树;内部 SDK 通常只声明合理版本范围,让使用方在自己的应用锁文件里统一解析。

如果团队已经开始使用 pylock.toml 或工具生成的 lock 文件,我建议把它纳入应用仓库 CI:构建镜像、跑测试、生成 SBOM、部署回滚都基于同一份锁定结果。库包则重点保证 dependencies 语义正确,不随手写死过窄版本。

私有源发布:token 不进代码库

发布到私有仓库时,凭据管理比命令本身重要。CI 里使用最小权限 token,只允许上传目标项目;token 存在 CI secret,不写进 pyproject.toml、脚本和镜像层。

python -m twine upload   --repository-url "$PRIVATE_PYPI_URL"   -u "__token__"   -p "$PRIVATE_PYPI_TOKEN"   dist/*

发布脚本要打印版本、仓库地址、构建产物摘要,但不要打印 token。出了问题时,日志应该帮你定位版本和产物,不应该帮别人拿到发布权限。

上线检查清单

  • [project].dependencies 只包含运行时依赖,不混入测试、格式化、发布工具。
  • [build-system].requires 完整声明构建后端和构建依赖。
  • 开发依赖按 testlintrelease 分组,CI 按需安装。
  • 构建必须在干净隔离环境执行,不能依赖本机已安装包。
  • 发布前执行 twine check,并从 wheel 安装跑最小导入用例。
  • 应用部署使用锁文件保证可复现,库包依赖范围不要无脑钉死。
  • 私有源 token 使用 CI secret 和最小权限,不进代码库、不进镜像层。

总结

Python 打包发布的核心不是命令,而是边界。运行依赖、开发依赖、构建依赖、部署锁定、发布凭据,各自解决不同问题。把它们混在一个文件里,短期省事,长期会把 CI、线上安装和供应链治理都变得很脆。

我的经验是,先把 pyproject.toml 写干净,再让 CI 用干净环境构建,最后用 wheel 安装结果验证发布物。这样一个 Python 包从本地开发到私有源,再到线上服务安装,才算真正可控。

声明:本文转载于:Python 博主原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>