
项目里一共四个 Python 子包,仓库根目录却躺着六份 requirements.txt。
更离谱的是,公共库改了一行代码,订单服务里没生效。查了半天,发现有人装的是本地 editable 包,有人装的是私服上个月的版本,还有一个人的虚拟环境压根没切对。
这种仓库我一般不急着看业务代码,先看依赖怎么管。依赖关系已经乱了,代码跑出什么结果都不奇怪。
以前处理 Python 多包项目,经常是这个样子:
trade-platform/
├── order_api/
│ ├── requirements.txt
│ └── .venv/
├── settlement_job/
│ ├── requirements.txt
│ └── .venv/
├── packages/
│ └── biz_rules/
│ ├── requirements.txt
│ └── .venv/
└── scripts/
└── install_local.sh
order_api 要调用 biz_rules,开发机先执行:
pip install -e ./packages/biz_rules
CI 里再写一遍,Dockerfile 里还得复制一遍。目录稍微调整一下,脚本全红。
UV Workspace 处理的就是这类问题:一个 Git 仓库放多个 Python 项目,每个子包保留自己的 pyproject.toml,但整个工作区共用一份 uv.lock。uv lock 会解析整个工作区,uv run、uv sync 也能通过 --package 指定某个子包。
先把目录收一下:
trade-platform/
├── pyproject.toml
├── uv.lock
├── services/
│ ├── order-api/
│ │ ├── pyproject.toml
│ │ └── src/order_api/
│ └── settlement-job/
│ ├── pyproject.toml
│ └── src/settlement_job/
└── packages/
└── biz-rules/
├── pyproject.toml
└── src/biz_rules/
根目录的配置不用写得很花:
[project]
name = "trade-platform"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[tool.uv.workspace]
members = [
"services/*",
"packages/*",
]
members 支持 glob。被匹配到的目录必须有自己的 pyproject.toml,不想纳入管理的临时项目,可以再配 exclude。
公共规则包里放真正需要复用的东西。别把数据库连接、全局配置这些也塞进去,最后公共包长成第二个单体应用。
# packages/biz-rules/src/biz_rules/pricing.py
from dataclasses import dataclass
from decimal import Decimal
@dataclass(frozen=True, slots=True)
class PriceLine:
sku: str
amount: Decimal
def calculate_payable(lines: list[PriceLine]) -> Decimal:
total = sum(
(line.amount for line in lines),
start=Decimal("0"),
)
if total <= 0:
raise ValueError("订单应付金额必须大于 0")
return total.quantize(Decimal("0.01"))
订单服务依赖这个包,不能只在代码里直接 import。这种写法我见过不少,本机能跑,换台机器立刻报错,因为依赖根本没声明。
在 services/order-api/pyproject.toml 里写清楚:
[project]
name = "order-api"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"biz-rules",
"fastapi>=0.115",
]
[tool.uv.sources]
biz-rules = { workspace = true }
workspace = true 表示这个依赖从当前 Workspace 里找,不去 PyPI 碰运气。工作区成员之间默认按 editable 方式安装,公共包代码改完,不需要每次重新打 wheel。
不想手改配置,也可以直接在根目录执行:
uv add --package order-api ./packages/biz-rules
uv add --package order-api fastapi
这里的 --package order-api 很重要。它改的是订单服务自己的依赖,不是把所有东西都堆到根项目里。
配置完成后,整个仓库第一次拉下来只需要:
uv sync
虚拟环境、第三方依赖、本地子包和 uv.lock 一起处理。UV 在执行 uv run 时也会自动检查锁文件和环境是否需要同步,这比“先激活环境,再装依赖,再祈祷版本一致”省事得多。
启动某个服务也不用 cd 来 cd 去:
uv run --package order-api python -m order_api.main
跑结算任务:
uv run --package settlement-job python -m settlement_job.runner
查订单服务到底拉进来了哪些依赖:
uv tree --package order-api
我比较喜欢把 CI 也收成同一套命令:
uv sync --locked
uv run --package order-api pytest services/order-api/tests
uv run --package settlement-job pytest services/settlement-job/tests
--locked 的意思不是更新锁文件,而是要求当前配置必须和锁文件一致。有人改了 pyproject.toml 却没提交新的 uv.lock,CI 直接失败,别让它悄悄解析出另一套版本。
不过 Workspace 也别乱套。
它共享一套依赖解析和工作区环境,不是给每个子包造一堵完全隔离的墙。订单服务要求 pydantic<2,结算任务非要 pydantic>=2,这种明显冲突还硬塞进一个 Workspace,最后只会和解析器较劲。UV 官方也明确提醒,成员依赖冲突严重,或者必须使用独立虚拟环境时,普通路径依赖往往更合适。
所以我判断一个仓库要不要上 Workspace,看的不是“子目录多不多”,而是这些子包是不是同一套工程的一部分。
它们一起开发、一起测试、需要引用本地公共库,版本也能协商,那就放进去。最后仓库里留一份锁文件、一套命令,谁再提交第五份 requirements.txt,代码评审里直接打回去。
以上就是“UV Workspace:一条命令管好所有 Python 子包!”的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。
扫码二维码 获取免费视频学习资料

- 本文固定链接: http://www.phpxs.com/post/14352/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料