Python能做大项目(6)Poetry -- 项目管理的诗和远方之一

发布时间:2023年12月25日

[Poetry] 是一个依赖管理和打包工具。Poetry 的作者解释开发 Poetry 的初衷时说:

在这里插入图片描述

通过前面的案例,我们已经提出了一些问题。但不止于此。

当您将依赖加入到 requirements.txt 时,没有人帮你确定它是否与既存的依赖能够和平共处,这个过程要比我们想象的复杂许多,不仅仅是直接依赖,还需要考虑彼此的传递依赖是否也能彼此兼容;所以一般的做法是,先将它们加进来,完成开发和测试,在打包之前,运行pip freeze > requirements.txt来锁定依赖库的版本。但我们也在前面的案例中提到,这种方法可能会将不必要的开发依赖打入到发行版中;此外,它也会过度锁定版本,从而使得一些活跃的第三方库失去自动更新热修复和安全更新的机会。

项目的版本管理也是一个问题。在老旧的 Python 项目中,一般我们使用 bumpversion 来管理版本,它需要使用三个文件。在我的日常使用时,它常常会出现各种问题,最常见的是单双引号导致把__version__ = 0.1当成一个版本号,而不是0.1。这样打出来的包名也会奇怪地多一个无意义的 version 字样。单双引号则是因为你的 format 工具对字符串常量应该使用什么样的引号规则有自己的意见。

项目进行打包和发布需要准备太多的文件,正如 Poetry 的开发者所说,要确保这些文件的内容完全正确,对一个有经验的开发者来说,也不是轻而易举的事。

Poetry 解决了所有这些问题(除了案例中的第一个,该问题要通过 tox 和 CI 来解决)。它提供了版本管理、依赖解析、构建和发布的一站式服务,并将所有的配置,集中到一个文件中,即 pyproject.toml。此外,Poetry 还提供了一个简单的工程创建向导。不过这个向导的功能仍然过于简单,我们的推荐则是使用上一章介绍的 python project wizard。

在这里插入图片描述

现在,让我们看一眼 sample 项目中的 pyproject.toml 文件:

[tool]
[tool.poetry]
name = "sample"
version = "0.1.0"
homepage = "https://github.com/zillionare/sample"
description = "Skeleton project created by Python Project Wizard (ppw)."
authors = ["aaron yang <aaron_yang@jieyu.ai>"]
readme = "README.md"
license =  "MIT"
classifiers=[
    'Development Status :: 2 - Pre-Alpha',
    'Intended Audience :: Developers',
    'License :: OSI Approved :: MIT License',
    'Natural Language :: English',
    'Programming Language :: Python :: 3',
    'Programming Language :: Python :: 3.7',
    'Programming Language :: Python :: 3.8',
    'Programming Language :: Python :: 3.9',
    'Programming Language :: Python :: 3.10',
]
packages = [
    { include = "sample" },
    { include = "tests", format = "sdist" },
]

[tool.poetry.dependencies]
python = ">=3.7.1,<4.0"
fire = "0.4.0"

black  = { version = "^22.3.0", optional = true}
isort  = { version = "5.10.1", optional = true}
flake8  = { version = "4.0.1", optional = true}
flake8-docstrings = { version = "^1.6.0", optional = true }
pytest  = { version = "^7.0.1", optional = true}
pytest-cov  = { version = "^3.0.0", optional = true}
tox  = { version = "^3.24.5", optional = true}
virtualenv  = { version = "^20.13.1", optional = true}
pip  = { version = "^22.0.3", optional = true}
mkdocs  = { version = "^1.2.3", optional = true}
mkdocs-include-markdown-plugin  = { version = "^3.2.3", optional = true}
mkdocs-material  = { version = "^8.1.11", optional = true}
mkdocstrings  = { version = "^0.18.0", optional = true}
mkdocs-material-extensions  = { version = "^1.0.3", optional = true}
twine  = { version = "^3.8.0", optional = true}
mkdocs-autorefs = {version = "^0.3.1", optional = true}
pre-commit = {version = "^2.17.0", optional = true}
toml = {version = "^0.10.2", optional = true}
livereload = {version = "^2.6.3", optional = true}
pyreadline = {version = "^2.1", optional = true}
mike = { version="^1.1.2", optional=true}

