uv 对接 Azure Artifacts:从索引配置、PAT/keyring 认证到 uv publish 发布的完整实践
uv 不仅可以从公共 PyPI 安装依赖,也支持将 Azure Artifacts 这类企业私有制品库作为包索引源,并可直接向其中发布包。本文基于 uv 官方集成文档 Azure Artifacts 指南,完整讲解:如何在 pyproject.toml 中注册 Azure Artifacts 索引、如何用 Personal Access Token(PAT)或 keyring + artifacts-keyring 插件完成认证、如何通过 uv publish 把自有包发布回 Artifacts,并深入 uv-auth 凭据服务 的源码,说明子进程 keyring 与发布凭据收集的真实实现路径。
读完本文,你可以在 Azure DevOps 流水线或本地开发环境中,让 uv 安全地读取私有制品库,并完成“配置索引 → 选择认证方式 → 发布包”的全流程。
在 pyproject.toml 中注册 Azure Artifacts 索引
使用 Azure Artifacts 的第一步,是在项目配置中声明索引。索引通过 [[tool.uv.index]] 数组定义,每个索引需要一个唯一的 name(供环境变量与 --index 参数引用)和 Artifacts 的 PEP 503 格式 url:
[[tool.uv.index]]
name = "private-registry"
url = "https://pkgs.dev.azure.com/<ORGANIZATION>/<PROJECT>/_packaging/<FEED>/pypi/simple/"
其中 <ORGANIZATION>、<PROJECT>、<FEED> 分别替换为你的 Azure DevOps 组织、项目与制品库(Feed)名称。索引 name 是后文所有认证配置的关键:uv 用 UV_INDEX_<NAME>_USERNAME / UV_INDEX_<NAME>_PASSWORD 这类环境变量按索引名定位凭据,因此 name 必须与环境变量中的 PRIVATE_REGISTRY(即名称的大写形式)一致,凭据才能被正确匹配。
认证方式一:Personal Access Token(PAT)
当环境中已有 PAT 时(例如 Azure 流水线中可直接使用系统变量 $(System.AccessToken)),可以通过 HTTP "Basic" 认证方案提供凭据:把 PAT 填入 URL 凭据的密码字段。用户名是必填项,但可以是任意字符串(Azure Artifacts 侧并不校验其具体内容,常用 dummy 占位)。
以 token 存放在 $AZURE_ARTIFACTS_TOKEN 环境变量为例:
export UV_INDEX_PRIVATE_REGISTRY_USERNAME=dummy
export UV_INDEX_PRIVATE_REGISTRY_PASSWORD="$AZURE_ARTIFACTS_TOKEN"
注意两点:
PRIVATE_REGISTRY必须与pyproject.toml中[[tool.uv.index]]的name字段对应(名称会做规范化大写处理);- 凭据只通过环境变量注入,不写入
pyproject.toml或uv.lock—— uv 在uv add等操作中同样不会把索引凭据持久化进这些会进入版本控制的文件,这是 uv 的通用安全行为,见 HTTP 凭据概念文档 中“Persistence of credentials”一节。
从 uv 的凭据优先级看,HTTP 认证来源依次为:URL 内嵌的 user:password、.netrc 文件、uv 凭据存储、keyring provider(默认关闭)。环境变量注入的索引凭据在命令执行期间按索引 URL / 网络位置(scheme、host、port)缓存复用,但不跨命令持久化(来源见 docs/concepts/authentication/http.md)。
认证方式二:keyring 与 artifacts-keyring 插件
另一种无需管理 token 字符串的方式,是使用 Python 的 keyring 包配合 Microsoft 的 artifacts-keyring 插件。artifacts-keyring 插件封装了 Azure Artifacts Credential Provider 工具,支持交互式登录等多种认证模式(具体配置参见该工具自身文档)。
有两个前置约束值得注意:
- 这两个包必须从 Artifacts 之外的来源(如公共 PyPI)预安装,因为它们本身就是认证 Artifacts 的依赖,若从 Artifacts 拉取会形成先有鸡还是先有蛋的循环;
- uv 目前仅支持 keyring 的 subprocess(子进程)模式:
keyring可执行文件必须在PATH中(全局安装或处于激活环境内),并且keyringCLI 要求 URL 中提供用户名,该用户名必须为VssSessionToken。
完整操作流程如下:
# 从公共 PyPI 预装 keyring 与 Artifacts 插件
uv tool install keyring --with artifacts-keyring
# 启用 keyring 子进程认证
export UV_KEYRING_PROVIDER=subprocess
# 为该索引设置用户名(必须为 VssSessionToken)
export UV_INDEX_PRIVATE_REGISTRY_USERNAME=VssSessionToken
同样的配置也可以通过项目文件固化:在 uv.toml 或 pyproject.toml 中设置 tool.uv.keyring-provider = "subprocess"(见 keyring providers 说明),并把 VssSessionToken 用户名直接写进索引 URL,避免每个环境重复导出环境变量。
源码视角:uv 如何调用 keyring 子进程
在 crates/uv-auth/src/keyring.rs 中,KeyringProvider 定义了 Native(系统原生密钥环)与 Subprocess(外部 keyring 命令)两种后端,KeyringProviderBackend::Subprocess 对应本文使用的模式。其 fetch_subprocess 方法(约 L266–L355)展示了实际行为:
// fetch_subprocess:拼出 keyring get <service> [<username>] 命令
let mut command = Command::new("keyring");
command.arg("get").arg(service_name);
if let Some(username) = username {
command.arg(username);
} else {
command.arg("--mode").arg("creds");
}
即 uv 会先以完整 URL 作为 service_name 查询一次 keyring,未命中再回退到 host(含端口)级别查询,非 HTTPS 场景还会尝试 scheme://host:port 形式的服务名,以避免跨协议泄露凭据。若 keyring 版本过低不支持 --mode creds,uv 会给出明确的升级提示(要求 keyring>=v25.2.1 或提供用户名)。这也解释了前文“必须提供 VssSessionToken 用户名”的原因:带用户名时 uv 只向 keyring 索要密码,行为更简单可靠。
发布包到 Azure Artifacts
如果还要把自己的包发布到 Artifacts,使用 uv 的 uv publish 命令(通用流程见 构建与发布指南)。
第一步,给目标索引增加 publish-url,指向 Artifacts 的 upload 端点:
[[tool.uv.index]]
name = "private-registry"
url = "https://pkgs.dev.azure.com/<ORGANIZATION>/<PROJECT>/_packaging/<FEED>/pypi/simple/"
publish-url = "https://pkgs.dev.azure.com/<ORGANIZATION>/<PROJECT>/_packaging/<FEED>/pypi/upload/"
第二步,若未走 keyring 方案,配置发布凭据(PAT 场景与安装时相同,用户名任意):
$ export UV_PUBLISH_USERNAME=dummy
$ export UV_PUBLISH_PASSWORD="$AZURE_ARTIFACTS_TOKEN"
第三步,用 --index 指定要发布的索引名:
$ uv publish --index private-registry
也可以在项目配置中不写 publish-url,改用 UV_PUBLISH_URL 环境变量临时指定上传地址:
$ export UV_PUBLISH_URL=https://pkgs.dev.azure.com/<ORGANIZATION>/<PROJECT>/_packaging/<FEED>/pypi/upload/
$ uv publish
官方文档特别提醒:这种方式不推荐。原因在 crates/uv/src/commands/publish.rs 的源码中可以印证——发布时 uv 会根据索引配置解析出 (publish_url, check_url) 二元组:
let (publish_url, check_url) = if let Some(index_name) = index {
// 按索引名查找,publish_url 来自索引的 publish-url
// check_url 用于上传前检查包是否已发布
}
只有当能从项目索引配置中解析出该索引时,check_url 才有值,uv 才会在上传前“Checking N files against ...”检查包是否已发布,避免重复发布冲突。直接给 UV_PUBLISH_URL 时没有关联的 simple 索引 URL,uv 无法执行这项预检,只能直接“Publishing N files to ...”。因此,只要发布目标是固定的 Azure Artifacts 仓库,把 publish-url 写进 pyproject.toml 是更稳妥的做法。
发布时的凭据收集逻辑(gather_credentials,约 L349 起)同样遵循通用优先级:先读取 UV_PUBLISH_USERNAME / UV_PUBLISH_PASSWORD 环境变量,再看 URL 内嵌用户名/密码,最后才会查询 keyring(keyring.rs 中的 fetch),并在使用 keyring 且无密码时报出 “Keyring has no password for URL ...” 的明确错误,便于排查认证配置。
小结与适用前提
把本文要点归纳为一张对照表:
| 场景 | 关键配置 / 命令 | 备注 |
|---|---|---|
| 声明索引 | [[tool.uv.index]] + url(.../pypi/simple/) |
name 须与 UV_INDEX_<NAME>_* 环境变量匹配 |
| PAT 认证 | UV_INDEX_<NAME>_USERNAME / UV_INDEX_<NAME>_PASSWORD |
用户名任意字符串即可 |
| keyring 认证 | uv tool install keyring --with artifacts-keyring + UV_KEYRING_PROVIDER=subprocess |
用户名必须为 VssSessionToken;keyring 需在 PATH 中 |
| 发布包 | 索引增加 publish-url + UV_PUBLISH_USERNAME/PASSWORD + uv publish --index <name> |
建议写 publish-url 以启用已发布检查 |
| 临时发布地址 | UV_PUBLISH_URL |
无法执行“是否已发布”预检,非首选 |
适用前提与限制:
- 本文配置基于当前仓库中 docs/guides/integration/azure.md 描述的 uv 行为;
tool.uv.keyring-provider仅支持subprocess一种值,原生系统密钥环存储(Keychain / Credential Manager / Secret Service)目前处于 preview 阶段,需通过UV_PREVIEW_FEATURES=native-auth开启,且只能读取 uv 自己写入的条目,与artifacts-keyring插件写入的条目不互通; - 环境变量按索引名匹配,多索引项目请为每个索引使用不同的
name,避免凭据串号; - 凭据不会被写入
pyproject.toml、uv.lock等可能入库的文件,流水线中建议用受保护变量或$(System.AccessToken)注入。
如需进一步了解认证机制的完整优先级、.netrc 支持与凭据持久化规则,可继续阅读 HTTP 凭据 与 索引认证 相关文档;索引与索引 URL 的概念见 索引文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00