uv 工程手册:从 Python 环境到通用锁文件与可复现 CI
把 pip install 换成 uv pip install,通常几分钟就能看到速度变化;把一个团队的 Python 工程迁到 uv,却远不止替换命令。解释器由谁提供,依赖写在 requirements 还是 pyproject.toml,锁文件覆盖哪些平台,私有索引如何认证,CI 是否允许自动改锁,原生扩展从 wheel 还是 sdist 构建,这些问题决定环境能否被别人重建。
uv 同时提供 Python 版本管理、项目管理、通用锁文件、workspace、构建、工具运行和一套 pip 风格接口。能力集中能减少工具拼装,但也更容易把不同层级混为一谈:resolver 负责求解,lock 保存解析结果,sync 改变当前环境,cache 只复用外部输入;跨平台、私有源、CI 和迁移治理都建立在这四层没有混淆的前提上。
当环境恢复开始依赖个人记忆
团队若同时靠 pyenv 找解释器、venv 隔离目录、pip 安装、pip-tools 锁定、build 打包,再用零散脚本拼接 CI,uv 可以把 Python、项目声明、解析、环境同步和构建收拢到同一条命令链。收益不只是安装更快,而是让“哪个解释器解析了哪份声明,最终同步出什么环境”可以复查。
若现有 requirements 流程已经稳定,团队暂时只需要更快的安装器,可以先采用 uv pip,不必立刻迁移项目模型。反过来,依赖 Poetry 插件、特殊发布流程或平台专用解析的项目,也不能把“命令相似”当成逐项等价。系统级解释器分发、私有制品库服务端和发布审批仍由各自平台管理,uv 负责的是项目侧恢复链路。
动手前先查清解释器和依赖来源
准备非生产开发机、可删除目录和一种已批准的安装渠道。先记录现状:
Get-Command uv,python -ErrorAction SilentlyContinue | Format-Table Name,Source
uv --version
python --versioncommand -v uv python python3 || true
uv --version
python --version不要在一个来源不明的已激活虚拟环境中建立基线。还要明确团队支持的 OS、CPU 架构和 Python 范围,以及是否允许 uv 下载 managed Python。若企业只允许内部解释器分发,需在项目基线中禁用自动下载并提供受控 Python 路径。
standalone installer 与固定版本
uv 的 standalone installer 不依赖已有 Python,并支持 macOS、Linux 与 Windows。执行远程脚本前应先下载或查看内容,再把通过团队验证的版本写入安装变量:
UV_VERSION="<已批准版本>"
curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" | less
curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" | sh
uv --versionWindows PowerShell 可先检查脚本,再执行:
$UvVersion = "<已批准版本>"
irm "https://astral.sh/uv/$UvVersion/install.ps1" | more
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/$UvVersion/install.ps1 | iex"
uv --version远程脚本执行策略必须服从企业安全基线。更严格的环境应从官方 GitHub Releases 下载二进制,在制品准入后由内部软件分发。使用 standalone installer 安装时可执行 uv self update;若由 WinGet、Homebrew、pipx 等安装,则由对应包管理器升级,避免双重管理。
包管理器与隔离安装
安装页还列出了 PyPI、Homebrew、WinGet、Scoop、Docker、GitHub Releases 与 Cargo 等入口。常见的隔离入口包括:
winget install --id=astral-sh.uv -ebrew install uv
pipx install uv不建议把 uv 直接装进业务项目 .venv,否则删除环境也会删除环境管理工具。团队模板应记录安装渠道、版本和可执行文件来源;仅写 uv >= 某版本 不能保证解析结果长期一致。
卸载与升级回滚
升级前保留旧安装包或包管理器版本,并在代表性 lock 上执行 uv lock --check 和 uv sync --locked。若新版本不能读取旧 lock、解析结果异常或平台矩阵失败,恢复旧 uv 二进制并恢复升级前 uv.lock,不要用新版本重写 lock 后再假装兼容。
核心模型:声明、解析、锁定与同步
uv 的项目结构至少包含四个不同对象:
pyproject.toml -> 人维护的项目元数据与依赖约束
uv.lock -> resolver 生成的跨环境候选与制品证据
.venv -> 当前机器上按锁文件选择并安装后的环境
uv cache -> 可复用下载、构建与归档缓存,不是项目真相uv add 会更新项目声明、lock 和环境;uv lock 只处理解析与锁;uv sync 根据项目与 lock 同步环境;uv run 默认会在运行命令前确保 lock 与环境处于可用状态。锁定与同步的命令语义会直接决定命令是否改写 lock 或删除额外包,团队不能把本地自动更新带进 CI。
--locked、--frozen 与 --no-sync
--locked:检查 lock 是否与项目元数据一致,不一致即失败,不改 lock。--frozen:直接使用现有 lock,不检查它是否过期;适合明确承担该风险的只读场景。--no-sync:不检查或更新环境;只适用于环境已由前一步可靠同步的场景。
CI 的常规入口应优先 uv sync --locked,因为它同时要求声明与 lock 一致,并按 lock 建环境。--frozen 不是更严格,而是跳过新旧一致性检查;不要仅凭名字误用。
通用锁文件不等于所有平台必然可安装
uv.lock 可以表达 marker、多个 Python 和平台候选,但某个包仍可能没有目标平台 wheel,或其元数据无法覆盖未来 Python。解析与 lock schema还规定:schema 变化可能让较旧的 uv 拒绝较新的 lock。架构师应区分“解析结果可表达多个环境”和“每个目标环境都已安装验证”。支持 Windows、Linux、macOS 或 x86_64、ARM64 时,CI 必须真的跑对应矩阵。
tool.uv.environments 可限制 lock 需要支持的环境,required-environments 可要求某些环境必须有可用 wheel。缩小范围会降低解析复杂度,但也明确放弃其他平台;该取舍必须进入项目支持矩阵。
下面创建一个可删除的 package 项目,固定 Python 约束,添加测试依赖并验证构建:
uv init --package --python 3.12 uv-demo
cd uv-demo
uv add httpx
uv add --dev pytest
uv lock
uv sync --locked
uv run python -c "import httpx; print(httpx.__version__)"
uv run pytest
uv build
uv lock --check
uv sync --check预期出现 pyproject.toml、uv.lock、.python-version、.venv 与 dist/。uv build 应生成 sdist 和 wheel。最小验证不是看命令退出码就结束,还应检查:
uv python find
uv tree
uv cache dir
python -c "import zipfile,glob; p=glob.glob('dist/*.whl')[0]; print(zipfile.ZipFile(p).namelist())"清理环境和构建产物后重建:
rm -rf .venv dist
uv sync --locked
uv run pytestPowerShell 清理使用 Remove-Item -Recurse -Force .venv,dist,且只在确认当前目录为练习项目后执行。不要用 uv cache clean 代替项目环境清理;全局 cache 可能被其他工作区共享。
Python 管理与项目环境
managed Python 的边界
uv python install 可安装 uv 管理的 Python,uv python pin 可把版本请求写入 .python-version。这解决“项目需要哪个 Python”,但不自动解决企业发行版、FIPS、系统库、原生调试符号和安全补丁责任。若组织不允许自动下载:
UV_PYTHON_DOWNLOADS=never并在 CI 显式提供已批准解释器。执行 uv python find 和 uv run python -c "import sys; print(sys.executable)",确认实际解释器,而不是只看 .python-version。
项目环境与激活环境
uv 项目默认使用项目根的 .venv,编辑器容易发现。uv 项目命令通常不依赖 shell 是否激活;这能减少“终端看似激活了另一个环境”的歧义。项目环境由 lock 驱动时,不要再用 uv pip install 手工塞入项目依赖,应通过 uv add/remove 同步修改声明和 lock。
若必须操作指定环境,使用 --python 或项目配置明确目标。直接设置 UV_PROJECT_ENVIRONMENT 指向系统 Python 风险很高,可能覆盖系统环境;只应在隔离镜像或可销毁前缀中使用。
一个可审查的最小 pyproject.toml 可以从标准元数据开始:
[project]
name = "uv-demo"
version = "0.1.0"
requires-python = ">=3.11,<3.14"
dependencies = [
"httpx>=0.28,<0.29",
]
[dependency-groups]
dev = [
"pytest>=8,<9",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"运行入口统一写成:
uv sync --locked
uv run pytest
uv build提交 pyproject.toml、uv.lock 和必要的 .python-version,忽略 .venv、dist 和本地 cache。不要提交从个人目录复制的虚拟环境;绝对路径、平台 tag 和链接方式会让它不可移植。
workspace
uv workspace 中每个 member 有自己的 pyproject.toml,整个 workspace 共享一个 lock。uv lock 作用于整个 workspace,uv sync 和 uv run 默认从根处理,也可用 --package 指定成员。这适合依赖需要统一解析的 Python 多包仓库,但意味着一个成员改依赖可能改动全局 lock。
[tool.uv.workspace]
members = ["packages/*"]团队应给根 lock 设置 owner,CI 同时验证全 workspace 和关键 member。若某成员需要其他成员不支持的 Python,就要在独立环境中使用 uv pip 验证;这说明 workspace 不是所有异构项目的强制容器。
uv pip 兼容边界
uv pip 提供熟悉的 install、compile、sync 和 check,适合低风险迁移现有 requirements 工作流:
uv venv
uv pip compile requirements.in --universal -o requirements.txt
uv pip sync requirements.txt
uv pip check但“pip-compatible”不是“实现细节完全相同”。uv 与 pip 的兼容差异包括:uv 不支持 --user,不会像 pip 那样在权限不足时回退到 user site;uv pip check 的诊断集合不同;binary/source 选择、直接 URL、editable、构建约束、环境发现和多 index 策略也可能不同。
迁移前要抓取现有命令、环境变量和 requirements 特性,逐项对照兼容文档。只要依赖安装依赖 pip 的未声明行为、私有补丁或特定 resolver 结果,就先保留 pip 入口并增加 uv 并行 job。
Index、认证、代理与 CA
私有 index 应显式命名
uv 支持在 pyproject.toml 中声明 index 和包来源,但仓库文件只保存无凭证 URL。凭证通过 CI secret、环境变量、netrc、uv auth 或 keyring provider 注入。示意:
[[tool.uv.index]]
name = "internal"
url = "https://packages.example.invalid/simple"
explicit = trueexplicit = true 可避免普通公共依赖意外从私有源解析;对内部包再用 [tool.uv.sources] 绑定该 index。不要把 user:password@host 写入 URL。HTTP 凭证的读取与持久化规则说明 uv add 不会把 index 凭证持久化到 pyproject.toml 或 uv.lock,但 direct URL 中的凭证仍有进入项目文件的风险,应禁止带凭证 direct URL。
uv 的 credential store 当前默认可能使用明文状态文件;native secret store 仍是 preview。团队不能把 preview 功能当成唯一凭证保护,CI 应使用短期 token,开发机优先使用批准的系统凭证方案或 keyring subprocess,并验证撤销路径。
代理与证书
代理通常由 HTTPS_PROXY / HTTP_PROXY / NO_PROXY 注入。企业 TLS 检查环境应把根 CA 放入受控证书链,并验证 uv、Python 构建后端和 Git 依赖都能信任。--allow-insecure-host 会跳过证书校验,只能用于隔离诊断,不能写进项目默认配置。
排查顺序是:DNS 与 TCP、代理是否命中、TLS 链、index 认证、包是否存在、目标平台是否有制品。不要一看到证书错误就改用 HTTP 或永久关闭校验。
Cache、离线与供应链
uv cache dir 显示全局 cache 位置。cache 可复用下载和构建结果,但不是声明或 lock;删除 cache 后项目仍应从批准来源恢复。CI 缓存键至少包含 OS、架构、uv minor、Python 范围和 lock hash,普通分支只恢复缓存,不向受信任共享 cache 写入未经审查的内容。
有离线要求时,先在联网受控环境预取所有目标平台所需的 wheel/sdist 与 build dependency,再断网执行同步和测试。仅在一台机器“cache 已热”不能证明离线可恢复。原生扩展还要记录编译器、系统库、manylinux/musllinux 或 Windows runtime 等外部输入。
供应链检查至少包括:
index 来源与包名绑定,防止 dependency confusion。lock 变更审查包版本、URL、hash、Git commit 与制品类型。优先批准 wheel;使用 sdist 时审查 build backend 和构建网络访问。
uv export --format cyclonedx1.5 可生成 SBOM,但输出仍需进入组织既有审查流程。同步阶段的 malware check 仍为 preview,只能作为补充信号,不能替代 SCA、来源准入与人工处置。
构建与 CI 契约
uv build 默认在 dist/ 生成 sdist 和 wheel。CI 应在干净环境中执行:
uv --version
uv python find
uv lock --check
uv sync --locked
uv run pytest
uv build对于应用镜像,可以使用 uv sync --locked --no-install-project 先缓存依赖层,再同步项目;但 partial installation 选项使用不当会产生缺包环境,必须在最终层执行完整验证。发布前检查 wheel 内容、元数据、导入入口和 sdist 能否在隔离环境重新构建。
CI 不允许普通验证 job 自动改写 uv.lock。升级依赖使用独立变更:uv lock --upgrade-package <包名>,审查 diff 后再跑 OS/Python 矩阵。uv 自身升级也应与依赖升级分开,以便解析变化可归因。
从 pip / requirements 迁移
先保留原 CI job。pip 项目迁移步骤从未完全固定的 requirements.in 导入,并用现有锁定文件作 constraints,以减少首轮版本漂移:
uv init
uv add -r requirements.in -c requirements.txt
uv lock
uv sync --locked
uv run pytest若 Windows、Linux 分别有 requirements,需要先补 marker,再合并成通用解析;不能把两个没有 marker 的平台锁直接作为 constraints,否则会冲突。并行比较 pip freeze、uv tree、测试、构建产物和应用启动。待所有支持平台通过后,才移除旧 requirements 生成入口。
若只想先获得安装性能收益,可先采用 uv pip compile/sync,保持 requirements 契约;这与迁移到 uv project 是两条不同路线,不应在一个提交中同时改变文件模型和 resolver。
从 Poetry 迁移
uv 迁移指南目录只给出了 pip 项目迁移路径,不能据此假设 Poetry 存在无损的一键转换。先把 Poetry 的 pyproject.toml、lock、dependency group、source、Python 约束、path/Git dependency、script、build backend 和 plugin 列成清单。
迁移步骤建议如下:
保留 poetry.lock、Poetry CI job 和旧构建产物。把可迁移元数据转换为标准 [project] 与 [dependency-groups],特殊 source 显式映射到 [tool.uv.sources]。用旧锁约束首轮解析,避免工具切换与依赖升级混在一起;无法自动约束的包逐项固定并记录。
执行 uv lock、uv sync --locked、测试和 uv build。比较依赖、entry point、wheel/sdist 元数据、可选依赖和私有源访问。在全部平台通过前,不删除 Poetry 入口,也不发布同一版本坐标的两种不同产物。
Poetry plugin 或动态版本插件没有 uv 等价物时,先决定改用标准构建后端、独立版本脚本或保留 Poetry。不能为了完成迁移,把发布语义静默删除。
uv add <包名>
uv remove <包名>
uv add --dev <包名>
uv lock --check
uv lock --upgrade-package <包名>
uv sync --locked
uv sync --check
uv run <命令>
uv tree
uv python list
uv python find
uv cache dir
uv build每个命令都对应不同写入面。评审 lock diff 前先确认是谁触发了解析;排障时先记录 uv --version、Python 路径、项目根、index 与 cache 位置,再考虑清缓存。
uv sync --locked 报 lock 过期
现象是本地 uv run 曾能工作,CI 的 locked 同步失败。判断 pyproject.toml 是否变更而 lock 未提交,或 uv 版本是否不支持当前 schema。用 uv lock --check 重现;若声明变化合理,在受控 uv 版本下重新 lock 并审查 diff。不要在 CI 改成无参数 uv sync 掩盖遗漏。
找不到或选错 Python
现象是 requires-python 不满足、原生包无 wheel,或编辑器与 uv run 使用不同解释器。执行 uv python find、uv run python -c "import sys; print(sys.executable, sys.version)",并检查 .python-version、PATH 与下载策略。修复后删除 .venv,从批准解释器重建。
私有包 401 / 403 或请求去了公共源
先区分认证失败与 source 选择错误。检查 index 是否显式命名、内部包是否绑定、CI secret 是否注入、token 权限与有效期、日志是否隐藏凭证。用最小权限账号重试。禁止临时把私有源改成默认 extra index 后提交,这可能引入 dependency confusion。
TLS 失败后使用 insecure 仍能下载
这证明网络路径可达,不证明安全配置正确。检查代理签发链、系统与 uv 使用的证书来源、SNI 和企业根 CA。导入批准 CA 后移除 insecure 配置再验证;无法恢复校验时停止上线。
同一 lock 在某个平台无可用制品
查看目标 Python、OS、架构和包的 wheel tag,判断是 marker、requires-python、缺少 wheel 还是 sdist 构建依赖失败。补齐 CI 平台、调整支持范围或选择兼容版本。不能用“通用 lock”推断所有平台可安装。
清缓存后构建失败
说明项目依赖隐式网络、未声明 build dependency、Git 分支漂移或本地 wheel。使用空 cache 重跑并记录首个外部请求;把依赖固定到批准 index/commit,补齐 build toolchain,再进行离线演练。长期共享个人 cache 不是修复。
开发者只需要读包权限;发布 token 不应出现在日常 uv sync 环境。CI 按 job 分离读取、构建与发布权限,发布优先使用短期凭证或 Trusted Publisher。凭证不得写入 pyproject.toml、uv.lock、命令参数截图、构建日志和 Docker layer。
轮换时同时验证旧 token 已撤销、新 token 只访问指定 index、缓存中没有带凭证 URL。netrc 与默认 credential store 的文件权限要纳入开发机检查;preview 的 native auth 必须有降级和数据迁移方案后才能试用。
团队应指定 uv 版本 owner、lock owner、私有 index owner 和原生构建 toolchain owner。项目模板统一:
uv 安装渠道与固定版本。Python 支持范围和下载策略。pyproject.toml、uv.lock、.python-version 的提交规则。
本地与 CI 的权威 uv sync --locked / uv run 入口。index、CA 和凭证注入方式。OS、架构、Python 矩阵与 sdist/wheel 策略。
升级、缓存清理、回滚和退出方案。
新成员验收应从空 .venv 和空项目 cache 开始;只在维护者机器上“秒装成功”不能证明模板可用。
uv 升级会成为解析输入
uv 迭代快,lock schema 在 minor 版本可发生破坏性变化。现象是新成员或旧 runner 无法读取 lock。团队应固定 uv minor,升级 PR 单独运行 uv lock --check、全平台 sync 和 lock diff;失败就恢复旧二进制与旧 lock,不允许不同版本轮流改写。
exact sync 会删除手工安装的调试包
uv sync 默认 exact,未声明包可能被移除。它能暴露环境漂移,但也会让依赖手工注入调试包的流程突然失败。临时工具用 uvx 或 uv run --with,项目必需依赖进入声明;若必须 --inexact,要记录用途和退出条件。
通用解析掩盖平台未验证
lock 中出现 Windows、Linux 和多个 Python marker,不代表所有组合可构建。原生扩展常在新 Python 或 ARM64 缺 wheel。CI 应覆盖承诺矩阵,并在发布前验证 sdist 构建;无法覆盖的平台从 environments 与支持声明中明确排除。
多 index 策略决定供应链边界
公共源与私有源同时启用时,包名冲突可能把内部名称发送到公共服务或解析到攻击者包。内部包应绑定 explicit index,日志与 lock 审查来源。发现来源漂移时冻结 lock 更新、吊销相关凭证并检查已安装制品。
workspace 的单 lock 放大变更半径
一个 member 改依赖可能让全局 lock 大面积变化。owner 应要求定向升级、解释跨 member 变化并运行受影响矩阵。若成员 Python 范围或发布周期长期冲突,应拆分 workspace,而不是堆叠越来越复杂的 marker。
cache 命中掩盖不可恢复输入
本地 cache 可能保留已从源站删除的 wheel、旧凭证访问结果或未声明构建产物。定期执行空 cache 重建和离线包清单审计;清缓存后失败时先修来源与声明,禁止把整个个人 cache 上传为团队制品。
uv 安装渠道、版本和可执行文件来源已固定并可回滚。项目声明、uv.lock、.venv 和 cache 的职责没有混淆。CI 使用 uv lock --check 与 uv sync --locked,不会静默改锁。
managed Python 下载策略、实际解释器和支持矩阵均可验证。uv pip 兼容差异已按现有命令逐项检查,没有宣称完全等价。私有 index 显式绑定,凭证不进入 URL、lock、日志或镜像层。
企业代理与 CA 已恢复严格 TLS 校验,没有永久 insecure 配置。OS、架构和 Python 矩阵真实安装验证,不只依赖通用 lock。sdist、wheel、构建后端、SBOM 和缓存来源进入供应链审查。
pip 或 Poetry 迁移保留旧入口,完成依赖、测试、产物和元数据并行比较。uv 升级与依赖升级拆分,旧二进制和旧 lock 回滚已演练。团队已有版本、lock、index、原生 toolchain 和退出方案 owner。
