第12回 Poetry依赖解析原理

在上一节,我们简单地介绍了如何使用 poetry 来向我们的项目中增加依赖。我们强调了依赖解析的困难,但并没有解释 poetry 是如何进行依赖解析的,它会遇到哪些困难,可能遭遇什么样的失败,以及应该如何排错。对于初学者来说,这往往是配置 poetry 项目时最困难和最耗时间的部分。

现在,我们往项目中增加一个新的依赖,通常我们使用poetry add xxx来往项目中增加依赖。为了一窥 poetry 依赖解析的究竟,这次我们加上详细信息输出:

$ poetry add gino -vvv

输出会很长很长。我们摘要读一下跟 gino 相关的一些解析过程:

首先,poetry 注意到 sample 0.1.0 依赖到 gino(>=1.0.1, < 2.0.0),以及其它一些依赖,生成了第一步的解析结果:

   1: fact: sample is 0.1.0
1: derived: sample
1: selecting sample (0.1.0)
1: derived: gino (>=1.0.1,<2.0.0)
1: derived: mike (>=1.1.2,<2.0.0)
...

接下来,下载 gino,解析出下面的依赖:

1 packages found for gino >=1.0.1,<2.0.0
1: fact: gino (1.0.1) depends on SQLAlchemy (>=1.2.16,<1.4)
1: fact: gino (1.0.1) depends on asyncpg (>=0.18,<1.0)
1: selecting gino (1.0.1)
1: derived: asyncpg (>=0.18,<1.0)
1: derived: SQLAlchemy (>=1.2.16,<1.4)

再接下来,它找到 sqlalchemy 的 29 个版本:

Source (ali): 14 packages found for asyncpg >=0.18,<1.0
Source (ali): 29 packages found for sqlalchemy >=1.2.16,<1.4

接下来比较幸运,当 poetry 查找 asyncpg 和 sqlalchemy 的传递依赖时,没有找到它们有更多的传递依赖,解析结束,这样,poetry 就顺利地选择了 29 个版本中,最新的一个,即 SQLAlchemy-1.3.24。这个版本又有 linux, windows 和 mac 等好几个包,poetry 最终选择跟当前环境中操作系统版本一致、python 版本一致的那个进行安装。

现在让我们看一看 poetry 最终解析出来的依赖树:


$ poetry show -t
black 22.12.0 The uncompromising code formatter.
├── click >=8.0.0
│ ├── colorama *
│ └── importlib-metadata *
│ ├── typing-extensions >=3.6.4
│ └── zipp >=0.5
├── mypy-extensions >=0.4.3
├── pathspec >=0.9.0
├── platformdirs >=2
│ └── typing-extensions >=4.4
├── tomli >=1.1.0
├── typed-ast >=1.4.2
└── typing-extensions >=3.10.0.0
gino 1.0.1 GINO Is Not ORM - a Python asyncio ORM on SQLAlchemy core.
├── asyncpg >=0.18,<1.0
└── sqlalchemy >=1.2.16,<1.4
mkdocs 1.2.4 Project documentation with Markdown.
├── click >=3.3
│ ├── colorama *
│ └── importlib-metadata *
│ ├── typing-extensions >=3.6.4
│ └── zipp >=0.5
├── ghp-import >=1.0
│ └── python-dateutil >=2.8.1
│ └── six >=1.5
├── importlib-metadata >=3.10
│ ├── typing-extensions >=3.6.4
│ └── zipp >=0.5
├── jinja2 >=2.10.1
│ └── markupsafe >=2.0
├── markdown >=3.2.1
│ └── importlib-metadata *
│ ├── typing-extensions >=3.6.4
│ └── zipp >=0.5
├── mergedeep >=1.3.4
├── packaging >=20.5
├── pyyaml >=3.10
├── pyyaml-env-tag >=0.1
│ └── pyyaml *
└── watchdog >=2.0

这个依赖树很长,这里只截取了一小部分,但大致上可以帮助我们了解 poetry 的工作原理。我们可以看到blackmkdocs都依赖了click,但black要求更新到 8.0 以上,而mkdocs则认为只要是 3.3 以上都可以。两者版本要求差距如此之大,也不免让人担心,8.0 的click与 3.3 的click还会是同一个click吗?

最终,关于 gino 和 sqlalchemy,poetry 安装的分别是 1.0.1 和 1.3.24,但是,上述解析树表明,如果存在 sqlalchemy 的 1.3.25 版本,它是可以自动升级的。我们许的愿,poetry 帮助实现了。

生成这棵依赖树可能要比你想像的困难得多。首先,PyPI 目前还没有给出它上面的某一个 package 的依赖树,这意味着 poetry 要知道black依赖哪些库,它必须先把black下载下来,打开它并解析才能知道。然后它从black中发现更多的依赖,这往往就需要它把这些依赖也下载下来,依次递归下去。

alt

更为糟糕的是,在这个过程中,某个库的好几个版本可能都需要依次下载下来 -- 因为它们的传递依赖不能兼容。我记得在某次解析中,poetry 把 numpy 的版本从 1.2.x 一直下载到了 0.1!最终还是失败了。

