跳到内容

使用工作区

工作空间的概念灵感来自 Cargo 同名概念,它指的是“一个或多个包的集合,称为工作空间成员,它们被一起管理。”

工作空间通过将大型代码库拆分为具有共同依赖项的多个包来组织代码库。例如:一个基于 FastAPI 的 Web 应用程序,以及一系列版本化并作为单独 Python 包维护的库,全部在同一个 Git 存储库中。

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

因此,uv lock 一次性作用于整个工作空间,而 uv runuv sync 默认作用于工作空间根目录,但两者都接受 --package 参数,允许您从任何工作空间目录在特定的工作空间成员中运行命令。

快速入门

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

提示

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

在定义工作空间时,您必须指定 members(必需)和 exclude(可选)键,这些键指示工作空间分别包含或排除特定目录作为成员,并接受 glob 列表

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 glob 包含(且未被 exclude glob 排除)的每个目录都必须包含一个 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 条目。

注意

如果一个工作空间成员为某个依赖项提供了 tool.uv.sources,它将忽略工作空间根目录中同一依赖项的任何 tool.uv.sources,即使该成员的源受到与当前平台不匹配的标记的限制。

工作空间布局

最常见的工作空间布局可以被认为是一个带有系列配套库的根项目。

例如,继续上面的例子,这个工作空间在 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

由于 seedspyproject.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 无法确保包不导入由另一个工作空间成员声明的依赖项。