UV Workspace 完全指南:轻松管理多子项目Python依赖
最近在看 google A2A 协议,它在官方的github 仓库有一些示例, https://github.com/google/A2A/tree/main/samples/python ,这些示例使用了uv ,并且用了 workspace,这个是之前使用uv 时没有接触过的,本文记录以下在uv 中如何使用 workspace 。
workspace 的定义
UV 的 Workspace 功能是用于管理包含多个子项目的 Python 代码库的工具,其设计灵感来自 Rust 的 Cargo。
1. 核心概念
- 共享依赖管理:工作区内的所有子项目共享同一个虚拟环境和锁文件(
uv.lock
),确保依赖一致性。
- 独立配置:每个子项目可定义自己的
pyproject.toml
,但通过工作区根目录的配置统一协调。
2. 有什么好处
关于使用 workspace 有什么好处呢? 我认为最主要的是可以共享项目依赖,拿上面提到的 A2A sample,以下是目录结构(agents 目录下做了以写删减)
| .
├── README.md
├── agents
│ ├── ag2
│ ├── crewai
├── common
├── hosts
├── pyproject.toml
└── uv.lock
|
这个项目的根目录下有common 和hosts 两个目录,这里的函数或者类在 agents 下各种不同的agent 框架都需要用的到的,而 agents 下的 crewai 和ag2 是两个不同的框架,虽然他们都需要目录下的 common 和 hosts ,但是他们的底层是不同的,如果用户只想运行 ag2 这个例子,那他完全没有必要同步 crewai 下的依赖
[!warning]
crewai 的依赖非常多!!!
如果没有用到 workspace, 那么需要将每个agent 目录抽出来形成单独的仓库,并且根目录下的common 和 hosts 需要复制到每个仓库中,如果哪天common 下的函数修改了,那么还需要同步到所有子项目中。
说了那么多,让我们来看一下如何在uv 中使用workspace 功能? A2A 官方那个实例有点复杂,我使用命令行从头开始创建一个新的uv 项目。
实战一下
| uv init root_project # 创建根项目
cd my_project
uv init service_a # 创建子项目
uv init service_b
|
此时的root_project 的pyproject.toml 内容
| [project]
name = "root-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = []
[tool.uv.workspace]
members = [
"service_a",
"service_b",
]
|
整个目录文件结构为
| .
├── README.md
├── main.py
├── pyproject.toml
├── service_a
│ ├── README.md
│ ├── main.py
│ └── pyproject.toml
├── service_b
│ ├── README.md
│ ├── main.py
│ └── pyproject.toml
└── uv.lock
|
在一个uv 的项目中使用 uv init
初始化项目,这个项目会自动添加到根项目的 tool.uv.workspace
的members 中。 这个配置很重要!
[!notice] 尽量使用uv 自动管理
最好不要手工修改pyproject.toml 文件,除非迫不得已
在root_project 下的pyproject.toml 中添加两个依赖 (也可以先使用 uv sync 创建好虚拟环境,然后再使用 uv add 添加依赖)
| dependencies = [
"httpx>=0.28.1",
"httpx-sse>=0.4.0",
]
|
使用 uv sync 同步依赖,此时会将刚才配置的 httpx 和 httpx-sse 依赖安装好。
在子项目中就可以使用 httpx 库了,而不需要在子项目的pyproject.toml 中再次添加 httpx 依赖
子项目单独的依赖
在根项目中定义的依赖,所有工作区内的项目都可以直接使用,那么如果工作区需要用到单独的依赖呢?
先 cd 到子项目中
| cd service_a
uv add requests
|
此时service_a 中的 pyproject.toml 中已经自动添加了依赖
| [project]
name = "service-a"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"requests>=2.32.3",
]
|
但是此时,并没有在 service_a 中创建 .venv 虚拟环境,新添加的requests 库其实安装到了根项目的 .venv 中,相应的文件版本信息也写到了 uv.lock 文件中。
同样的操作在 service_b 中添加 pydantic 包。
回到 root_project 目录下运行 uv tree
查看依赖关系
| (root-project) ➜ root_project git:(master) ✗ uv tree
Resolved 19 packages in 29ms
service-b v0.1.0
└── pydantic v2.11.4
├── annotated-types v0.7.0
├── pydantic-core v2.33.2
│ └── typing-extensions v4.13.2
├── typing-extensions v4.13.2
└── typing-inspection v0.4.0
└── typing-extensions v4.13.2
service-a v0.1.0
└── requests v2.32.3
├── certifi v2025.4.26
├── charset-normalizer v3.4.2
├── idna v3.10
└── urllib3 v2.4.0
root-project v0.1.0
├── httpx v0.28.1
│ ├── anyio v4.9.0
│ │ ├── idna v3.10
│ │ ├── sniffio v1.3.1
│ │ └── typing-extensions v4.13.2
│ ├── certifi v2025.4.26
│ ├── httpcore v1.0.9
│ │ ├── certifi v2025.4.26
│ │ └── h11 v0.16.0
│ └── idna v3.10
└── httpx-sse v0.4.0
|
其实此时service_a 和 service_b 还有roo_project 三个项目公用的是同一套虚拟环境,service_a 和 service_b 可以引用根项目的模块。
[!success] 最佳实践
既然使用了 workspace,那么就不要让子项目互相引用,如 service_a 引用service_b 中的类,如果引用了,那么就失去了子项目的意义了!
如何同步依赖
先将之前创建的虚拟环境删除掉,模拟从github 仓库下载的项目
现在三个pyproject.toml 分别定义如下
root_project:
| [project]
name = "root-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"httpx>=0.28.1",
"httpx-sse>=0.4.0",
]
[tool.uv.workspace]
members = [
"service_a",
"service_b",
]
|
service_a:
| [project]
name = "service-a"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"requests>=2.32.3",
]
|
service_b:
| [project]
name = "service-b"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"pydantic>=2.11.4",
]
|
此时我在项目根目录下运行 uv sync 命令
| root_project git:(master) ✗ uv sync
Using CPython 3.11.10 interpreter at: /opt/miniconda3/bin/python3.11
Creating virtual environment at: .venv
Resolved 19 packages in 51ms
Installed 9 packages in 105ms
+ anyio==4.9.0
+ certifi==2025.4.26
+ h11==0.16.0
+ httpcore==1.0.9
+ httpx==0.28.1
+ httpx-sse==0.4.0
+ idna==3.10
+ sniffio==1.3.1
+ typing-extensions==4.13.2
|
可以看到只安装了root_project 中定义的 httpx 和 httpx-sse相关的依赖,servive_a 中定义的 requests 和 service_b 中定义的 pydantic 均没有下载。
查看官方的解释
By default, uv run
and uv sync
operates on the workspace root. For example, in the above example, uv run
and uv run --package albatross
would be equivalent, while uv run --package bird-feeder
would run the command in the bird-feeder
package.
也就是说:uv run
和 uv sync
默认在工作区根目录下运行,那么如果我想即安装根目录下的依赖,又安装 service_a 项目的依赖,应该怎么操作呢?
上面的文档中也说明了,可以通过 --package
参数指定包名,包名又是如何获取的?
查看 service_a 的 pyproject.toml 定义
| [project]
name = "service-a"
version = "0.1.0"
...
|
这里的 name 就是包名,可以使用 uv sync --package service-a
来同步依赖
| root_project git:(master) ✗ uv sync --package service_a
Resolved 19 packages in 3ms
Uninstalled 7 packages in 68ms
Installed 3 packages in 111ms
- anyio==4.9.0
+ charset-normalizer==3.4.2
- h11==0.16.0
- httpcore==1.0.9
- httpx==0.28.1
- httpx-sse==0.4.0
+ requests==2.32.3
- sniffio==1.3.1
- typing-extensions==4.13.2
+ urllib3==2.4.0
|
可以看到,这次是把service_a 中的requests 安装上了,但是也把roo_project 中的 httpx 和 httpx-sse 也给删掉了 😓
使用 uv sync
是不行了,目前没有很好的方法可以解决这个问题,或者我还不知道,如果有清楚怎么操作的请留言指出。
不过uv sync 还有一个 --all-packages
参数,可以同步所有的依赖包,使用这个命令就可以同时安装root_project 和 它下面的所有子项目依赖
| root_project git:(master) ✗ uv sync --all-packages
Resolved 19 packages in 26ms
Installed 11 packages in 102ms
+ annotated-types==0.7.0
+ anyio==4.9.0
+ h11==0.16.0
+ httpcore==1.0.9
+ httpx==0.28.1
+ httpx-sse==0.4.0
+ pydantic==2.11.4
+ pydantic-core==2.33.2
+ sniffio==1.3.1
+ typing-extensions==4.13.2
+ typing-inspection==0.4.0
|
但是这种方式,会安装好的额外的包,也不是很方便。
这里提供一个解决方案
进到相应的子项目目录,然后使用 uv run xxxx.py
,先运行一下这个项目,这时uv 就会先同步这个子项目的依赖,然后再运行相应的脚本。
这种方法,不会像 uv sync 那样删除掉root_project 下的依赖。
| root_project git:(master) ✗ uv sync # 在根目录下运行 uv sync,同步根项目依赖
Using CPython 3.11.10 interpreter at: /opt/miniconda3/bin/python3.11
Creating virtual environment at: .venv
Resolved 19 packages in 24ms
Installed 9 packages in 75ms
+ anyio==4.9.0
+ certifi==2025.4.26
+ h11==0.16.0
+ httpcore==1.0.9
+ httpx==0.28.1
+ httpx-sse==0.4.0
+ idna==3.10
+ sniffio==1.3.1
+ typing-extensions==4.13.2
root_project git:(master) ✗ source .venv/bin/activate # 激活虚拟环境
(root-project) ➜ root_project git:(master) ✗ cd service_b # cd 到子项目
(root-project) ➜ service_b git:(master) ✗ uv run main.py
Installed 4 packages in 54ms # 自动安装依赖
Hello from service-b!
|
总结
本文介绍了在uv 中如何使用workspace 来管理主项目和子项目,以及在下载一个含有 workspace 的项目以后如何同步依赖,上面的示例很简单,如果感兴趣的可以查看A2A 仓储的sample,这个也不是太复杂,相信看完此文章,再查看sample 就会清晰很多了。