跳到主要内容

发布包到公共市场

把可安装库发到 PyPI(Python Package Index),别人就能 pip install / uv add。本篇在「项目与包管理」之上,走通 构建 → 试发 → 正式发布;工具链与绿场推荐一致,默认用 uv。

先说结论:公共市场上传的是 发行物(sdist / wheel),不是整份源码仓库。包名在 PyPI 上全局唯一,同一版本一旦发布不可覆盖——错了只能发新版本。

发之前要齐什么​

项说明
可安装布局常见 src/包名/ 或扁平包目录;见「模块与包」
pyproject.toml[project] 元数据 + [build-system] 构建后端
README / license通常 readme = "README.md";许可证写进元数据
账号pypi.org 与建议先用的 test.pypi.org 各注册一份

uv init --package mylib 会生成带 [build-system] 的库项目骨架;已有应用项目若要发布,需补上构建配置(见下)。

最小可发布配置​

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

[project]
name = "example-pkg" # PyPI 上的包名(可含连字符)
version = "0.1.0"
description = "一句话说明"
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
authors = [{ name = "你的名字", email = "you@example.com" }]
dependencies = [
"requests>=2.31",
]

[project.urls]
Homepage = "https://github.com/you/example-pkg"

# 可选:命令行入口
# [project.scripts]
# example-cli = "example_pkg.cli:main"

注意:

  • import 名 与 PyPI 名 可以不同(如包名 example-pkg,代码里 import example_pkg)
  • 没有 [build-system] 时,uv build 会回退到旧式 setuptools;正式发布请显式声明(常用 hatchling)
  • 若项目用了 tool.uv.sources(本地路径 / git 源),发布构建建议:uv build --no-sources,避免别人装不上

构建发行物​

uv build
# 或:uv build --no-sources

产物默认在 dist/:

dist/
example_pkg-0.1.0.tar.gz # sdist
example_pkg-0.1.0-py3-none-any.whl # wheel

本地先装进干净环境试一把:

uv run --with ./dist/example_pkg-0.1.0-py3-none-any.whl --no-project -- \
python -c "import example_pkg; print(example_pkg.__file__)"

改版本​

uv version # 查看
uv version 0.2.0 # 设为指定版本
uv version --bump patch # 0.1.0 → 0.1.1
uv version --bump minor # 0.1.1 → 0.2.0

--bump 还支持 major / alpha / beta / rc / stable 等。发正式版前确认 pyproject.toml 里的版本与 changelog / git tag 一致。

先发 TestPyPI(强烈建议)​

TestPyPI 是练习场:流程与正式站相同,不影响真实索引。

  1. 在 test.pypi.org 注册,Account settings → API tokens 生成 token
  2. 配置命名索引(可选,便于复用):
[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true
  1. 上传:
uv publish --index testpypi --token pypi-...
# 或一次性:
uv publish --publish-url https://test.pypi.org/legacy/ --token pypi-...
  1. 从 TestPyPI 安装验证:
uv run --with example-pkg --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ --no-project -- \
python -c "import example_pkg"

依赖若只在正式 PyPI 上,常需要 --extra-index-url 指向正式源。

正式发布到 PyPI​

  1. 在 pypi.org 创建项目(或首次上传时自动创建)
  2. 生成 API token(Scoped 到该项目更稳妥)
  3. 构建并上传:
uv build --no-sources
uv publish --token pypi-...
# 也可用环境变量:UV_PUBLISH_TOKEN

PyPI 已不支持 账号密码上传;token 等价于用户名 __token__ + 密码为 token。

装回验证:

uv run --with example-pkg --no-project --refresh-package example-pkg -- \
python -c "import example_pkg"

CI:Trusted Publishing(推荐)​

在 GitHub Actions 等环境发版时,优先用 PyPI 的 Trusted Publisher(OIDC),不必把长期 token 放进 Secrets:

  1. 在 PyPI 项目设置里添加 Trusted Publisher(绑定仓库、workflow、环境)
  2. workflow 给予 id-token: write,检出代码后执行 uv build + uv publish
  3. 无需 --token;uv 会使用短期身份令牌

细节以 PyPI Trusted Publishers 与 uv 打包指南 为准。

发布检查表​

  1. 包名在 PyPI / TestPyPI 上可注册且不冲突
  2. version 未在目标索引上用过(同版本不可覆盖)
  3. README、依赖、requires-python 在构建后的元数据里正确
  4. uv build --no-sources 成功;wheel 在干净环境可 import
  5. 先 TestPyPI,再正式 PyPI
  6. token / Trusted Publisher 权限最小化;token 不进仓库
  7. 打 git tag(如 v0.1.0)与发布版本对齐,便于追溯

常见坑​

现象常见原因
上传报版本已存在该版本曾发过;只能升版本
别人装上后 import 失败包布局 / hatch 包路径未配好;先本地装 wheel 验证
构建依赖解析异常发布时未加 --no-sources,带上了仅本地可用的源
TestPyPI 装依赖失败传递依赖不在 TestPyPI;加正式源作 extra index
中途失败部分文件已上传PyPI 上可重跑相同命令;相同文件会被忽略

私有源(公司 Artifactory 等)同样用 [[tool.uv.index]] 配 publish-url,再 uv publish --index <name>。本篇只覆盖公共市场;私有策略跟团队规范。

要点​

  1. 公共市场 = PyPI;先 TestPyPI 再正式站
  2. 声明用 pyproject.toml,构建用 uv build,上传用 uv publish
  3. 版本不可覆盖;发错就升版本
  4. 鉴权用 API token 或 CI Trusted Publishing,不要用账号密码
  5. 依赖与环境管理仍见「项目与包管理」