开发时,发行时的包依赖管理。 (使用 uv)
有时候依赖不一定会自动安装
ubelt==1.3.3 # just for xdoctest
mypy==1.11.2
soundfile
fonttools>=4.43.0 # not directly required, pinned by Snyk to avoid a vulnerability
numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability
pillow>=10.3.0 # not directly required, pinned by Snyk to avoid a vulnerability
requests>=2.32.2 # not directly required, pinned by Snyk to avoid a vulnerability
urllib3>=2.2.2 # not directly required, pinned by Snyk to avoid a vulnerability
werkzeug>=3.0.6 # not directly required, pinned by Snyk to avoid a vulnerability
zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability
(现在看起来,依赖的补全很重要,当然,从我目前理解来看,如果仅仅只是扫描提供的依赖库的依赖子集并且补全,那可能并不是解决安全隐患的做法,反而它只是在包升级前告诉你哪些依赖库可能不兼容。重要的是,build 时需要的依赖,但是运行时不一定需要,这就可能造成一些隐患,比如安装正常,一运行就废了,当然,这很多时候还得考虑跨系统。复杂性还是有的。)
虽然大部分情况下,只要写下主要的包,依赖包都会被自动安装。
但是那个是对于 user 来说的。如果管理包的人也抱有同样的想法,那么遭殃的很容易是 user 。
大概是这么一回事:
我所认知的 依赖自动安装
源于 numpy
,pandas
,PIL
等等库都在 torch
,torchvision
这样的大型库的依赖中(dependencies), 用户在 pip install torch
的时候会同时安装它的dependecies
或者修正原本就具有的依赖(如 numpy) 的版本。而常见的依赖冲突通常也是 dependencies
之间的版本冲突,而且这种链式嵌套关系不止一层,比如 numpy 可能也依赖了一些。
所以这个自动安装前提是你手动安装的库中至少有一个管理者在 dependencies 中写入了所需依赖,才会自动安装依赖。或者懒一点,你知道 torch
必然是包含 numpy
的,那么这时候如果你并不是直接使用到了 numpy
这个库,只是用 torch, 那么还真不用写。
而最怕的是,管理者给出的 dependency 不完整,并且,缺少的依赖库并没有和给出的 dependencies
有依赖(被写入 dependencies
成员的依赖集中。)并且,在 build 的过程中并没有依赖到这个库,只在运行时依赖这个库,那么这个包就成功发行到了 pypi 上,可以安装,但是运行时必然抛出缺少依赖。
类似这样:
PS D:\tmp\XnneHangLab\packages\yutto-uiya> uv run yutto https://www.bilibili.com/video/BV1vZ4y1M7mQ/
Traceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "D:\tmp\XnneHangLab\.venv\Scripts\yutto.exe\__main__.py", line 4, in <module>
File "D:\tmp\XnneHangLab\.venv\Lib\site-packages\yutto\__main__.py", line 11, in <module>
from yutto.cli.cli import cli, handle_default_subcommand
File "D:\tmp\XnneHangLab\.venv\Lib\site-packages\yutto\cli\cli.py", line 14, in <module>
from yutto.utils.funcutils.functional import map_optional
File "D:\tmp\XnneHangLab\.venv\Lib\site-packages\yutto\utils\funcutils\functional.py", line 5, in <module>
from returns.maybe import Maybe
ModuleNotFoundError: No module named 'returns'
我查看了它的 dependencies
(从 uv.lock):
name = "yutto"
version = "2.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiofiles" },
{ name = "biliass" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "dict2xml" },
{ name = "httpx", extra = ["http2", "socks"] },
{ name = "pydantic" },
{ name = "typing-extensions" },
]
缺少 returns , 并且显然 returns 和其他没有关联。并且如果你项目中所有依赖也和它没有关联,那么只能通过手动添加 returns
库来修复它。
所以,一个比较好的习惯是,写出所有用到的已知的依赖库。这大概是从包使用者到包管理者的一种必要的觉悟 =-= 。
当然,实际上这个包并不存在遗漏,之所以报错,是因为我先前用了 github 最新分支的代码,但是后面切回去的时候,依赖变回去了,但是代码没变,应该算 bug 。删掉 uv.lock ,并且删掉 .venv , 解决。
有时候删除 uv.lock 会解决很多问题。
我发现,从 github 仓库安装的某个包一直停留在同一个哈希,它让我感到抓狂,即使我后面 push 了很多次,但是它依然停留在那里。
uv.lock 里面并没有锁死哈希,它只是锁死分支和 .git ,实际上 uv.lock 和 pyproject.toml 反映出来的是一致的,只是更详细。
但是无论我 uv sync 多少遍,就是无法更新到最新的哈希。
这个时候,删掉 uv.lock
并且重新尝试 uv lock
, uv sync
会很不一样。虽然我也不太懂为啥,否则即使把 .venv
删了哈希也依然不会更新。
也许是 uv.lock
存在, uv sync
就处于一种懒加载的状态?
关于 uv.workspace 依赖不同优先使用谁的问题
- packages
- yutto-uiya
- pyproject.toml
- pyproject.toml
我的 yutto-uiya
的 pyproject.toml
中存在对 yutto
的依赖,从 pypi
安装,而我根目录,工作区的 pyproject.toml
里也包含了 yutto
, 但是是从 github 安装。那么我运行时用的是 github 还是 pypi ?
其中 yutto-uiya 是 uv.workspace 的子成员。
先说现象:
- 我不在根目录的 pyproject.toml 里添加 packages 的所需的依赖,那么最终运行起来会报缺乏依赖。
- 如果我在根目录中添加
yutto==2.0.2
,而 packages 添加yutto==2.0.3
,最终它会报依赖冲突。阻止我安装. - 如果我同时指定
yutto==2.0.3
, 根目录从 github, packages 从 pypi , 那么最终,我用到的 yutto 是从 github 安装的。
说结论:
- uv.workspace 子成员的依赖不会自动安装(或者说不会全部自动安装),它只参与检查。实际用到的都是根工作区的 pyproject.toml 内的依赖。
- uv.workspace 子成员如果不独立工作,可以考虑不写依赖,直接全部写在根工作区。工作区成员可以共用依赖。
- 如果希望 uv.workspace 成员脱离项目和工作区也可以继续工作,那么就需要写各自的依赖。源可以不同,但是版本不能冲突。
目前的话个人建议源(uv.sources)做法:
有独立工作区,或者子成员也需要依赖,那么请 release to pypi (稳定) 或者 从 github 安装(但这个不具有普及性,没那么容易使用镜像)。 根工作区,可以对子成员使用
workspace=True
. 有独立工作区且有时候快速开发,可以考虑用.gitignore+path
比如我的 packages/yutto 可以从pypi 安装,但是我希望能够本地调试修改代码并且同步最新代码的功能。
这个时候,可以这么做:
.gitignore 中添加 yutto
防止 git 工作区交叉污染。(我们也并不作为 submodule)
packages/yutto/
然后在 justfile
中写入:
dev-clean:
rm packages/yutto/dist -rf
rm packages/wexpect-uv/dist -rf
dev:
uv build packages/yutto
uv build packages/wexpect-uv
uv sync --no-cache # 防止 hash-check 阻止同步最新源代码
uv run get_root
uv run streamlit run src/lab/ui.py --server.port 5050
然后 pyproject.toml 写入:
[tool.uv.sources]
yutto = { path = "./packages/yutto/dist/yutto-2.0.3-py3-none-any.whl"}
wexpect-uv = { path = "packages/wexpect-uv/dist/wexpect_uv-0.0.1-py3-none-any.whl" } # 不作为 workspace 成员,因为 workspace 成员 yutto-uiya 也有引用到它,嵌套的 workspace 是不被支持的。
然后每次运行前删一删 uv.lock
.
再运行:
just dev-clean
just dev
即可在各个 packages 均为最新源代码的情况下运行。不会被卡哈希(github 经常卡哈希,给开发带来了不确定性)。