[tool.poetry.extras]
test = [
    "pytest",
    "black",
    "isort",
    "flake8",
    "flake8-docstrings",
    "pytest-cov"
    ]

dev = ["tox", "pre-commit", "virtualenv", "pip", "twine", "toml"]

doc = [
    "mkdocs",
    "mkdocs-include-markdown-plugin",
    "mkdocs-material",
    "mkdocstrings",
    "mkdocs-material-extension",
    "mkdocs-autorefs",
    "mike"
    ]

[tool.poetry.scripts]
sample = 'sample.cli:main'

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.black]
line-length = 88
include = '\.pyi?$'
exclude = '''
/(
    \.eggs
  | \.git
  | \.hg
  | \.mypy_cache
  | \.tox
  | \.venv
  | _build
  | buck-out
  | build
  | dist
)/
'''
[tool.isort]
profile = "black"

我们简单地解读一下这个文件:
在 [tool.poetry] 那一节,定义了包的名字(这里是 sample)、版本号(这里是 0.1.0)和其它的一些字段,比如 classifiers,这是打包和发布时需要的。如果您熟悉 python setup tools,那么对这些字段将不会陌生。packages 字段指明了打包时需要包含的文件。在示例中,我们要求在以.whl 格式发布的包中,将 sample 目录下的所有文件打包发布;而以 sdist 格式(即.tar.gz) 发布的包中,还要包含 tests 目录下的文件。

接下来是 [tool.poetry.dependencies] 一节,这是我们声明项目依赖的地方。首先是项目要求的 python 版本声明。这里我们要求必须在 3.7.1 以上,4.0 以下的 python 环境中运行。因此,python 3.7.1,3.8, 3.9, 3.10 都是恰当的 python 版本,但 4.0 则不允许。

接下来就是工程中需要用到的其它第三方依赖,有运行时的(即当最终用户使用我们的程序时,必须安装的那些第三方依赖),也有开发时的(即只在开发和测试过程中使用到的,比如文档工具类 mkdocs,测试类 tox, pytest 等)。

我们对运行时和开发时需要的依赖进行了分组。对开发时需要的依赖,我们分成 dev, test 和 doc 三组,通过 [tool.poetry.extras] 中进行分组声明。对于归入到 dev, test 和 doc 分组中的依赖,我们在 [tool.poetry.dependencies] 中,将其声明为 optional 的,这样在安装最终分发包时,这些声明为 optional 的第三方依赖将不会安装到用户环境中。

再接下来,[tool.poetry.scripts] 声明了一个 console script 入口。Console script 是一种特殊的 Python 脚本,它使得您可以象调用普通的 shell 命令一样来调用这个脚本。

[tool.poetry.scripts]
sample = 'sample.cli:main'

在这里插入图片描述

当 sample 包被安装后,就往安装环境里注入了一个名为sample的shell 命令。它可以接受各种参数,最终将交给 sample\cli.py 中的 main 函数来执行。

接下来就是关于如何构建的相关指示,在 [build-system] 中。如果你的程序中只包含纯粹的 Python 代码,那么这部分可不做任何修改。如果你的程序包含了一些原生的代码(比如 c 的),那么就需要自己定义构建脚本。

在示例代码中,还有 [tool.black] 和 [tool.isort] 两个小节,分别是 black(代码格式化工具)和 isort(将导入进行排序的工具)的配置文件。它们是对 pyproject.toml 的扩展,并不是 poetry 所要求的。

版本管理

poetry 为我们的 package 提供了基于语义 (semantic version) 的版本管理功能。它通过poetry version这个命令,让我们查看 package 的版本,并且实现版本号的升级。

假设您已经使用 [python project wizard] 生成了一个工程框架,那么应该可以在根目录下找到 pyproject.toml 文件,其中有一项:

