跳转到内容

使用工作区

灵感来源于同名概念Cargo,工作区是"一个或多个包的集合,称为工作区成员,它们被统一管理。"

工作区通过将大型代码库拆分为具有共同依赖关系的多个包来组织它们。例如:一个基于FastAPI的Web应用程序,以及一系列作为独立Python包进行版本控制和维护的库,所有这些都位于同一个Git仓库中。

在工作区中,每个包都定义了自己的pyproject.toml,但工作区共享一个锁文件,确保工作区使用一致的依赖项集运行。

因此,uv lock会一次性对整个工作区进行操作,而uv runuv sync默认情况下仅在工作区根目录下运行,不过这两个命令都接受--package参数,允许您从任何工作区目录对特定工作区成员运行命令。

入门指南

要创建工作区,请在pyproject.toml中添加一个tool.uv.workspace表,这将隐式创建一个以该包为根的工作区。

提示

默认情况下,在现有包内运行uv init会将新创建的成员添加到工作区,如果工作区根目录中尚不存在tool.uv.workspace表,则会创建该表。

在定义工作区时,必须指定members(必需)和exclude(可选)键, 它们分别指示工作区包含或排除特定目录作为成员,并接受通配符列表:

pyproject.toml
[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/*"]
exclude = ["packages/seeds"]

members通配符包含(且未被exclude通配符排除)的每个目录都必须包含一个pyproject.toml文件。不过,工作区成员可以是应用程序;这两种类型在工作区上下文中都受支持。

每个工作区都需要一个根目录,它同时也是工作区成员。在上面的例子中,albatross是工作区根目录,工作区成员包括packages目录下的所有项目,但seeds除外。

默认情况下,uv runuv sync 在工作区根目录下运行。例如,在上面的例子中,uv runuv run --package albatross 是等效的,而 uv run --package bird-feeder 则会在 bird-feeder 包中运行命令。

工作区数据源

在工作区内,对工作区成员的依赖通过tool.uv.sources实现,例如:

pyproject.toml
[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 = ["hatchling"]
build-backend = "hatchling.build"

在这个例子中,albatross项目依赖于工作区成员bird-feeder项目。tool.uv.sources表中的workspace = true键值对表明bird-feeder依赖项应由工作区提供,而不是从PyPI或其他注册表获取。

注意

工作区成员之间的依赖关系是可编辑的。

工作区根目录中的任何 tool.uv.sources 定义适用于所有成员,除非在特定成员的 tool.uv.sources 中被覆盖。例如,给定以下 pyproject.toml

pyproject.toml
[project]
name = "albatross"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["bird-feeder", "tqdm>=4,<5"]

[tool.uv.sources]
bird-feeder = { workspace = true }
tqdm = { git = "https://github.com/tqdm/tqdm" }

[tool.uv.workspace]
members = ["packages/*"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

默认情况下,每个工作区成员都会从GitHub安装tqdm,除非特定成员在其自己的tool.uv.sources表中覆盖了tqdm条目。

工作区布局

最常见的工作区布局可以被视为一个根项目附带一系列配套库。

例如,继续上面的示例,这个工作区在albatross处有一个显式根目录, 在packages目录中包含两个库(bird-feederseeds):

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

何时(不)使用工作区

工作区旨在促进在单个代码仓库中开发多个相互关联的包。随着代码库复杂度的增长,将其拆分为更小、可组合的包会很有帮助,每个包都有自己的依赖项和版本约束。

工作区有助于实现隔离和关注点分离。例如,在uv中,我们为核心库和命令行界面分别设置了独立的包,这使得我们能够独立于CLI测试核心库,反之亦然。

工作区的其他常见用例包括:

  • 一个库,其中包含在扩展模块(Rust、C++等)中实现的关键性能子程序。
  • 一个带有插件系统的库,其中每个插件都是一个独立的工作区包,依赖于根目录。

工作区适用于成员需求冲突或希望为每个成员创建独立虚拟环境的情况。在这种情况下,路径依赖通常是更好的选择。例如,与其将albatross及其成员分组到工作区中,您始终可以将每个包定义为其独立项目,并在tool.uv.sources中将包间依赖关系定义为路径依赖:

pyproject.toml
[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 = ["hatchling"]
build-backend = "hatchling.build"

这种方法传递了许多相同的好处,但允许对依赖项解析和虚拟环境管理进行更细粒度的控制(缺点是uv run --package不再可用;相反,必须从相关包目录运行命令)。

最后,uv的工作区强制要求整个工作区使用单一的requires-python版本,取所有成员requires-python值的交集。如果您需要测试某个成员在不被工作区其他部分支持的Python版本上的兼容性,可能需要使用uv pip将该成员安装到单独的虚拟环境中。

注意

由于Python不提供依赖隔离,uv无法确保一个包仅使用其声明的依赖项而不使用其他内容。特别是对于工作区,uv无法确保包不会导入由另一个工作区成员声明的依赖项。