文章目录
迁移前要确定的事
- Python 版本区间:项目实际验证过的版本
- 非默认来源 :私有源地址、
--extra-index-url、--trusted-host、裸 wheel URL - 当前版本快照 :
pip freeze > baseline-freeze.txt,迁移后拿它做对照,出问题时也能回滚 - 搞清楚两个容易混的概念 :
.python-version和requires-python.python-version:由uv python pin生成,决定uv run、uv sync用哪个本地解释器requires-python:写在pyproject.toml里,决定uv lock解析时覆盖的版本区间。区间越窄,锁文件越小
初始化 uv 项目
已有 pyproject.toml 则跳过 uv init
bash
uv init --no-readme
uv python pin 3.12
toml
[project]
requires-python = ">=3.12,<3.13"
最好把 .python-version 提交,用 git 管理。
一个容易踩的坑:uv pip install 不读 requires-python,只认当前激活环境或当前目录下的 .venv。所以用 uv pip 系列命令之前,先跑一遍 uv sync 把 .venv 建好。
导入 requirements.txt
requirements.txt 中的关键包最好锁定精确版本:
text
# 不推荐
vlt-xxx-aws
# 推荐
vlt-xxx-aws==0.1.53
为什么要精确?一是私有源上的包往往没有严格的 semver 保证,版本号跳动的含义跟 PyPI 上的包不是一回事;二是如果内部包跟 PyPI 上某个公开包重名,不锁精确版本时解析器可能会取到完全没预料到的来源。这类问题排查起来很费时间,因为表现出来就是"依赖版本对不上",但根源是包来源搞错了。
导入命令:
bash
uv add -r requirements.txt
uv add -r 会重写 pyproject.toml 并重新解析锁文件。uv sync 只按现有锁文件同步环境,不能替代迁移导入。
私有源踩过的坑
举一个真实场景。配好私有源之后,uv add 突然报这样的错:
text
No solution found when resolving dependencies:
`-> Because there is no version of psutil==6.1.0 ...
hint: `psutil` was found on http://xxxx-pip.xxxx.lan/simple, but not at the requested version
A compatible version may be available on a subsequent index ...
第一反应往往是"这个包是不是被删了",但其实包还在,只是版本不全------原因出在 uv 默认的 index-strategy = "first-index" 策略上:uv 按索引顺序查找,包名一旦在某个索引里找到,就只从这个索引解析这个包,不会再去后面的索引找更全的版本。这里的坑是,内部镜像代理了 psutil,但只同步了部分版本,uv 找到包名就停手了,根本不会意识到后面还有一个版本更全的索引。
索引配置长这样:
toml
[[tool.uv.index]]
name = "private"
url = "http://xxo-private-pip.xxxx.lan:9090/simple"
default = true
[[tool.uv.index]]
name = "internal-wheels"
url = "http://172.xx.xxx.229:8899/simple/"
[[tool.uv.index]]
name = "xxp"
url = "http://xxp-pip.xx.lan/simple"
[tool.uv]
allow-insecure-host = [
"xxo-private-pip.xxxx.lan",
"172.xx.xxx.229",
"xxp-pip.xx.lan",
]
几点要注意:
default = true会禁用 PyPI,并把这个索引放到已配置索引里优先级最低的位置。- requirements.txt 里的
--trusted-host对应的是allow-insecure-host,不是索引配置里的trusted字段------这两个名字太像,很容易配错地方。 - 能配出有效 TLS 证书的话,优先修证书,
allow-insecure-host只应该是长期方案里的例外,不是常态。
解决刚才那个 psutil 问题,有两种思路:
一种是全局关闭 first-index 策略:
toml
[tool.uv]
index-strategy = "unsafe-best-mxxxh"
这样会跨所有索引找最高版本,但风险也最大------只有当你配置的所有索引都完全可信时才应该这么做,否则相当于把供应链安全性拱手让出去。
更推荐的做法是单包 pin 来源,只解决出问题的那一个包:
toml
[tool.uv.sources]
psutil = { index = "private" }
[[tool.uv.index]]
name = "private"
url = "http://xxxx-private-pip.xxxx.lan:9090/simple"
URL 依赖
uv 不允许 URL 依赖仅作为传递依赖存在,必须在 dependencies 中显式声明,并在 [tool.uv.sources] 里配置来源:
toml
[project]
dependencies = ["xxx-engine-alarm"]
[tool.uv.sources]
xxx-engine-alarm = { url = "http://.../xxx_engine_alarm-0.0.3-py3-none-any.whl" }
如果第三方包内部也用 URL 声明了同一个依赖,两边必须完全一致(版本、URL、hash),否则会报类似这样的冲突:
text
error: Requirements contain conflicting URLs for package `xxx-engine-alarm`:
- http://.../xxx_engine_alarm-0.0.3-py3-none-any.whl
- http://.../xxx_engine_alarm-0.0.3-py3-none-any.whl (from a transitive dependency)
看着像是同一个 URL,但差一个字符(比如内部包里写的是旧版本号,或者 hash 对不上)都会触发。遇到这种报错,先去翻第三方包自己声明的依赖来源,跟你项目里写的逐字比对。
过渡期兜底
如果时间紧,可以先用两条命令临时把依赖跑起来:
bash
uv add -r requirements.txt --frozen # 跳过锁文件更新,临时导入依赖声明
uv pip install -r requirements.txt # 完全绕开项目解析
这两条本质上相同------都是"先让代码跑起来,锁文件的事后面再说"。--frozen 不会生成可信的 uv.lock;uv pip install 干脆不走项目解析这条路,执行前记得确认 .venv 已经建好。这两条命令用完之后,一定要回头补上正式的 uv lock。
收尾验证
重新生成锁文件:
bash
uv lock
干净环境复现:
bash
# Windows
Remove-Item -Recurse -Force .venv
# Unix/macOS
rm -rf .venv
uv sync
uv run python -c "import sys; print(sys.version)"
对照 baseline-freeze.txt 抽查关键包版本
在 CI 或另一台干净机器上再跑一次 uv sync,确认不依赖本机缓存。最终再执行一遍工程的测试或者服务,确保没有报错,这才是根本的。
如果要回滚
迁移过程中如果卡住,回滚比继续排查更划算的情况不少见。核心是保留住两样东西:baseline-freeze.txt 和原来的 requirements.txt。
bash
rm -rf .venv
python -m venv .venv
source .venv/bin/activate # Windows 用 .venv\Scripts\activate
pip install -r requirements.txt
回滚不需要动 pyproject.toml 和 uv.lock------留着它们,下次再迁移时可以从上次中断的地方继续。
uv.lock 体积过大
最常见的原因是 requires-python 区间过宽。修复方式是收窄区间后重新生成锁文件:
toml
[project]
requires-python = ">=3.12,<3.13"
bash
uv lock
另一个不那么明显的原因是索引配置太多 :配置的源越多,解析器给每个包做候选校验时要跨的源就越多,锁文件里记录的候选信息也会跟着膨胀。如果收窄 requires-python 之后体积还是没降下来,可以回头看看 [[tool.uv.index]] 是不是配多了,有没有可以合并或去掉的。
注意 --python 3.12 只影响本次命令使用的解释器,不等同于把锁文件限制到 3.12------这是两回事,别用命令行参数当作长期配置的替代品。