version = 0.1

如果您现在运行poetry version这个命令,就会显示0.1这个版本号。

Poetry 使用基于语义的版本 (semantic version) 表示法。

在 Poetry 中,当我们需要修改版本号时,并不是直接指定新的版本号,而是通过poetry version semver来修改版本。semver可以是patch, minor, major, prepatch, preminor, premajorprerelease中的一个。这些关键字定义在规范 PEP 440 中。

semver与您当前的版本号相结合,通过运算,就得出了新的版本号:

rulebeforeafter
major1.3.02.0.0
minor2.1.42.2.0
patch4.1.14.1.2
premajor1.0.22.0.0-alpha.0
preminor1.0.21.1.0-alpha.0
prepatch1.0.21.0.3-alpha.0
prerelease1.0.21.0.3-alpha.0
prerelease1.0.3-alpha.01.0.3-alpha.1
prerelease1.0.3-beta.01.0.3-beta.1

可以看出,poetry 对版本号的管理是完全符合 semantic version 的要求的。当你完成了一个小的修订(比如修复了一个 bug,或者增强了性能,或者修复了安全漏洞),此时只应该递增 package 的修订号,即 x.y.z 中的’z’,这时我们就应该使用命令:

$ poetry version patch

如果之前的版本是 0.1.0,那么运行上述命令后,版本号将变更为 0.1.1。
如果我们的 package 新增加了一些功能,而之前提供的功能(API)都还能不加修改,继续使用,那么我们应该递增次版本号,即 x.y.z 中的’y’。这时我们应该使用命令:

$ poetry version minor

如果之前的版本是 0.1.1,那么运行上述命令后,版本号将变更为 0.2.0

如果我们的 package 进行了大幅的修改,并且之前提供的功能(API)的签名已经变掉,从而使得调用者必须修改他们的程序才能继续使用这些 API,又或者新的版本不再能兼容老版本的数据格式,用户必须对数据进行额外的迁移,那么,我们就认为这是一次破坏性的更新,必须升级主版本号:

$ poetry version major

如果之前的版本号是 0.3.1, 那么运行上述命令之后,版本号将变更为 1.0.0;如果之前的版本号是 1.2.1,那么运行上述命令之后,版本号将变更为 2.0.0。

除此之外,poetry 还提供了预发布版本号的支持。比如,上一个发布的版本是 0.1.0,那么我们在正式发布 0.1.1 这个修订之前,可以使用 0.1.1.a0 这个版本号:

$ poetry version prerelease
Bumping version from 0.1.0 to 0.1.1a0

如果需要再出一个 alpha 版本,则可以再次运行上述命令:

$ poetry version prerelease
Bumping version from 0.1.1a0 to 0.1.1a1

如果 alpha 版本已经完成,可以正式发布,运行下面的命令:

$ poetry version patch
Bumping version from 0.1.1a1 to 0.1.1

poetry 暂时还没有提供从 alpha 转到 beta 版本系列的命令。如果有此需要,您需要手工编辑 pyproject.toml 文件。

除了 poetry version prerelease 之外,我们还注意到上面列出的 premajor, preminor 和 prepatch 选项。它们的作用也是将版本号修改为 alpha 版本系列,但无论你运行多少次,它们并不会象 prerelease 选项一样,递增 alpha 版本号。所以在实际的 alpha 版本管理中,似乎只使用poetry version prerelease就可以了。


本文来源于《Python能做大项目》(暂定名),将由机械工业出版社出版。全书已经在大富翁量化官网上首发,欢迎提前阅读。


【本系列其它文章】

Python能做大项目(1) - 为什么要学Python之一
Python能做大项目(2) - 开发环境构建
Python能做大项目(3) - 依赖地狱与Conda虚拟环境
Python能做大项目(4) - 项目布局与生成向导
Python能做大项目(5) - 基于语义的版本管理
Python能做大项目(6) - Poetry: 项目管理的诗和远方之一

文章来源:https://blog.csdn.net/hbaaron/article/details/135185939
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。