更省心的 Python 项目维护
在维护 PyPI 包和项目的过程中,我常遇到两大痛点:
- 上传发布繁琐:过去,上传 PyPI 包通常需要配置 PyPI Token,有一定的安全隐患,同时还需要在本地或 CI 中打包上传,尤其是当涉及
.whl
文件的项目还需要额外的环境配置,工作量会增加不少。 - 固定子依赖耗时:稍大型的 Python 项目,子依赖往往较多,不固定版本会有上游库的不兼容风险。传统工具如 PDM、Poetry 等在锁定依赖时速度不尽人意,在开发过程中不得不忍受漫长的耗时甚至固定失败。在 2021 年是我用 Poetry 固定一个项目花了 10 分钟还失败了,自此之后就不用了。
近两年内,这些问题有了舒服的解决方案。
PyPI 支持 Trusted Publishers#
2023 年 PyPI 宣布,支持 Trusted Publishers 的无密码发布机制。这一机制避免了对 PyPI Token 的依赖,只需在 PyPI 后台填写 GitHub Actions 的基本信息,配置过程非常简单,省去许多维护工作。
uv 的发布#
2024 年,uv
的发布 则彻底改善了 Python 项目固定子依赖的效率问题。之前,PDM、Poetry 等工具虽然好用,但锁定依赖的速度相对较慢,实际项目中持续集成的时间较长。我在 GitHub 上建立了一个对比仓库,用于展示各工具固定我参与过的一个项目的依赖锁定速度(数据基于 Actions 执行时间):
工具 | 耗时 |
---|---|
Poetry | 43s |
PDM | 1m 7s |
uv | 2s |
uv 基本上可以在秒级别固定,开发过程几乎无感知的。
更进一步的,则是一些 Python 项目本身依赖机器学习模型,这时候避免不了依赖 PyTorch 及关联的生态项目。这时候会有一些问题:
- PyTorch 的 tag 即便都是 CPU 版本,在不同的平台也不一样,所以需要根据平台来选择不同的版本。
- PyTorch 关联的第三方生态项目对其版本要求非常严格,而且有时候遇到安装的版本不对,不会有直观的报错,而是让人摸不到头脑的
Segmentation fault (core dumped)
。
所以我目前在工作中也将 uv
用于这类项目,通过 uv
的 index
机制,可以很方便地指定 PyTorch 的源。比如如下是一个使用 uv
的项目的 pyproject.toml
文件:
[project]
name = "foo-bar"
version = "0.1.0"
description = ""
readme = "README.md"
requires-python = "==3.12.*"
dependencies = [
"torch==2.5.0; platform_system == 'Darwin'",
"torch==2.5.0+cpu; platform_system != 'Darwin'",
]
[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cpu"
explicit = true
[tool.uv.sources]
torch = { index = "pytorch" }
另外,利用 tool.uv.find-links
配置第三方项目自行发布维护的源,确保安装到正确的版本。这样机器学习相关的项目,可以让本地开发环境(macOS)和线上环境(Linux)尽可能一致(不过涉及比较多的项目细节就不展示了)。这样 Dockerfile 写起来也十分清爽了,不用依赖特定的安装顺序,直接 uv sync --compile --frozen
即可。
进一步的,则是 uv 实际上可以管理 Python 包的构建/打包/发布等工作,并且也支持 PyPI 的 Trusted Publishers 机制,所以可以一步到位,省去了很多维护工作。如下是 Apopy 项目用于发布到 PYyPI 的 GitHub Actions 配置文件:
name: publish
on:
push:
tags:
- "v*.*.*"
jobs:
pypi-publish:
name: upload release to PyPI
runs-on: ubuntu-latest
environment:
name: PyPI
url: https://pypi.org/p/apopy
permissions:
id-token: write
env:
TRUSTED_PUBLISHING: always
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: "pyproject.toml"
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- run: make build
- run: uv publish