缓存
依赖缓存
uv 采用主动缓存策略,避免重新下载(和重新构建)之前运行中已访问过的依赖项。
uv 的缓存语义细节会根据依赖项的性质而有所不同:
- 对于注册中心依赖项(例如从 PyPI 下载的依赖项),uv 遵循 HTTP 缓存头。
- 对于直接 URL 依赖项,uv 遵循 HTTP 缓存头,并且还基于 URL 本身进行缓存。
- 对于 Git 依赖项,uv 基于完全解析的 Git 提交哈希进行缓存。因此,uv pip compile
在写入已解析的依赖项集时,会将 Git 依赖项固定到特定的提交哈希。
- 对于本地依赖项,uv 基于源存档(即本地的 .whl
或 .tar.gz
文件)的最后修改时间进行缓存。对于目录,uv 基于 pyproject.toml
、setup.py
或 setup.cfg
文件的最后修改时间进行缓存。
如果遇到缓存问题,uv 提供了一些解决方法:
- 要强制 uv 重新验证所有依赖项的缓存数据,在任何命令中传递 --refresh
(例如 uv sync --refresh
或 uv pip install --refresh ...
)。
- 要强制 uv 重新验证特定依赖项的缓存数据,在任何命令中传递 --refresh-package
(例如 uv sync --refresh-package flask
或 uv pip install --refresh-package flask ...
)。
- 要强制 uv 忽略现有的已安装版本,在任何安装命令中传递 --reinstall
(例如 uv sync --reinstall
或 uv pip install --reinstall ...
)。
作为一种特殊情况,uv 始终会重新构建并重新安装在命令行中显式传递的任何本地目录依赖项(例如 uv pip install .
)。
动态元数据
默认情况下,只有当目录根目录中的 pyproject.toml
、setup.py
或 setup.cfg
文件发生更改,或者添加或删除了 src
目录时,uv 才会重新构建并重新安装本地目录依赖项(例如,可编辑的依赖项)。这是一种启发式方法,在某些情况下,重新安装的次数可能比预期的少。
要将其他信息纳入给定包的缓存键中,可以在tool.uv.cache-keys
下添加缓存键条目,该条目涵盖文件路径和 Git 提交哈希值。设置tool.uv.cache-keys
将替换默认值,因此任何必要的文件(如 pyproject.toml
)仍应包含在用户定义的缓存键中。
例如,如果一个项目在 pyproject.toml
中指定依赖项,但使用setuptools-scm
来管理其版本,因此每当提交哈希值或依赖项发生变化时都应重新构建,则可以在项目的 pyproject.toml
中添加以下内容:
如果动态元数据包含来自 Git 标签集的信息,可以扩展缓存键以包含标签:
[tool.uv]
cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }]
同样,如果一个项目从 requirements.txt
读取以填充其依赖项,可以在项目的 pyproject.toml
中添加以下内容:
file
键支持通配符,遵循glob
库的语法。例如,要在项目目录或其任何子目录中的 .toml
文件被修改时使缓存失效,可以使用以下内容:
注意
使用通配符可能代价高昂,因为 uv 可能需要遍历文件系统以确定是否有任何文件发生了更改。这反过来可能需要遍历大型或深度嵌套的目录。
同样,如果一个项目依赖于一个环境变量,可以在项目的 pyproject.toml
中添加以下内容,以便在环境变量发生变化时使缓存失效:
最后,要在特定目录(如 src
)被创建或删除时使项目失效,可以在项目的 pyproject.toml
中添加以下内容:
请注意,dir
键只会跟踪目录本身的更改,而不会跟踪目录内的任意更改。
作为一种应急措施,如果一个项目使用 tool.uv.cache-keys
未涵盖的 dynamic
元数据,可以通过将项目添加到 tool.uv.reinstall-package
列表中,指示 uv 始终重新构建并重新安装它:
这将强制 uv 在每次运行时重新构建并重新安装 my-package
,无论该包的 pyproject.toml
、setup.py
或 setup.cfg
文件是否发生了更改。
缓存安全性
并发运行多个 uv 命令是安全的,即使针对的是同一个虚拟环境。uv 的缓存设计为线程安全且仅追加,因此对多个并发读写操作具有鲁棒性。在安装时,uv 会对目标虚拟环境应用基于文件的锁,以避免跨进程的并发修改。
请注意,在其他 uv 命令运行时修改 uv 缓存(例如 uv cache clean
)是不安全的,并且直接修改缓存(例如通过删除文件或目录)永远不安全。
清除缓存
uv 提供了几种不同的机制来从缓存中删除条目:
- uv cache clean
会从缓存目录中删除所有缓存条目,将其完全清空。
- uv cache clean ruff
会删除 ruff
包的所有缓存条目,这对于使单个或有限的一组包的缓存失效很有用。
- uv cache prune
会删除所有未使用的缓存条目。例如,缓存目录可能包含在以前的 uv 版本中创建的不再需要且可以安全删除的条目。定期运行 uv cache prune
是安全的,以保持缓存目录的整洁。
持续集成中的缓存
在持续集成环境(如 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-dir
、UV_CACHE_DIR
或tool.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 使用单个共享缓存是安全的,不过由于缓存版本的变化,缓存本身在核心元数据存储桶中可能会包含重复的条目。