3.4.9 使用工作区
5 分钟阅读
原文链接: https://docs.astral.sh/uv/concepts/projects/workspaces/
3.4.9 使用工作区
工作区(workspace)的概念受 Cargo 同名概念启发,指“一个或多个称为工作区成员的包的集合,它们被一起管理”。
工作区通过把庞大的代码库拆分为多个具有公共依赖的包来组织它们。可以想象:一个基于 FastAPI 的 Web 应用,连同若干作为独立 Python 包进行版本管理和维护的库,全部位于同一个 Git 仓库中。
在工作区中,每个包定义自己的 pyproject.toml,但整个工作区共享一个锁文件,确保工作区以一致的依赖集合运行。
因此,uv lock 会一次性作用于整个工作区,而 uv run 和 uv sync 默认作用于工作区根目录,不过两者都接受 --package 参数,让你可以从任意工作区目录在特定工作区成员中运行命令。
快速开始
要创建工作区,请向 pyproject.toml 添加一个 tool.uv.workspace 表,它会在该包处隐式创建以它为根的工作区。
提示
默认情况下,在已有包内部运行
uv init会把新创建的成员加入工作区;如果工作区根目录中尚无tool.uv.workspace表,还会创建它。
定义工作区时,必须指定 members(必需)和 exclude(可选)键,它们分别指示工作区把特定目录包含或排除为成员,接受 glob 列表:
| |
被 members glob 包含(且未被 exclude glob 排除)的每个目录都必须包含 pyproject.toml 文件。不过工作区成员既可以是应用,也可以是库;工作区场景下两者都支持。
每个工作区都需要一个根,它同样是工作区成员。在上面的示例中,albatross 是工作区根,工作区成员包括 packages 目录下除 seeds 之外的所有项目。
默认情况下,uv run 和 uv sync 作用于工作区根。例如在上面的示例中,uv run 与 uv run --package albatross 等价,而 uv run --package bird-feeder 会在 bird-feeder 包中运行命令。
工作区来源
在工作区内,对工作区成员的依赖通过 tool.uv.sources 建立,例如:
| |
在这个示例中,albatross 项目依赖 bird-feeder 项目,后者是工作区成员。tool.uv.sources 表中的 workspace = true 键值对表示 bird-feeder 依赖应由工作区提供,而不是从 PyPI 或其他仓库获取。workspace 字段也可以设置为路径字符串,以从其他工作区解析依赖。该路径相对于声明该来源的项目(对于工作区级来源,则相对于工作区根)解析,并且必须指向外部工作区根。uv 会选择与依赖名匹配的成员。
注意
工作区成员之间的依赖是可编辑的。
工作区根中的任何 tool.uv.sources 定义都适用于所有成员,除非在某个成员的 tool.uv.sources 中被覆盖。例如,给定如下 pyproject.toml:
| |
默认情况下,每个工作区成员都会从 GitHub 安装 tqdm,除非某个成员在自己的 tool.uv.sources 表中覆盖了 tqdm 条目。
注意
如果某个工作区成员为某个依赖提供了
tool.uv.sources,它会忽略工作区根中该依赖的任何tool.uv.sources,即使该成员的来源被一个与当前平台不匹配的标记限制。
工作区布局
最常见的工作区布局可以看作一个根项目加上一系列配套库。
例如,延续上面的示例,该工作区在 albatross 处有显式根,packages 目录中有两个库(bird-feeder 和 seeds):
| |
由于 seeds 在 pyproject.toml 中被排除,该工作区共有两个成员:albatross(根)和 bird-feeder。
何时(不)使用工作区
工作区旨在便于在单个仓库中开发多个相互关联的包。随着代码库复杂度增加,把它拆分为更小、可组合的包(每个包有自己的依赖和版本约束)会很有帮助。
工作区有助于实现隔离和关注点分离。例如在 uv 中,核心库和命令行接口就是分开的包,这让我们可以独立于 CLI 测试核心库,反之亦然。
工作区的其他常见用例包括:
- 一个库,其性能关键的子程序用扩展模块(Rust、C++ 等)实现。
- 一个带插件系统的库,其中每个插件都是依赖根包的独立工作区包。
工作区不适合成员之间存在冲突要求,或希望每个成员有独立虚拟环境的情况。这种情况下,路径依赖通常更可取。例如,你可以不把 albatross 及其成员归入同一工作区,而是把每个包定义为独立项目,并在 tool.uv.sources 中把包间依赖定义为路径依赖:
| |
这种方式带来许多相同的好处,但对依赖解析和虚拟环境管理有更细粒度的控制(缺点是 uv run --package 不再可用;此时必须从相关包目录运行命令)。
最后,uv 的工作区对整个工作区强制使用单一的 requires-python,取所有成员 requires-python 值的交集。如果你需要在工作区其余部分不支持的 Python 版本上测试某个成员,可能需要使用 uv pip 在单独的虚拟环境中安装该成员。
注意
由于 Python 不提供依赖隔离,uv 无法确保某个包只使用它声明的依赖而不使用其他依赖。具体到工作区,uv 无法确保包不导入其他工作区成员声明的依赖。