跳到内容

缓存

依赖项缓存

uv 使用积极的缓存策略,以避免重新下载(和重新构建)在之前运行中已经访问过的依赖项。

uv 缓存语义的具体细节取决于依赖项的类型

  • 对于注册表依赖项(如从 PyPI 下载的依赖项),uv 遵循 HTTP 缓存头。
  • 对于直接 URL 依赖项,uv 遵循 HTTP 缓存头,并根据 URL 本身进行缓存。
  • 对于 Git 依赖项,uv 根据完全解析后的 Git 提交哈希进行缓存。因此,当写入解析后的依赖项集时,uv pip compile 会将 Git 依赖项锁定到特定的提交哈希。
  • 对于本地依赖项,uv 根据源归档文件(即本地 .whl.tar.gz 文件)的最后修改时间进行缓存。对于目录,uv 根据 pyproject.tomlsetup.pysetup.cfg 文件的最后修改时间进行缓存。

如果您遇到缓存问题,uv 提供了一些补救措施:

  • 要完全清除缓存,请运行 uv cache clean。要清除特定包的缓存,请运行 uv cache clean <package-name>。例如,uv cache clean ruff 将清除 ruff 包的缓存。
  • 要强制 uv 为所有依赖项重新验证缓存数据,请在任何命令中传递 --refresh(例如,uv sync --refreshuv pip install --refresh ...)。
  • 要强制 uv 为特定依赖项重新验证缓存数据,请在任何命令中传递 --refresh-package(例如,uv sync --refresh-package ruffuv pip install --refresh-package ruff ...)。
  • 要强制 uv 忽略已安装的版本,请在任何安装命令中传递 --reinstall(例如,uv sync --reinstalluv pip install --reinstall ...)。(建议先运行 uv cache clean <package-name>,以确保在重新安装前已清除缓存。)

作为一个特殊情况,对于在命令行中明确传递的本地目录依赖项(例如 uv pip install .),uv 总是会重新构建并重新安装。

动态元数据

默认情况下,uv 在目录根目录中的 pyproject.tomlsetup.pysetup.cfg 文件发生更改,或者添加或删除了 src 目录时,才会重新构建并重新安装本地目录依赖项(例如可编辑安装模式)。这是一种启发式方法,在某些情况下,可能导致重新安装的次数少于预期。

若要将额外信息纳入给定包的缓存键中,您可以在 tool.uv.cache-keys 下添加缓存键条目,这涵盖了文件路径和 Git 提交哈希。设置 tool.uv.cache-keys 将会替换默认设置,因此任何必要的文件(如 pyproject.toml)仍应包含在用户定义的缓存键中。

例如,如果项目在 pyproject.toml 中指定依赖项,但使用 setuptools-scm 来管理版本,因此需要在每次提交哈希或依赖项更改时重新构建,您可以将以下内容添加到项目的 pyproject.toml 中:

pyproject.toml
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true } }]

如果您的动态元数据合并了 Git 标签集中的信息,您可以扩展缓存键以包含这些标签:

pyproject.toml
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }]

同样,如果项目通过读取 requirements.txt 来填充其依赖项,您可以将以下内容添加到项目的 pyproject.toml 中:

pyproject.toml
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { file = "requirements.txt" }]

file 键支持使用 glob 通配符,遵循 glob crate 的语法。例如,要确保每次修改项目目录或其任何子目录中的 .toml 文件时都使缓存失效,请使用以下内容:

pyproject.toml
[tool.uv]
cache-keys = [{ file = "**/*.toml" }]

注意

使用 glob 可能会消耗较多资源,因为 uv 可能需要遍历文件系统以确定是否有文件发生更改。这反过来可能需要遍历庞大或深层嵌套的目录。

同样,如果项目依赖于环境变量,您可以将以下内容添加到项目的 pyproject.toml 中,以便在环境变量发生更改时使缓存失效:

pyproject.toml
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { env = "MY_ENV_VAR" }]

最后,要确保在创建或删除特定目录(如 src)时使项目缓存失效,请将以下内容添加到项目的 pyproject.toml 中:

pyproject.toml
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { dir = "src" }]

请注意,dir 键仅跟踪目录本身的变化,而不跟踪目录内的任意文件更改。

作为一种补救措施,如果项目使用的 dynamic 元数据未被 tool.uv.cache-keys 涵盖,您可以将该项目添加到 tool.uv.reinstall-package 列表中,指示 uv 始终对其进行重新构建和重新安装:

