关于 python-packaging
帮助将 Python 库构建为可安装发行包并验证产物的技能。它涵盖项目元数据、构建后端选择、基于 uv 的依赖管理,以及 sdist 和 wheel 检查。
主要功能
- 01以 pyproject.toml 集中管理打包配置
- 02选择 PEP 517 构建后端
- 03采用 src/ 布局和 uv
- 04验证元数据与构建产物
- 05检查 wheel 内容和导入
适用场景
- 01为新 Python 库配置打包
- 02迁移旧式打包配置
- 03验证 wheel 和 sdist 内容
- 04排查软件包文件缺失
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ paldom/ python-skills --skill python-packaging安装后在 Claude Code 中输入/python-packaging确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/build-backends.mdscripts/check_wheel.py详细介绍
python-packaging
让 Python 库可安装、可构建,并验证构建产物:使用一个 pyproject.toml 作为唯一事实来源,审慎选择 PEP 517 构建后端,采用 src/ 布局,使用 uv 管理依赖并提交锁文件,同时检查 sdist 和 wheel 的实际内容,而不是想当然。本技能可避免以下问题:声明的文件(py.typed、数据文件)悄无声息地从 wheel 中缺失;构建后端无法处理项目的布局或扩展模块;尚未验证依赖解析结果就切换 Poetry 的构建后端;以及调用 pip 导致 uv 管理的环境失去同步。
不适用场景
- 发布到 PyPI、升级版本、编写变更日志、创建 git 标签、可信发布——如果已安装,请使用 python-release 技能。验证完构建产物后即停止。
- 选择或配置类型检查器,以及是否要打包
py.typed——使用 python-typing 技能。本技能只验证现有的py.typed是否确实进入了构建出的 wheel。 [tool.ruff]配置——使用 python-lint。pytest/coverage 配置——使用 python-testing。GitHub Actions——使用 python-ci。依赖漏洞审计——使用 python-supply-chain。- 只需依赖项的单文件脚本——PEP 723 内联脚本元数据加上
uv run script.py即可,无需打包。
工作流
1. 评估现有情况
ls setup.py setup.cfg MANIFEST.in requirements*.txt poetry.lock Pipfile uv.lock 2>/dev/null
grep -n "build-backend\|tool.poetry\|dependency-groups\|optional-dependencies" pyproject.toml 2>/dev/null
find . -name "*.pyx" -o -name "*.c" -o -name "Cargo.toml" | grep -v .venv | head
更改前记录三项事实:(a)是否有原生扩展,还是纯 Python——这决定构建后端;(b)是扁平布局还是 src/ 布局;(c)仓库现有的开发依赖约定([dependency-groups]、[project.optional-dependencies] 下的 dev extra,或旧式 [tool.uv] dev-dependencies)——沿用该约定,或整体迁移,切勿混用。
2. 选择构建后端
| 构建后端 | 适用场景 | 避免场景 |
|---|---|---|
uv_build | 纯 Python 库;无需配置即可发现 src/,严格验证元数据 | 含任何 C/Cython/Rust 扩展模块(不支持);需要基于 VCS 标签动态确定版本 |
hatchling | 需要通过 hatch-vcs 根据 git 标签动态确定版本;通常被视为通用指南中的默认选择;插件生态丰富 | 希望使用 uv_build 更严格的验证和零配置 |
setuptools | 扩展模块(ext_modules)、复杂的旧式配置 | 从零开始的纯 Python 项目——验证较宽松,自动发现可能带来意外 |
新项目不要采用 poetry-core;整个生态已统一使用 PEP 621 [project] 元数据。注意,uv build 是与构建后端无关的 PEP 517 前端——它驱动 hatchling 或 setuptools 同样得心应手,因此采用 uv 作为项目管理器并不意味着必须迁移构建后端;应将迁移视为独立步骤并加以验证。
[build-system]
requires = ["uv_build>=0.12,<0.13"]
build-backend = "uv_build"
为 uv_build 保留版本上限:它遵循 uv 的版本策略,次版本更新可能包含破坏性更改,因此不设上限的依赖要求可能会在新版本发布当天导致构建失败。Hatchling 和 setuptools 的兼容性规范更保守;通常会设置最低版本(例如 hatchling>=1.27)。
风险说明:uv 整体仍处于 1.0 之前,但 uv_build 构建后端在 PyPI 上明确标记为 Production/Stable,并且自 2025 年年中以来一直是 uv init 的默认后端——不要将两者混为一谈,也不要反向混淆。
完整的权衡说明、[tool.uv.build-backend] 键以及各构建后端的文件包含配置,请参阅:references/build-backends.md。
3. 设置包布局(src/)
my-package/
├── pyproject.toml
├── README.md
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── py.typed # only if the project already ships one
└── tests/
库应使用 src/:这样,测试和工具导入的就是已安装的包。采用扁平布局时,即使构建出的 wheel 有问题或缺少文件,import my_package 仍会解析到工作目录中的副本,导致打包错误一直被掩盖,直到发布时才暴露。对于新项目,uv init --lib my-package 一步即可生成 src/、py.typed 和 uv_build 构建后端。
uv_build 要求使用 src/<module>/__init__.py,且模块名称必须与规范化后的项目名称相同。如果导入名称不同,请声明:
[tool.uv.build-backend]
module-name = "actual_import_name"
4. 编写 [project] 元数据
可发布的最小配置集——validate-pyproject(步骤 6)会据此检查:
[project]
name = "my-package"
version = "0.1.0"
description = "One line, benefit first."
readme = "README.md"
license = "MIT" # SPDX expression (PEP 639; needs a current backend)
requires-python = ">=3.10"
authors = [{ name = "Your Name", email = "[email protected]" }]
dependencies = ["httpx>=0.27"]
classifiers = ["Programming Language :: Python :: 3"]
[project.scripts]
my-cli = "my_package.cli:main" # callable must take no required args; parse sys.argv inside
[dependency-groups]
dev = ["pytest>=8"]
开发工具应放在 PEP 735 [dependency-groups] 中(由 uv add --dev 写入),而不是放在已发布的 extra 中:消费者可以从 PyPI 安装 [project.optional-dependencies] 下的 extras,但不能安装依赖组。dev extra 会把你的工具链泄漏给下游解析器。extras 用于可选的运行时功能(例如 my-package[cli])。
5. 使用 uv 管理项目
uv init --lib my-package # new projects only (uv >= 0.12 also declares a build system for plain `uv init`; `--no-package` opts out)
uv add httpx # runtime dep -> [project.dependencies] + uv.lock
uv add --dev pytest # dev dep -> [dependency-groups].dev
uv sync # build .venv from uv.lock (--no-dev in production)
uv lock --upgrade-package httpx # targeted upgrade; rest of the lockfile intact
uv run pytest # run tools in the env, no activation
提交 uv.lock;不要手动编辑它。每次运行 uv run 前,uv 都会检查锁文件是否与 pyproject.toml 保持同步,以及环境是否与锁文件保持同步,这正是结果可复现的原因。不要在 uv 项目中调用 pip——它会在锁文件不知情的情况下修改 .venv。
迁移:
- 从 setup.py/setup.cfg 迁移——先逐一梳理所有配置段:
[options]中的元数据移到[project],package_data/MANIFEST.in指令转换为后端的文件包含配置,工具配置段([tool:pytest]、[flake8]、[mypy])移到pyproject.toml或对应工具的配置文件中。先用旧配置构建一次,再用新配置构建一次,对比 wheel/sdist 文件列表,然后再删除setup.py、setup.cfg和MANIFEST.in(步骤 6–8 会验证结果)。例外:带有 C 扩展的 setuptools 项目仍需要通过setup.py配置ext_modules——只保留这部分。 - 从 Poetry 迁移——运行
uvx 'migrate-to-uv==0.12.0'(固定版本:不固定版本的uvx会执行最近发布到 PyPI 的版本,这既无法复现,也会造成供应链安全漏洞;应有意升级固定版本)。该工具会将[tool.poetry.*]映射到 PEP 621,并将poetry.lock映射为uv.lock。最后再将[build-system]从poetry-core切换出去,确保uv lock && uv sync能顺利解析——先切换后端,是同时调试两项迁移的典型方式。 - 从 requirements.txt 迁移——运行
uv add -r requirements.txt(如果有constraints.txt,再加上-c constraints.txt),然后删除该文件。如果下游工具仍需要它,使用uv export生成。
6. 验证元数据
uvx --from 'validate-pyproject[all]==0.26' validate-pyproject pyproject.toml
即使 pyproject.toml 能解析为有效 TOML,也仍可能违反打包 PEP 模式(许可证格式错误、键位置不当、入口点格式错误)——这些问题是格式化工具和 TOML 解析器无法发现的。每次修改元数据后都运行此检查;它开销很小。
7. 构建
uv build # sdist (.tar.gz) + wheel (.whl) into dist/
在任何前端下调试 uv_build 时,设置 RUST_LOG=uv=debug。无 uv 后备方案(仅在 uv 不可用的环境中使用一次):python -m pip install build==1.6.0 && python -m build 会生成相同的构件,而 pip install -e . --group dev(pip >= 25.1;依赖组不是 extras)可替代开发安装中的 uv sync,但会牺牲锁文件的可复现性。
8. 验证构件——绝不跳过
运行随附的检查器(相对于此技能文件夹的路径):
python3 "${CLAUDE_SKILL_DIR}/scripts/check_wheel.py" --project-root /path/to/project /path/to/project/dist
该检查器只读;遇到以下情况时会以非零状态码退出并输出 ERROR 行:wheel 缺少 METADATA/WHEEL/RECORD;源代码树中存在的 py.typed 未包含在 wheel 中;tests/、docs/ 或类似目录泄漏到 wheel 中;或 sdist 缺少 pyproject.toml。手动检查的等效命令:unzip -l dist/*.whl 和 tar -tzf dist/*.tar.gz。
然后对 wheel 本身(而不是源代码树)进行冒烟测试:
uv run --isolated --no-project --with dist/my_package-0.1.0-py3-none-any.whl \
python -c "import my_package; print(my_package.__name__)"
输出规范——完成后的预期状态
pyproject.toml是唯一的打包配置;setup.py/setup.cfg/MANIFEST.in/requirements.txt均已删除(若setup.py仅用于ext_modules,则可保留)。validate-pyproject以 0 状态码退出。uv build同时生成dist/*.tar.gz和dist/*.whl。scripts/check_wheel.py以 0 状态码退出——已验证 wheel 中包含预期文件(包括源代码附带py.typed时的该文件);没有测试或文档泄漏。- 构建出的 wheel 可在隔离环境中导入。
uv.lock存在且已提交。
失败模式与注意事项
- 仓库中有
py.typed,但 wheel 中缺失。 不能假设任何后端都会打包任意非.py文件:setuptools 需要[tool.setuptools.package-data],hatchling 仅在 wheel 目标指向包目录时才会打包其中的文件, uv_build 会纳入模块根目录下的文件。使用缺少该标记的 wheel 的用户会悄悄地将整个 API 都视为Any——请检查构建产物,而非源代码树。各后端的代码片段见 references/build-backends.md。 - uv_build 报“找不到模块”或元数据验证失败。 默认情况下,它会在
src/<module>/或./<module>/中查找一个以规范化项目名称命名的模块。如需声明不同的名称、多个模块或命名空间包,请使用[tool.uv.build-backend]中的module-name = [...]/module-root/namespace(建议使用显式列表,而非会禁用安全检查的namespace = true)。 - uv 发布新版本后构建失败。
requires = ["uv_build"]未限定版本范围——请恢复>=X,<X+1限定(步骤 2)。 - Poetry 迁移未完成。
[build-system]已切换,但实际依赖列表仍留在[tool.poetry.dependencies]中——构建产物会包含错误或缺失的元数据。先处理元数据,再验证依赖解析,最后切换后端。 - Heisenbug 环境。 有人在 uv 项目中运行了
pip install;下一次uv run会悄悄还原更改。两个方向看起来都像是不稳定。只有uv add/uv sync会修改环境。 - 测试通过,但已安装的包无法使用。 扁平布局导入了工作目录中的副本,掩盖了 wheel 中缺失的文件。
src/布局加上步骤 8 的 wheel 冒烟测试可以发现此问题。 tests/被打包进 wheel。 setuptools 的自动发现会纳入任何可导入的顶层目录。请限制发现范围([tool.setuptools.packages.find] where = ["src"]),或改用src/布局;check_wheel.py会标记此类泄漏。- 开发用额外依赖被发布到 PyPI。 将开发工具声明为
[project.optional-dependencies] dev,会让每位用户都能安装此额外依赖组。请使用[dependency-groups]配置工具;额外依赖组仅用于可选的运行时功能。 - TOML 有效,但元数据无效。 格式化工具和 TOML 解析器接受的文件仍可能违反打包架构的要求——这正是步骤 6 的用途。
- 性能传言。 关于 uv_build 可大幅提升构建速度的说法,依据的是从业者的博客文章,而非受控基准测试。请仅将其视为趋势,不要将具体数字当作事实传播。
随附文件
- references/build-backends.md — 深入介绍后端的权衡、
[tool.uv.build-backend]参考、各后端的文件纳入规则(py.typed、数据文件)、迁移路径,以及从旧工具到 uv 的命令对照表。 scripts/check_wheel.py— 只读的 sdist/wheel 验证工具;每次构建后运行;失败时以非零状态退出,并输出机器可读的ERROR行。