从 requirements.txt 到 uv:一个 Streamlit 项目的 Python 工程化实录
前言
我们的项目 platform_v1 是一个基于 Streamlit 的内部实验平台,还带有 FastAPI 后端。前期为了赶进度,依赖写在 requirements.txt 里,业务代码堆在 src/ 下,各页面通过 sys.path.insert 才能 import。能跑,但新人上手难、环境不一致、改 import 心里没底。
这篇文章记录我们第一次把项目往现代 Python 工程化方向推的过程:引入uv、pyproject.toml、platform_v1可安装包 ,并清掉 API 层的 src.api 旧路径。不是教科书式的完美方案,而是我们实际做了什么、卡在哪里、怎么验证。
一、为什么要改?
1. 只有 requirements.txt 时
-
只声明「要装什么」,不声明「项目本身是什么」
-
每个人
pip install -r requirements.txt装到的传递依赖版本可能不同 -
没有锁文件时,很难复现「上个月能跑、今天不能跑」的环境
2. sys.path 是技术债
典型写法:
_SRC = Path(__file__).resolve().parent sys.path.insert(0,
str(_SRC)) from biocontrol_fedbatch import run_simulation
问题在于:
-
依赖从哪启动、当前工作目录
-
和「已安装的包」不是同一套机制,IDE、测试、CI 行为不一致
-
我们仓库里甚至并存两种风格:
from biocontrol_...与from src.api...
工程化的目标:from platform_v1.xxx import ...自己的代码也像 streamlit、fastapi 一样,装进虚拟环境,用 导入。
二、工具选择:为什么用 uv?
社区里常见 PDM / Poetry / uv。我们最终选uv,原因很务实:
|
能力 |
说明 |
|---|---|
|
依赖 + 锁文件 |
|
|
环境 |
|
|
运行 |
|
|
速度 |
大依赖(torch、rdkit 等)解析安装相对省心 |
常见误解要澄清:
-
pip install uvpip install pdmpyproject.toml/ 不会自动生成 -
要写 toml、要
uv init/uv add,或pdm init/pdm import
三、我们做了什么(分阶段)
阶段 1:依赖工程化
# 安装 uv(macOS) curl -LsSf https://astral.sh/uv/install.sh | sh source $HOME/.local/bin/env uv init --name platform-v1 uv add -r requirements.txt # 注意处理本地 iCH360 等特殊行 uv sync
得到:
-
pyproject.toml:项目元数据 + 依赖列表 -
uv.lock:完整依赖树锁定
此时还没有解决「自己的代码怎么 import」。
阶段 2:可安装包 + 目录搬迁
在 pyproject.toml 增加构建后端:
[build-system] requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/platform_v1"]
把原来平铺在 src/ 下的代码挪到:
src/platform_v1/ __init__.py api/ pages/ settings.py ...
然后:
uv sync --reinstall-package platform-v1
uv run python -c "import platform_v1; print(platform_v1.__file__)"
踩坑 1:空 pyproject.toml / 空目录时 sync
搬迁前 sync 会装上一个只有 dist-info、没有代码的空包。 搬迁后若只 uv sync 而没有重建,import platform_v1 仍会失败。
解决:
uv sync --reinstall-package platform-v1
踩坑 2:import platform_v1 成功 ≠ 工程化完成
包能导入,只说明安装路径对了;业务里仍是 from biocontrol_... 时,Streamlit 仍可能挂。
阶段 3:API 层 — 去掉 sys.path,统一 platform_v1.api
start_api.py 之前:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
uvicorn.run("src.api.main:app", ...)
之后:
uvicorn.run("platform_v1.api.main:app", host="0.0.0.0", port=8000, reload=True)
api/ 下约 30 个文件,规则只有一条:
from src.api.xxx → from platform_v1.api.xxx
验证:
uv run python -c "from platform_v1.api.main import app; print('OK', app.title)"
# OK Enzyme-Agent uv run python start_api.py
踩坑 3:导入链在「第一个漏改的文件」断掉
报错示例:
... database.py", line 6, in <module> from src.api.config import get_settings ModuleNotFoundError: No module named 'src.api'
说明:main.py 已改对,但链路上的 database.py 等仍有一处 src.api。
排查(macOS 没有 rg 时):
grep -r "from src\.api" src/platform_v1/api/ tests/ scripts/
踩坑 4:编辑器里改完了,磁盘上还没保存
本机 uv run 读的是已保存文件。改完务必 Save All,再以终端为准。
四、两层工程化(一张图记住)
```mermaid flowchart LR A[pyproject + uv.lock] --> B[第三方依赖可复现] C[src/platform_v1 包] --> D[import platform_v1.*] B --> E[uv sync] D --> E E --> F[改代码更有底气] ```
-
第一层:uv 管「装什么」
-
第二层:包布局管「代码在哪、怎么引」
两层都做好,才接近「工程化」;只做第一层,只是「换了个依赖文件格式」。
五、我们还没做完什么(诚实收尾)
|
项 |
状态 |
|---|---|
|
API |
✅ 主路径已通 |
|
Streamlit |
⏳ |
|
|
⏳ |
|
CI: |
⏳ |
|
README 全面改成 uv 工作流 |
⏳ |
读者如果是同类项目,可以先 API、再 Streamlit、再 CI,不要一次改完全仓库。
六、给后来者的检查清单
# 包能导入
uv run python -c "import platform_v1; print(platform_v1.__file__)"
# API 能导入
uv run python -c "from platform_v1.api.main import app; print(app.title)"
# 无旧 import(API 目录)
grep -r "from src\.api" src/platform_v1/api/ || echo "clean"
# 测试
uv run pytest
七、总结
1.
pip install uv
只是装 CLI,工程化要靠 pyproject.toml、锁文件和包结构。 2. sys.path 是补丁,可编辑安装的 platform_v1 才是正道。 3. --reinstall-package搬迁目录后记得 ,否则可能是空包。 4. 批量改 import 要用 grep 收尾,导入链会在第一个漏改处断掉。 5. CI 是下一步:自动 pytest + 规范检查,改代码才有「机器帮你看过」的底气。
附录:常用命令
uv sync
uv sync --reinstall-package platform-v1
uv add 包名
uv run streamlit run src/platform_v1/首页.py
uv run python start_api.py
uv run pytest
更多推荐

所有评论(0)