pyproject.toml
[tool.uv]
reinstall-package = ["my-package"]

这将强制 uv 在每次运行时重新构建并重新安装 my-package,无论该包的 pyproject.tomlsetup.pysetup.cfg 文件是否已更改。

缓存安全性

并发运行多个 uv 命令是安全的,即使针对同一个虚拟环境也是如此。uv 的缓存设计为线程安全且仅追加(append-only),因此对于多个并发的读取器和写入器非常稳健。在安装时,uv 会对目标虚拟环境应用基于文件的锁,以避免跨进程的并发修改。

请注意,直接修改缓存(例如通过删除文件或目录)是不安全的。

清理缓存

uv 提供了几种从缓存中删除条目的机制:

  • uv cache clean 会从缓存目录中删除所有缓存条目,将其完全清空。
  • uv cache clean ruff 会删除 ruff 包的所有缓存条目,这对于使单个或有限的一组包的缓存失效非常有用。
  • uv cache prune 会删除所有未使用的缓存条目。例如,缓存目录可能包含旧版本 uv 创建的条目,这些条目不再需要,可以安全删除。定期运行 uv cache prune 是安全的,有助于保持缓存目录整洁。

当其他 uv 命令正在运行时,uv 会阻塞缓存修改操作。默认情况下,这些 uv cache 命令有 5 分钟的超时时间,用于等待其他 uv 进程终止,以避免死锁。此超时时间可以通过 UV_LOCK_TIMEOUT 进行更改。在已知没有其他 uv 进程正在读取或写入缓存的情况下,可以使用 --force 来忽略锁。

持续集成中的缓存

在持续集成环境(如 GitHub Actions 或 GitLab CI)中缓存包安装工件以加速后续运行是常见的做法。

默认情况下,uv 会缓存它从源代码构建的 wheel 以及直接下载的预构建 wheel,以实现高性能的包安装。

然而,在持续集成环境中,保留预构建的 wheel 可能并不理想。使用 uv,通常在缓存中省略预构建的 wheel(并在每次运行时从注册表重新下载它们)往往更快。另一方面,缓存从源代码构建的 wheel 往往是值得的,因为 wheel 构建过程可能非常昂贵,尤其是对于扩展模块而言。

为了支持这种缓存策略,uv 提供了 uv cache prune --ci 命令,该命令会从缓存中删除所有预构建的 wheel 和解压后的源分发包,但保留任何从源代码构建的 wheel。我们建议在持续集成任务结束时运行 uv cache prune --ci,以确保最大的缓存效率。有关示例,请参阅 GitHub 集成指南

缓存目录

uv 按照以下顺序确定缓存目录:

  1. 如果请求了 --no-cache,则使用临时缓存目录。
  2. 通过 --cache-dirUV_CACHE_DIRtool.uv.cache-dir 指定的特定缓存目录。
  3. 系统适配的缓存目录,例如 Unix 上的 $XDG_CACHE_HOME/uv$HOME/.cache/uv,以及 Windows 上的 %LOCALAPPDATA%\uv\cache

注意

uv 始终需要一个缓存目录。当请求 --no-cache 时,uv 仍将使用一个临时缓存来在单次调用内共享数据。

在大多数情况下,应该使用 --refresh 而不是 --no-cache——因为它会更新缓存以供后续操作使用,但不会从缓存中读取。

缓存目录必须与 uv 操作的 Python 环境位于同一个文件系统上,这对于性能至关重要。否则,uv 将无法将缓存中的文件链接到环境中,而不得不回退到缓慢的复制操作。

缓存版本控制

uv 缓存由多个存储桶组成(例如,wheel 的存储桶、源分发包的存储桶、Git 存储库的存储桶等)。每个存储桶都有版本控制,因此如果某个发布版本包含了缓存格式的重大更改,uv 将不会尝试读取或写入不兼容的缓存存储桶。

例如,uv 0.4.13 对核心元数据存储桶进行了重大更改。因此,存储桶版本从 v12 增加到了 v13。在同一个缓存版本内,更改保证是向前和向后兼容的。

由于缓存格式的更改伴随着缓存版本的更改,多个版本的 uv 可以安全地读取和写入同一个缓存目录。但是,如果一对特定的 uv 发布版本之间缓存版本发生了变化,那么这些版本可能无法共享同一个底层缓存条目。

例如,对 uv 0.4.12 和 uv 0.4.13 使用单个共享缓存是安全的,尽管由于缓存版本的更改,缓存本身在核心元数据存储桶中可能包含重复的条目。