Poetry 经典 [tool.poetry] 项目结构解析:从 legacy 风格 pyproject.toml 到现代迁移实践
【免费下载链接】poetryPython packaging and dependency management made easy项目地址: https://gitcode.com/GitHub_Trending/po/poetry
本篇技术指南围绕当前仓库 tests/fixtures/simple_project_legacy 中的 legacy 风格示例项目展开,逐一拆解其 pyproject.toml 与 README.rst 的每个配置项,并结合 Poetry 的 Factory、EditableBuilder 等源码与测试用例,说明这种"经典"项目结构在可编辑安装、构建元数据生成、依赖解析中的实际行为,以及迁移到现代 PEP 621 风格的注意事项。读完你将能读懂任意一个 legacy 风格 Poetry 项目的完整配置,并掌握其升级路径。
一、什么是 legacy 风格的 Poetry 项目
Poetry 的项目配置经历了两个主要阶段:
- 现代风格(PEP 621):元数据写在
[project]表中,[tool.poetry]只保留依赖组、源等 Poetry 专有信息; - legacy 风格:所有项目元数据(名称、版本、描述、作者、许可证、readme、关键词、分类器、脚本入口)都写在
[tool.poetry]表中。
当前仓库在tests/fixtures/下同时维护了两种示例:simple_project 与 simple_project_legacy,且测试代码将二者并列使用(例如 test_editable_builder.py 中的@pytest.mark.parametrize("project", ("simple_project", "simple_project_legacy"))),说明 Poetry 的构建与安装链路对两种风格一视同仁,legacy 风格至今仍被完整支持。
注意:本仓库的
tests/fixtures/simple_project_legacy是测试夹具(fixture),并非可直接发布的真实项目,其源码包 simple_project/init.py 为空文件。本文以它为"解剖样本",讲解其中每种配置的语义与底层行为。
二、完整解剖 legacy pyproject.toml
该夹具的 pyproject.toml 是理解 legacy 风格的最小完整样例,全文如下:
[tool.poetry] name = "simple-project" version = "1.2.3" description = "Some description." authors = [ "Sébastien Eustace <sebastien@eustace.io>" ] license = "MIT" readme = ["README.rst"] homepage = "https://python-poetry.org" repository = "https://github.com/python-poetry/poetry" documentation = "https://python-poetry.org/docs" keywords = ["packaging", "dependency", "poetry"] classifiers = [ "Topic :: Software Development :: Build Tools", "Topic :: Software Development :: Libraries :: Python Modules" ] # Requirements [tool.poetry.dependencies] python = "~2.7 || ^3.4" [tool.poetry.scripts] foo = "foo:bar" baz = "bar:baz.boom.bim" fox = "fuz.foo:bar.baz" [build-system] requires = ["poetry-core>=1.1.0a7"] build-backend = "poetry.core.masonry.api"下面逐项解释各配置的语义。
1. 项目基础元数据
| 配置项 | 示例值 | 作用 |
|---|---|---|
name | simple-project | 项目/发行包名称,构建时会被规范化(normalize)为simple_project用于 wheel 文件名(见下文test_prepare_directory) |
version | 1.2.3 | 版本号,可采用约束语义(本夹具为固定版本) |
description | Some description. | 一句话描述,最终写入构建产物的METADATA的Summary字段 |
authors | 邮箱+姓名格式的字符串列表 | 作者信息,写入Author/Author-email字段 |
license | MIT | SPDX 表达式或自定义字符串;legacy 风格下会额外自动生成License :: OSI Approved :: MIT License分类器 |
在 test_editable_builder.py 中,测试精确断言了由这些字段生成的METADATA,例如:
Metadata-Version: ... Name: simple-project Version: 1.2.3 Summary: Some description. License: MIT Author: Sébastien Eustace Author-email: sebastien@eustace.io Classifier: License :: OSI Approved :: MIT License Project-URL: Documentation, https://python-poetry.org/docs Project-URL: Homepage, https://python-poetry.org Project-URL: Repository, https://github.com/python-poetry/poetry Description-Content-Type: text/x-rst测试代码 L208-L211 还揭示了一个 legacy 与现代的关键差异:legacy 项目生成的是License: MIT+License :: OSI Approved :: MIT License分类器,而现代simple_project生成的是License-Expression: MIT且不附加 license 分类器。这是 PEP 639 之后许可证表达式的规范化行为差异。
2. 多文件 readme
readme = ["README.rst"]- 取值可以是单个字符串,也可以是字符串列表(多文件 readme,适用于同时有 README 与 CHANGELOG 的场景);
- 列表形式下,Poetry 会按顺序把它们都嵌入构建产物的
METADATA(多段描述拼接); - 本夹具的 README.rst 内容为 RST 标题:
My Package ==========该内容会以Description-Content-Type: text/x-rst的形式写入METADATA(见上节测试断言)。RST 文件头部====为 reStructuredText 文档标题语法,PyPI 与 ReadTheDocs 均能正确渲染,这也是 legacy 项目选择.rst而不是.md的常见原因。
3. 项目链接
homepage = "https://python-poetry.org" repository = "https://github.com/python-poetry/poetry" documentation = "https://python-poetry.org/docs"这三项在构建时会被合并为METADATA中的三条Project-URL记录(Homepage、Repository、Documentation),是 PyPI 页面上展示项目入口的核心来源。注意以上 URL 只是夹具的示例数据,并不代表本仓库的实际主页。
4. keywords 与 classifiers
keywords = ["packaging", "dependency", "poetry"] classifiers = [ "Topic :: Software Development :: Build Tools", "Topic :: Software Development :: Libraries :: Python Modules" ]keywords逗号拼接后写入METADATA的Keywords字段;classifiers是 Trove 分类器列表,legacy 风格下 Poetry 会自动追加基于python约束推导出的Programming Language :: Python :: x.y分类器(见 test_editable_builder.py 的expected_python_classifiers帮助函数)。因此 legacy 项目通常只需写"主题类"分类器,Python 版本分类器由构建过程自动补全。
5. 依赖约束:python = "~2.7 || ^3.4"
[tool.poetry.dependencies] python = "~2.7 || ^3.4"这是夹具中最具"时代感"的一行:Python 版本约束采用兼容版本范围(caret)与近似版本(tilde)的组合:
^3.4:允许>=3.4,<4.0范围内不改变最左侧非零段的所有升级;~2.7:允许>=2.7,<3.0,即只允许补丁级升级;||:并集,表示项目同时支持 Python 2.7 与 3.4+ 的早期时代约束。
由该约束,Poetry 会在METADATA中生成Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*(见 test_editable_builder.py),自动排除约束范围内已被判死刑的 3.0~3.3 版本。在实际的现代项目中,应改为类似python = ">=3.9,<4.0"的写法。
6. 控制台脚本入口
[tool.poetry.scripts] foo = "foo:bar" baz = "bar:baz.boom.bim" fox = "fuz.foo:bar.baz"每个条目是命令名 = "模块:函数路径"的形式,Poetry 安装时会在虚拟环境的 bin 目录生成对应可执行脚本。测试 L234-L265 精确断言了生成脚本的内容,例如foo会生成:
#!<venv-python> import sys from foo import bar if __name__ == '__main__': sys.exit(bar())而fox = "fuz.foo:bar.baz"会被解析为"导入fuz.foo,调用bar.baz",即路径中最后一个点号之前是模块,之后是逐层调用的函数/属性链:
from fuz.foo import bar if __name__ == '__main__': sys.exit(bar.baz())测试还覆盖了非法脚本的报错行为(L443-L466):foo = "bar.bin.foo"(缺少冒号)会抛出Bad script并给出Hint: 'foo = "bar.bin.foo:main"'的修复建议;foo = "foo::bar"(多个冒号)会提示Too many冒号。
7. build-system:legacy 项目的"启动器"
[build-system] requires = ["poetry-core>=1.1.0a7"] build-backend = "poetry.core.masonry.api"build-backend声明构建后端为poetry.core.masonry.api,这是poetry-core提供的 PEP 517 构建入口,负责 wheel 与 sdist 的构建;requires指定构建时所需的最小poetry-core版本(夹具使用>=1.1.0a7,实际项目建议使用更高稳定版本);- 注意本夹具没有
[tool.poetry.packages],即采用"包目录与项目同名、位于项目根目录"的默认布局,Poetry 会自动发现simple_project/目录作为待打包源码包。
三、legacy 项目在构建与安装链路中的真实行为
1. Factory 如何读取 legacy 配置
poetry.factory.Factory是解析 pyproject.toml 的核心入口。除了读取配置,它还提供create_legacy_pyproject_from_package类方法(src/poetry/factory.py),可将一个Package对象反向序列化为 legacy 风格的[tool.poetry]表——其中name、version、description、authors、license、classifiers、homepage/repository/documentation、keywords、readme(列表形式)等字段,与本夹具的字段一一对应。这说明本夹具的每一项配置都对应 Poetry 内部Package数据模型的真实属性,而非测试专用摆设。
对应的测试 tests/test_factory.py#L155-L160 把simple_project_legacy与project_with_extras并列参数化,验证create_legacy_pyproject_from_package生成的[tool.poetry]与原始配置一致,进一步印证了字段映射的完整性。
2. 可编辑安装(editable install)的完整产物
test_editable_builder.py 的test_builder_installs_proper_files_for_standard_packages把simple_project_legacy安装进一个临时虚拟环境,并断言了可编辑安装应生成的全部产物:
| 产物 | 说明 |
|---|---|
simple_project.pth | 把项目根目录加入sys.path,实现"改代码即生效"的可编辑效果 |
simple_project-1.2.3.dist-info/ | 发行元数据目录 |
entry_points.txt | 三个脚本入口baz=bar:baz.boom.bim、foo=foo:bar、fox=fuz.foo:bar.baz |
METADATA | 含上一节列出的全部字段与 readme 内容 |
licenses/LICENSE | license 文件(夹具目录下的 LICENSE 文件被自动纳入) |
INSTALLER | 内容为poetry,标记安装方 |
direct_url.json | {"dir_info": {"editable": true}, "url": "<项目目录的 file:// URI>"} |
RECORD | 完整的安装文件清单(每行 3 列) |
bin 下的foo/baz/fox | 控制台脚本 |
这些断言说明:legacy 项目与现代项目在安装产物层面完全一致,[tool.poetry.scripts]的入口会被翻译成[console_scripts]写入entry_points.txt,readme会进入METADATA,license会自动收集 LICENSE 文件。
3. 目录依赖的准备(Chef.prepare)
在 tests/installation/test_chef.py 中,simple_project_legacy被用作"目录依赖"的构建原料:
archive = fixture_dir("simple_project_legacy").resolve() wheel = chef.prepare(archive) assert wheel.name == "simple_project-1.2.3-py2.py3-none-any.whl"Chef.prepare会把这类项目(比如foo = { path = "../simple_project_legacy" }形式的路径依赖)在构建隔离环境中现场构建成 wheel,wheel 文件名simple_project-1.2.3-py2.py3-none-any.whl恰好体现了本夹具两个关键配置的衍生结果:
- 包名
simple-project被规范化(normalize)为simple_project; python = "~2.7 || ^3.4"的兼容范围使得 wheel 的py2.py3通用标签生效(同时兼容 Python 2 与 3)。
这解释了 legacy 风格下"包名带连字符、wheel 名带下划线"的常见现象。
四、legacy 项目与现代(PEP 621)的迁移对照
poetry check命令会在 legacy 配置上打印一组弃用警告,tests/console/commands/test_check.py#L87-L140 完整列出了这些警告,实际构成了官方的迁移清单:
| legacy 写法 | 现代写法 | 警告要点 |
|---|---|---|
[tool.poetry] name | [project] name | [tool.poetry.name]已弃用 |
[tool.poetry] version | [project] version或[project.dynamic]含version | 静态值用[project.version],动态值(如poetry build --local-version或插件设置)需把version加入[project.dynamic] |
[tool.poetry] description | [project] description | 已弃用 |
[tool.poetry] readme | [project] readme;多文件时定义在[tool.poetry]并把readme加入[project.dynamic] | 静态用[project.readme] |
[tool.poetry] license | [project] license | 已弃用 |
[tool.poetry] authors | [project] authors | 已弃用 |
[tool.poetry] keywords | [project] keywords | 已弃用 |
[tool.poetry] classifiers | [project] classifiers;若希望保留 Poetry 的自动补全,则定义在[tool.poetry]并把classifiers加入[project.dynamic] | 迁移到[project]后需手工维护全部分类器(自动补全会关闭),含 license 分类器与 Python 版本分类器 |
[tool.poetry] homepage/repository/documentation | [project] urls | 已弃用 |
[tool.poetry.scripts] | [project.scripts] | 已弃用([tool.poetry.scripts]仅保留给file类型脚本) |
从源码角度,这些警告的逻辑集中在 src/poetry/console/commands/check.py,测试 tests/console/commands/test_check.py 是对其行为的完整验证。即便不做迁移,legacy 风格依然可用(前文所有构建/安装测试均通过),迁移主要是为了消除弃用警告、获得 PEP 517/621 生态的更好互操作性,以及避免 "classifiers 自动补全被关闭" 这类隐性行为变化。
五、把 legacy 夹具改造成自己的项目
如果要基于本夹具结构开始一个新项目(或把旧项目移植到当前 Poetry),可按以下步骤操作:
- 复制目录骨架:将
tests/fixtures/simple_project_legacy/中的pyproject.toml、README.rst、LICENSE、simple_project/复制到项目根目录,simple_project/内放入真实的包代码(可删除或替换空的__init__.py); - 改写元数据:按第二节对照表替换
name、version、authors、license、description、keywords、classifiers; - 修正依赖:将
python = "~2.7 || ^3.4"改为实际支持的 Python 版本(如>=3.9,<4.0),并在[tool.poetry.dependencies]下添加真实依赖; - 配置脚本:把
[tool.poetry.scripts]中的示例入口改为命令名 = "你的模块:入口函数"; - 初始化并安装:
poetry install该命令会依据[build-system]中的poetry-core构建项目,并生成与 test_editable_builder.py 断言一致的可编辑安装产物(.pth、dist-info、控制台脚本)。
如需校验配置正确性,可运行poetry check——在 legacy 配置上它会输出第三节中的弃用警告;配置无致命错误时命令返回成功状态码(参见 tests/console/commands/test_check.py#L87-L140 对expected_status的断言)。
六、小结
tests/fixtures/simple_project_legacy虽然只是测试夹具,却浓缩了 Poetry legacy 项目结构的全部要点:
- 元数据集中式管理:
name/version/description/authors/license/readme/keywords/classifiers/链接全部收敛在[tool.poetry]; - 脚本入口灵活:
[tool.poetry.scripts]支持模块:函数、模块:对象.属性等多种写法,并带有严格的格式校验; - 构建闭环成熟:通过
[build-system]声明poetry-core后端,legacy 项目与现代项目在 wheel 构建、可编辑安装、目录依赖解析上行为完全一致; - 迁移路径清晰:
poetry check的警告列表即官方迁移清单,逐项对照即可升级到 PEP 621 的[project]风格。
无论你是要阅读老项目的 Poetry 配置,还是准备把旧项目迁移到现代标准,本文的逐项剖析与源码佐证都能帮助你快速定位每一项配置的作用与去向。
【免费下载链接】poetryPython packaging and dependency management made easy项目地址: https://gitcode.com/GitHub_Trending/po/poetry
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考