这篇文章深入介绍 uv 管理 Python 项目的使用
Features
Python versions
uv python install
: 安装 Python 版本uv python list
: 查看可用的 Python 版本uv python find
: 查找安装的 Python 版本uv python pin
: 固定当前项目的 Python 版本uv python uninstall
: 卸载一个 Python 版本
Scripts
uv run
: 运行一个脚本uv add --script
: 为脚本添加一个依赖uv remove --script
: 移除一个依赖
Projects
使用 pyproject.toml
配置项目
uv init
: 创建一个 Python 项目uv add
: 为项目添加依赖uv remove
: 删除项目依赖uv sync
: 同步环境下的依赖uv lock
: 为项目依赖创建一个锁文件uv run
: 在项目环境执行命令uv tree
: 查看项目依赖树uv build
: 将项目构建为分发归档文件uv publish
: 将项目发布到包索引
Tools
允许与安装工具
uvx / uv tool run
: 在临时环境运行一个工具uv tool install
: 安装一个工具uv tool uninstall
: 卸载一个工具uv tool list
: 列出已安装工具uv tool update-shell
: 更新 shell 以包含工具执行
The pip interface
手动管理环境与包, 用于旧的工作流, 或一些高级命令功能
uv venv
: 创建虚拟环境
下面命令手动管理环境中的包 (替代 pip
和 pipdeptree
)
uv pip install
: 在当前环境安装包uv pip show
: 显示已安装包的细节uv pip freeze
: 列出安装的包以及版本uv pip check
: 检查当前环境是否有兼容的软件包uv pip list
: 列出已安装包uv pip uninstall
: 卸载包uv pip tree
: 查看环境依赖
在环境中锁定包 (替代 pip-tools
)
uv pip compile
: 将依赖编译到一个锁文件中uv pip sync
: 使用锁文件同步环境
Utility
管理与检查 uv 的状态, 例如缓存、存储目录或子升级
uv cache clean
: 清除缓存条目uv cache prune
: 移除过期的缓存条目uv cache dir
: 显示 uv 缓存目录的路径uv tool dir
: 显示 uv 工具目录路径uv python dir
: 显示 uv 安装的 Python 各版本路径uv self update
: 更新 uv
Projects
Structure and files
Python 项目依赖元数据定义在 pyproject.toml
中, uv 依赖这个文件来确定项目根目录.
一个最小的项目定义包含名称和版本
[project]
name = "example"
version = "0.1.0"
额外的项目元数据和配置包含:
The project environment
使用 uv 处理项目时, uv 会根据需要创建虚拟环境.
虽然某些 uv 命令会创建临时环境 (uv run --isolated
), 但 uv 也会在 pyproject.toml
旁边的 .venv
目录中管理一个持久环境, 其中包含项目及其依赖项.
它存储在项目内部是为了方便编辑器查找, 编辑器需要环境来提供代码补全和类型提示.
不建议将 .venv
目录包含在版本控制中, 它会通过内部 .gitignore
文件自动从 git 中排除.
要在项目环境中运行命令, 应该使用 uv run
.
或者, 项目环境可以像普通虚拟环境一样正常激活.
当调用 uv run
时, 如果项目环境尚不存在, 它将创建项目环境, 如果已存在则确保其是最新的.
项目环境也可以通过 uv sync
显式创建.
不建议手动修改项目环境, 例如使用 uv pip install
.
对于项目依赖项, 使用 uv add
将包添加到环境中.
对于一次性需求, 使用 uvx
或 uv run --with
.
TIP 提示
如果不希望 uv 管理项目环境, 设置 managed = false
来禁用项目的自动锁定和同步, 例如:
[tool.uv]
managed = false
The lockfile
uv 会在 pyproject.toml
旁边创建一个 uv.lock
文件.
uv.lock
是一个通用或跨平台锁文件, 它捕获在所有可能的 Python 标记 (操作系统、架构和Py版本) 上将要安装的包.
与用于指定项目广泛要求的 pyproject.toml
不同, 锁文件包含在项目环境中安装的确切解析版本.
此文件应检入版本控制, 以允许在不同机器上进行一致且可重现的安装.
锁文件确保从事项目工作的开发人员使用一致的包版本. 它确保在将项目部署为应用程序时,已知使用的确切包版本.
锁文件在使用项目环境的 uv 调用期间自动创建和更新, 即 uv sync
和 uv run
.
锁文件也可以使用 uv lock
显式更新.
uv.lock 是一个人类可读的 TOML 文件, 但由 uv 管理, 不应手动编辑. uv.lock 格式专用于 uv, 其他工具无法使用.
Relationship to pylock.toml
在 PEP 751 中, Python 标准化了一种新的解析文件格式 pylock.toml
.
pylock.toml
是一种解析输出格式,旨在替代 requirements.txt
.
pylock.toml
是标准化的且与工具无关的, 这样在未来, uv 生成的 pylock.toml
文件可以由其他工具安装.
uv 的某些功能无法在 pylock.toml
格式中表达; 因此, uv 将继续在项目接口中使用 uv.lock
格式.
但是, uv 支持将 pylock.toml
作为导出目标以及在 uv pip
CLI 中使用:
uv export -o pylock.toml
: 将uv.lock
导出为pylock.toml
格式uv pip compile -o pylock.toml -r requirements.in
: 一组要求生成pylock.toml
文件uv pip sync pylock.toml
或uv pip install -r pylock.toml
: 从pylock.toml
文件安装
Creating projects
uv 支持使用 uv init
创建项目.
当创建项目时, uv 支持两种基本的模板: applications 和 libraries.
默认 uv 会创建应用项目, 使用 --lib
flag 可以创建 library 项目.
Target directory
uv 会在工作目录创建项目, 或者在目标目录, 例如 uv init foo
.
如果这个位置已经存在项目了, uv 会退出并报错.
Applications
应用项目模板很适合 web 服务器、脚本和命令行接口. 该模板是默认的 uv init
生成目标, 也可以使用 --app
flag 显示指定
uv init example-app
项目结构包含一个 pyproject.toml
、一个样本文件 main.py
一个 readme 和一个 Python 版本文件 .python-version
pyproject.toml
文件包含了基本的元数据.
不包含构建系统, 它不是一个包, 并且不会在环境中安装.
[project]
name = "example-app"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = []
main.py
文件有类似下面的样板
def main():
print("Hello from example-app!")
if __name__ == "__main__":
main()
通过下面命令运行 python 代码
uv run main.py
Packaged applications
如果是想要创建一个命令行工具, 并发布到 PyPI 上面, 可以使用 package 模板.
uv init --package example-pkg
main.py
被替换为 src/
目录, 该目录中会包含一个模块目录, 以及一个 __init__.py
文件.
Build system 被定义了, 因此该项目会被安装到环境中, pyproject.toml
类似下面这样:
[project]
name = "example-pkg"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = []
[project.scripts]
example-pkg = "example_pkg:main"
[build-system]
requires = ["uv_build>=0.8.20,<0.9.0"]
build-backend = "uv_build"
命令也被定义了, 可以使用下面这样命令运行 uv run
uv run exmaple-pkg
Libraries
一个库提供函数和对象为其他项目使用.
库需要构建和分发, 例如上传到 PyPI.
使用 --lib
flag 来创建库结构.
uv init --lib example-lib
类似 packaged application, 项目采用了 src/
布局, 其中包含一个 py.typed
标记文件, 用于告知使用者此库提供了类型注解信息.
[project]
name = "example-lib"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = []
[build-system]
requires = ["uv_build>=0.8.20,<0.9.0"]
build-backend = "uv_build"
可以自行选择不同的构建后端, 使用 --build-backend
结合 hatchling
, uv_build
, flitcore
, pdm-backend
, setuptools
, maturin
或 scikit-build-core
等使用.
创建的模块定义了一个简单的 API 函数 __init__.py
def hello() -> str:
return "Hello from example-lib!"
然后可以使用 uv run
运行
uv run python -c "import example_lib; print(example_lib.hello())"
Projects with extension modules
大多数 Python 项目都是 “pure Python”, 这意味着他们不使用其他语言定义模块, 例如 C, C++, FORTRAN 或 Rust. 然而, 使用扩展模块的项目通常在性能敏感的代码中使用.
创建一个扩展模块需要选择一个构建系统. uv 支持下面的构建系统:
maturin
用于支持 Rust 项目scikit-build-core
: 用于 C, C++, FORTRAN, Cython
使用 --build-backend
flag 指定构建系统
uv init --build-backend maturin example-ext
这个项目包含一个 Cargo.toml
和一个 src/lib.rs
文件.
Rust 库定义了一个简单的函数
use pyo3::prelude::*;
#[pyfunction]
fn hello_from_bin() -> String {
"Hello from example-ext!".to_string()
}
#[pymodule]
fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(hello_from_bin, m)?)?;
Ok(())
}
并在 Python 模块中将其导入, src/example-ext/__init__.py
内容如下:
from example_ext._core import hello_from_bin
def main() -> None:
print(hello_from_bin())
可以使用下面命令执行
uv run example-ext
Creating a minimal project
如果只想创建一个 pyproject.toml
使用 --bare
选项
uv init example --bare
Managing dependencies
Dependency fields
项目依赖由以下几个字段定义
project.dependencies
: 发布的依赖project.optional-dependencies
: 发布的可选依赖dependency-groups
: 本地开发依赖tool.uv.sources
: 开发依赖的可选源
NOTE
项目的 project.dependencies
和 project.optional-dependencies
字段即使不发布也能使用.
uv 支持使用 uv add
和 uv remove
来定义项目依赖, 但也可以直接修改 pyproject.toml
.
Adding dependencies
例如这样
uv add httpx
可以添加一个项目依赖
[project]
name = "example"
version = "0.1.0"
dependencies = ["httpx>=0.27.2"]
使用 --dev
, --group
, --optional
flag 可以用来为可选字段添加依赖.
依赖将会添加一个约束, 例如 >=0.27.2
, 这是最近的兼容版本.
这样的约束可以使用 --bounds
调整, 或者直接提供约束.
uv add "httpx>=0.20"
当从源添加一个依赖而不是 package registry 的时候, uv 会在源字段中添加一个条目, 例如下面从 GitHub 添加 httpx
:
uv add "httpx @ git+https://github.com/encode/httpx"
这会在 pyproject.toml
添加一个 Git 入口源:
[project]
name = "example"
version = "0.1.0"
dependencies = [
"httpx",
]
[tool.uv.sources]
httpx = { git = "https://github.com/encode/httpx" }
如果依赖无法添加, uv 将会输出一个报错.
- Importing dependencies from requirements files
可以使用 -r
选项添加 requirements.txt
里面的依赖
uv add -r requirements.txt
Removing dependencies
使用下面命令移除依赖
uv remove httpx
使用 --dev
, --group
或 --optional
flags 来移除其他特定的依赖.
如果被移除的依赖项定义了源, 并且没有其他依赖项引用它, 那么这个源也将被一并移除.
Changing dependencies
如果要修改现有的依赖, 可以这样
uv add "httpx>0.1.0"
使用一个不同的依赖源
uv add "httpx @ .../httpx"
Platform-specific dependencies
为了使用只在具体平台的依赖或具体的 Python 版本, 使用 environment markers.
例如, 只在 Linux 平台安装 jax
:
uv add "jax; sys_paltform == 'linux'"
这会使 pyproject.toml
包含下面这样的定义
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["jax; sys_platform == 'linux'"]
相似的, 只为 Python 3.11 之后的版本添加 numpy
:
uv add "numpy; python_version >= '3.11'"
Project dependencies
project.dependencies
代表上传到 PyPI 或者构建 wheel 使用的依赖.
单独依赖具体使用 dependency specifiers 语法, 并遵循 PEP 621 标准.
project.dependencies
定义了一个项目需要的依赖列表, 以及需要的版本限制.
每一项都包含一个项目名称和项目版本号, 有些项还包含额外的 environment markers, 例如:
[project]
name = "albatross"
version = "0.1.0"
dependencies = [
# Any version in this range
"tqdm >=4.66.2,<5",
# Exactly this version of torch
"torch ==2.2.2",
# Install transformers with the torch extra
"transformers[torch] >=4.39.3,<5",
# Only install this package on older python version
"importlib_metadata >=7.1.0,<8; python_version < '3.10'",
"mollymawk ==0.1.0"
]
Dependency sources
tool.uv.sources
表是对标准依赖表的扩展, 增加了例如可编辑安装和相对路径.
例如, 从项目根目录的相对目录安装 foo
.
[project]
name = "example"
version = "0.1.0"
dependencies = ["foo"]
[tool.uv.sources]
foo = { path = "./package/foo" }
uv 支持下面几种依赖源:
- Index: 从特定包索引解析的包
- Git: 一个 Git 仓库
- URL: 一个远程的 wheel 包或源码分发包
- Path: 一个本地 wheel, 源码包或项目目录
- Workspace: 当前工作空间的成员
Optional dependencies
项目作为库发布时, 使一些功能称为可选的, 从而降低默认依赖树的大小, 这种做法很常见.
例如, Pandas 有额外的 excel extra 和 plot extra 包, 来避免安装 Excel 解析器和 matplotlib.
除非明确说明安装, Extras 使用 package[<extra>]
语法指明, 例如 pandas[plot, excel]
.
可选依赖具体在 [project.optional-dependencies]
中, 可选依赖可以像普通依赖一样在 tool.uv.sources
中替换源
[project]
name = "pandas"
version = "1.0.0"
[project.optional-dependencies]
plot = [
"matplotlib>=3.6.3"
]
excel = [
"odfpy>=1.4.1",
"openyxl>=3.1.0",
"python-calamine>=0.17",
"pyxlsb>=1.0.10",
"xlrd>=2.0.1",
"xlsxwriter>-3.0.5"
]
如果要添加可选依赖, 使用 --optional <extra>
选项:
uv add httpx --optional network
源也可以声明为只适用特定的可选依赖项.
例如, 根据可选的 cpu
或 gpu
额外依赖项, 从不同的 PyTorch 索引来去 torch
包
[project]
dependencies = []
[project.optional-dependencies]
cpu = [
'torch',
]
gpu = [
'torch',
]
[tool.uv.sources]
torch = [
{ index = "torch-cpu", extra = "cpu" },
{ index = "torch-gpu", extra = "gpu" },
]
[[tool.uv.index]]
name = "torch-cpu"
url = "https://download.pytorch.org/whl/cpu"
[[tool.uv.index]]
name = "torch-gpu"
url = "https://download.pytorch.org/whl/cu124"
Development dependencies
不同于可选依赖, 开发依赖只会在本地安装, 不会在发布到 PyPI 这样的项目依赖中生效.
因此, 开发依赖不包含在 [project]
表中.
同样地, 开发依赖也可以使用 tool.uv.sources
来指定源.
使用下面命令添加开发依赖
uv add --dev pytest
uv 使用 [dependency-groups]
表来声明开发依赖, 上面命令会创建一个 dev
组
[dependency-groups]
dev = [
"pytest >=8.1.1<9"
]
dev
组是一个特例, 可以使用 --dev
, --only-dev
和 --no-dev
flags 来设置包含或排除依赖.
Dependency groups
开发环境可以被拆分成多个组, 使用 --group
flag.
例如, 想要为开发依赖 ruff
加入 lint 组:
uv add --group lint ruff
这会导致下面的 [dependency-groups]
定义:
[dependency-groups]
dev = [
"pytest"
]
lint = [
"ruff"
]
一但组被定义了, --all-groups
, --no-default-groups
, --group
, --only-group
和 --no-group
选项可以用来包含和排除依赖.
uv 要求所有的依赖组之间是相互兼容的, 并且会在创建 lockfile 时解析所有的组.
如果一组的依赖和其他组的依赖不兼容, uv 会无法解析依赖并报错.
Nesting groups
一个依赖组可以包含其他依赖组, 例如:
[dependency-groups]
dev = [
{include-group = "lint"},
{include-group = "test"}
]
lint = [
"ruff"
]
test = [
"pytest"
]
Default groups
默认情况下, uv 在环境中包含一个 dev
依赖组.
默认的组可以使用 tool.uv.default-groups
来设置:
[tool.uv]
default-groups = ["dev", "foo"]
要默认启用所有的组依赖, 使用 “all” 而不是列出所有的组名:
[tool.uv]
default-groups = "all"
requires-python
组
默认情况下, 依赖组必须和项目 requires-python
兼容.
如果一个依赖组需要不同的 Python 版本, 可以在 [tool.uv.dependency-groups]
里面指定 Python 版本.
[project]
name = "example"
version = "0.0.0"
requires-python = ">=3.10"
[dependency-groups]
dev = ["pytest"]
[tool.uv.dependency-groups]
dev = {requires-python = ">=3.12"}
Legacy dev-dependencies
在 [dependency-groups]
成为标准之前, uv 使用 tool.uv.dev-dependencies
字段来指定开发依赖
[tool.uv]
dev-dependencies = [
"pytest"
]
Build dependencies
如果一个项目使用 Python package 的结构, 它可能需要构建项目的依赖, 但无需运行. 这类依赖是 [build-system]
, 在 build-system.requires
下面, 详见 PEP 518.
例如, 如果一个项目使用 setuptools
作为后端构建, 它应该声明 setuptools
作为一个构建依赖:
[project]
name = "pandas"
version = "0.1.0"
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
默认情况下, uv 构建依赖的时候会检查 tool.uv.sources
.
例如, 使用本地版本的 setuptools
进行构建, 将源添加进 tool.uv.sources
[project]
name = pandas
version = "0.1.0"
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
setuptools = { path = "./packages/setuptools" }
当发布一个包的时候, 建议当 tool.uv.sources
被禁用时, 运行 uv build --no-source
来确保正确构建.
使用其他构建工具也是一样的, 例如 pypa/build
.
Editable dependencies
常规的目录安装会先构建 wheel 包, 然后将该 wheel 包安装到虚拟环境中, 这回复制所有的源文件. 当包源文件被编译时, 虚拟环境中的版本将保持旧版本.
可编辑安装通过向虚拟环境内添加项目连接 (.pth 文件) 解决了这个问题, 该链接指示解释器直接包含源文件. 可编辑安装存在一些限制, 但对于开发常见非常实用, 因为虚拟环境会始终使用包的最新修改.
uv 默认对工作区包采用可编辑安装模式.
Virtual dependencies
uv 运行依赖项设置为"虚拟"模式, 在此模式下, 依赖项本身不会作为软件包被安装, 但其依赖关系会被正常处理.
默认情况下, 依赖项永远不会被视作虚拟依赖.
对于路径源的依赖项, 若显示设置 tool.uv.package = false
则可成为虚拟依赖.
与在依赖项目中使用 uv
工作不同, 即使未声明构建系统, 该软件包仍会被构建.
若要将某个依赖项视位虚拟依赖, 需要在其源配置中设置 package = false
:
[project]
dependencies = ["bar"]
[tool.uv.sources]
bar = { path = "../projects/bar", package = false }
如果依赖设置 tool.uv.package = false
, 则可以在 source 通过声明 package = true
来覆盖.
[project]
dependencies = ["bar"]
[tool.uv.source]
bar = { path = "../projects/bar", package = true }
同样地, 对于工作区源的依赖项, 若显式设置 tool.uv.package = false
也可成为虚拟依赖.
即使未声明构建系统, 该工作区成员仍会被构建.
对于非依赖项的工作区成员, 默认情况下可设置为虚拟模式, 例如, 若父级 pyproject.toml
文件配置如下:
[project]
name = "parent"
version = "1.0.0"
dependencies = []
[tool.uv.workspace]
members = ["child"]
并且 child 的 pyproject.toml
不含有构建系统.
[project]
name = "child"
version = "1.0.0"
dependencies = ["anyio"]
那么 child
workspace 成员将不会被安装, 但是依赖 anyio
会被传递.
相对的, 如果父级声明了对 child
的依赖:
[project]
name = "parent"
version = "1.0.0"
dependencies = ["child"]
[tool.uv.sources]
child = { workspace = true }
[tool.uv.workspace]
members = ["child"]
那么 child
将会被构建和安装.
Dependency specifiers
uv 采用最初由 PEP 508 定义的依赖项限定符, 依赖项限定符按顺序包括以下组件:
- 依赖项名称
- 所需额外功能
- 版本限定符
- 环境标记
版本限定符通过逗号分割进行组合, 例如 foo >=1.2.3,<2,!=1.4.0
表示 “foo
的版本需不低于 1.2.3, 小于 2, 且不能是 1.4.0”.
限定符会自动补零, 因此 foo ==2
也会匹配 foo 2.0.0.
星号可以用于等号匹配的最后一位数字, 例如 foo ==2.1.*
将接受 2.1 系列的所有版本.
类似地, ~=
匹配最后一位数字相等或更高的版本, 例如 foo ~=1.2
等价于 foo >=1.2,<2
, 而 foo ~=1.2.3
等价于 foo >=1.2.3,<1.3
.
额外功能在名称和版本直接的方括号内用逗号分隔, 例如 pandas[excel, plot] ==2.2
.
额外功能名称之间的空格会被忽略.
某些依赖项仅在特定环境中需要, 例如特定的 Python 版本或系统.
比如要为 importlib.metadata
模块安装 importlib-metadata 回溯包, 可使用 importlib-metadata >=7.1.0,<8; python_version < '3.10'
.
要在 Windows 上安装 colorama 则可使用 colorama >=0.4.6,<5; platform_systemm == "Windows
.
环境标记通过 and, or 和括号进行组合, 例如
aiohttp >=3.7.4,<4; (sys_paltform != 'win32' or implementation_name != 'pypy') and python_version >= '3.10'
注意标记内的版本需要加引号, 而标记外的版本不加引号.
Using workspace
工作空间受到 Cargo 的同名概念启发, 即将一个或多个包一起管理, 叫做 workspace.
Workspace 通过将项目拆分多个包和相同的依赖, 从而组织大型代码库.
例如, 有一个 FastAPI 的 web 应用, 和一些列的由不同的 Python 包维护的库, 都放在同一个 Git 仓库中.
在一个 workspace 中, 每个包定义自己的 pyproject.toml
文件, 但是工作空间共享同一个 lockfile, 以确保工作空间内的依赖一致.
在定义 workspace 的时候, 必须要指定 members
(必填) 和 exclude
(可选) 键, 他们分别用于指示工作区包含或排除特定目录作为成员, 并接受通配符列表:
[project]
name = "albatross"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["bird-feeder", "tqdm>=4,<5"]
[tool.uv.sources]
brid-feeder = { workspace = true }
[tool.uv.workspace]
members = ["packages/*"]
exclude = ["packages/seeds"]
由 members
通配符包含的每个目录都必须包含一个 pyproject.toml
文件.
不过, 工作区成员即可以是应用程序, 也可以是库, 这两种类型在工作区环境中都支持.
每个工作区都需要一个根目录, 该目录同时也是工作区成员.
在上面示例中, albatross
是工作区根目录, 工作区成员包括 packages
目录下面除 seeds
以外的所有项目.
默认情况下, uv run
和 uv sync
命令会作用于工作区根目录.
例如上例中, uv run
与 uv run --package albatross
是等效的, 而 uv run --package bird-feeder
则会在 bird-feeder
包中执行命令.
Workspace sources
在一个 workspace 内, 对工作区成员的依赖通过 tool.uv.sources
实现, 例如:
[project]
name = "albatross"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["bird-feeder", "tqdm>=4,<5"]
[tool.uv.sources]
bird-feeder = { workspace = true }
[tool.uv.workspace]
members = ["packages/*"]
[build-system]
requires = ["uv_build>=0.8.20,<0.9.0"]
在此示例中, albatross 项目依赖于同属工作区的 bird-feeder 项目.
tool.uv.sources
表中 workspace = true
的键值对表明, bird-feeder 依赖应由工作区提供, 而非 PyPI 或其他注册源获取.
工作区根目录中的任何 tool.uv.sources
定义将适用于所有成员, 除非在特定成员的 tool.uv.sources
中被覆盖.
Workspace layouts
最常见的工作区布局可以是一个根目录和一系列的库组成.
例如, 接着上面的例子, 工作区间有一个根目录 albatross
, 以及 packages
目录下面的两个库 bird-feeder
和 seed
.
albatross
├── packages
│ ├── bird-feeder
│ │ ├── pyproject.toml
│ │ └── src
│ │ └── bird_feeder
│ │ ├── __init__.py
│ │ └── foo.py
│ └── seeds
│ ├── pyproject.toml
│ └── src
│ └── seeds
│ ├── __init__.py
│ └── bar.py
├── pyproject.toml
├── README.md
├── uv.lock
└── src
└── albatross
└── main.py
由于 seeds
被 pyproject.toml
排除在外, 故该工作区一共有两个成员: albatross
和 bird-feeder
.
When (not) to use workspaces
Workspaces 是为了促进同一仓库下的多个包之间内部连接的开发. 当代码库逐渐变复杂, 将其切分成更小的可组合的包将十分有用. 每个包都有自己的依赖和版本限制.
工作区有助于执行隔离和分离问题. 例如, 在 uv 中, 有分离的 core 包和命令行界面, 这使得我们可以分开测试 core 包和 CLI, 反之亦然.
其他常见工作区间使用包括:
- 在模块中实现了性能关键的扩展库
- 插件系统库, 每个插件是一个分离的 workspace 包和一个根目录下的依赖
工作区模式不适用于成员之间存在需求冲突, 或需要为每个成员创建独立虚拟环境的场景.
此类情况下, 路径依赖通常是更优选择.
例如, 无需将 albatross 及其相关组件强制归入同一工作区, 可以将每个软件包定义为独立项目, 并通过在 tool.uv.sources
中配置路径依赖来定义包间依赖关系.
[project]
name = "albatross"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["bird-feeder", "tqdm>=4,<5"]
[tool.uv.sources]
bird-feeder = { path = "packages/bird-feeder" }
[build-system]
requires = ["uv_build>=0.8.20,<0.9.0"]
build-backend = "uv_build"
这种方法能带来许多相同优势, 同时运行依赖解析和虚拟环境管理进行更精细的控制 (但无法使用 uv run --package
命令, 需要从对应软件目录运行命令).
最后, uv 的工作区强制要求整个工作区间采用统一的 requires-python
配置, 该配置取所有 requires-python
值的交集.
如果需要给某个成员测试工作区其他成员不支持的 Python 版本, 可能需要使用 uv pip
将该成员安装到独立的虚拟环境中.