所以,如果你在添加某个依赖时,发现 poetry 耗时过长,不要慌张,很多人都有与你一样的经历。这种情况主要是 poetry 无法快速锁定某个 package 的正确版本,不得不向后一个个版本搜索下载所致。我们能做的,就是加快 poetry 下载的速度。

poetry 正常情况下,是从 pypi.org 上下载 package。如果遇到解析速度问题,我们可以临时添加一个源:

poetry source add ali https://mirrors.aliyun.com/pypi/simple --default

再次运行poetry add,这次你会发现解析速度快了很多。

alt

现在我们来移除 gino:

$ poetry remove gino
Updating dependencies
Resolving dependencies... (1.2s)

Writing lock file

Package operations: 0 installs, 0 updates, 3 removals

• Removing asyncpg (0.27.0)
• Removing gino (1.0.1)
• Removing sqlalchemy (1.3.24)

可以看出,不仅是 gino 本身被卸载,它的传递依赖 -- asyncpg 和 sqlalchemy 也被移除掉了。这是 pip 做不到的。

1. 虚拟运行时

Poetry 自己管理着虚拟运行时环境。当你执行poetry install命令时,Poetry 就会安装一个基于 venv 的虚拟环境,然后把项目依赖都安装到这个虚拟的运行环境中去。此后,当你通过 poetry 来执行其它命令时,比如poetry pytest,也会在这个虚拟环境中执行。反之,如果你直接执行pytest,则会报告一些模块无法导入,因为你的工程依赖并没有安装在当前的环境下。

我们推荐在开发过程中,使用 conda 来创建集中式管理的运行时。在调试 Python 程序时,都要事先给 IDE 指定解析器,这里使用集中式管理的运行时,可能更方便一点。Poetry 也允许这种做法。当 Poetry 检测到当前是运行在虚拟运行时环境下时,它是不会创建新的虚拟环境的。

但是 Poetry 的创建虚拟环境的功能也是有用的,主要是在测试时,通过 virtualenv/venv 创建虚拟环境速度非常快。

2. 构建发行包

2.1 Python 构建标准和工具的变化

在 poetry 1.0 发布之前,打包一个 python 项目,需要准备 MANIFEST.in, setup.cfg, setup.py,makefile 等文件。这是 PyPA(python packaging authority) 的要求,只有遵循这些要求打出来的包,才可以上传到 pypi.org,从而向全世界发布。

但是这一套系统也有不少问题,比如缺少构建时依赖声明,自动配置,版本管理。因此,PEP 517 被提出,然后基于 PEP 517, PEP 518 等一系列新的标准,Sébastien Eustace 开发了 poetry。

2.2 基于 Poetry 进行发行包的构建

我们通过运行poetry build来打包,打包的文件约定俗成地放在 dist 目录下。

poetry 支持向 pypi 进行发布,其命令是poetry publish。不过,在运行该命令之前,我们需要对 poetry 进行一些配置,主要是 repo 和 token。

# PUBLISH TO TEST PYPI
$ poetry config repositories.testpypi https://test.pypi.org/legacy/
$ poetry config testpypi-token.pypi my-token
$ poetry publish -r testpypi

#
PUBLISH TO PYPI
$ poetry config pypi-token.pypi my-token
$ poetry publish

上面的命令分别对发布到 test pypi 和 pypi 进行了演示。默认地 Poetry 支持 PyPI 发布,所以有些参数就不需要提供了。当然,一般情况下,我们都不应该直接运行poetry publish命令来发布版本。版本的发布,都应该通过 CI 机制来进行。这样的好处时,可以保证每次发布,都经过了完整的测试,并且,构建环境是始终一致的,不会出现因构建环境不一致,导致打出来的包有问题的情况。

3 其它重要的 Poetry 命令

我们已经介绍了 poetry add, poetry remove, poetry show, poetry build, poetry publish, poetry version 等命令。还有一些命令也值得介绍。

2.5.1. poetry lock

该命令将进行依赖解析,锁定所有的依赖到最新的兼容版本,并将结果写入到 poetry.lock 文件中。通常,运行 poetry add 时也会生成新的锁定文件。

在对代码执行测试、CI 或者发布之前,务必要确保 poetry.lock 存在,并且这个文件也应该提交到代码仓库中,这样所有的测试,CI 服务器,你的同侪开发者构建的环境才会是完全一致的。

3.1 poetry export

$ poetry export -f requirements.txt --output requirements.txt

3.2 poetry config

我们可以通过 poetry config --list 来查看当前配置项:

cache-dir = "/path/to/cache/directory"
virtualenvs.create = true
virtualenvs.in-project = null
virtualenvs.options.always-copy = true
virtualenvs.options.no-pip = false
virtualenvs.options.no-setuptools = false
virtualenvs.options.system-site-packages = false
virtualenvs.path = "{cache-dir}/virtualenvs" # /path/to/cache/directory/virtualenvs
virtualenvs.prefer-active-python = false
virtualenvs.prompt = "{project_name}-py{python_version}"

这里面比较重要的有配置 pypi-token,以便发布你的项目。

本文由 mdnice 多平台发布

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

量化风云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值