PEP 633 – 使用展开的 TOML 表格在 pyproject.toml 中指定依赖项
- 作者:
- Laurie Opperman <laurie_opperman at hotmail.com>,Arun Babu Neelicattu <arun.neelicattu at gmail.com>
- 赞助商:
- Brett Cannon <brett at python.org>
- 讨论列表:
- Discourse 帖子
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建:
- 2020-09-02
- 发布历史:
- 2020-09-02
- 决议:
- Discourse 消息
拒绝通知
由于其受欢迎程度、与现有 PEP 508 字符串用法的兼容性以及与现有打包工具套件的兼容性,此 PEP 已被拒绝,转而采用 PEP 631。
摘要
此 PEP 指定了如何在 pyproject.toml
文件中编写项目的依赖项,以便与打包相关的工具使用 PEP 621 中定义的字段进行使用,作为 PEP 631 中定义的基于 PEP 508 的方法的替代方案。
动机
与使用 PEP 508 字符串相比,使用 TOML 表格和其他数据类型来表示需求有诸多好处
- 通过 TOML 语法轻松进行初始验证。
- 使用模式(例如 JSON 模式)轻松进行二次验证。
- 用户可以推测给定功能的键,而不是死记硬背语法。
- 许多其他流行语言的用户可能已经熟悉 TOML 语法。
- TOML 直接表示与 JSON 中相同的数据结构,因此也是 Python 字面量的子集,因此用户可以理解层次结构和值的类型
基本原理
大部分内容取自 PEP 621 依赖项主题 中的讨论。这包含了 Pipfile、Poetry、Dart 的依赖项 和 Rust 的 Cargo 的元素。一份 比较文档 展示了此格式与 PEP 508 样式规范之间的优缺点。
在使用相同发行版名称的多个需求的规范中(其中环境标记选择合适的依赖项),选择的解决方案类似于 Poetry 的解决方案,其中允许使用需求数组。
直接引用键与 PEP 610 和 PEP 440 紧密结合并利用它们,以减少打包生态系统中的差异并依赖于规范中的先前工作。
规范
与 PEP 621 一样,如果元数据指定不正确,则工具必须引发错误。元数据必须符合 TOML 规范。
为了避免混淆,因为本文档是指定依赖项的规范,所以“需求”一词表示 PEP 508 依赖项规范。
以下表格将添加到 PEP 621 中指定的 project
表格中。
依赖项
格式:表格
此表格中的键是所需发行版的名称。值可以具有以下类型之一
- 字符串:需求仅由版本需求定义,与需求表中的
version
具有相同的规范,但允许使用空字符串""
对版本不加限制。 - 表格:需求表。
- 数组:需求表的数组。将空数组
[]
指定为值是错误的。
需求表
需求表的键如下(所有键都是可选的)
version
(字符串):PEP 440 版本规范,它是由版本规范子句组成的逗号分隔列表。字符串必须是非空的。extras
(字符串数组):发行版的 PEP 508 扩展声明列表。列表必须是非空的。markers
(字符串):PEP 508 环境标记表达式。字符串必须是非空的。url
(字符串):要安装并满足需求的工件的 URL。请注意,file://
是用于从本地文件系统检索包的前缀。git
、hg
、bzr
或svn
(字符串):要克隆的 VCS 存储库的 URL(如 PEP 440 中指定),其树将被安装以满足需求。其他 VCS 键将通过对 PEP 610 的修订来添加,但是工具可以选择在修订被接受之前使用其命令行命令来支持其他 VCS。revision
(字符串):要安装之前签出的指定 VCS 存储库的特定修订版的标识符。仅当使用git
、hg
、bzr
、svn
或其他 VCS 键之一来识别要安装的发行版时,用户才必须提供此标识符。修订版标识符在 PEP 610 中进行了建议。
最多可以同时指定以下键中的一个,因为它们在需求中在逻辑上相互冲突:version
、url
、git
、hg
、bzr
、svn
以及任何其他 VCS 键。
除了空字符串 ""
之外,空需求表 {}
对需求不加限制。
提供的任何未在此文档中指定的键都必须导致解析错误。
可选依赖项
格式:表格
此表格中的键是扩展所需发行版的名称。值可以具有以下类型之一
- 表格:需求表。
- 数组:需求表的数组。
这些需求表具有 与上述相同的规范,并增加了以下必需键
for-extra
(字符串):此需求所需的 PEP 508 扩展的名称。
参考实现
工具需要将此格式转换为 PEP 508 需求字符串。下面是该转换的一个示例实现(假设已执行验证)
def convert_requirement_to_pep508(name, requirement):
if isinstance(requirement, str):
requirement = {"version": requirement}
pep508 = name
if "extras" in requirement:
pep508 += " [" + ", ".join(requirement["extras"]) + "]"
if "version" in requirement:
pep508 += " " + requirement["version"]
if "url" in requirement:
pep508 += " @ " + requirement["url"]
for vcs in ("git", "hg", "bzr", "svn"):
if vcs in requirement:
pep508 += " @ " + vcs + "+" + requirement[vcs]
if "revision" in requirement:
pep508 += "@" + requirement["revision"]
extra = None
if "for-extra" in requirement:
extra = requirement["for-extra"]
if "markers" in requirement:
markers = requirement["markers"]
if extra:
markers = "extra = '" + extra + "' and (" + markers + ")"
pep508 += "; " + markers
return pep508, extra
def convert_requirements_to_pep508(dependencies):
pep508s = []
extras = set()
for name, req in dependencies.items():
if isinstance(req, list):
for sub_req in req:
pep508, extra = convert_requirement_to_pep508(name, sub_req)
pep508s.append(pep508)
if extra:
extras.add(extra)
else:
pep508, extra = convert_requirement_to_pep508(name, req)
pep508s.append(pep508)
if extra:
extras.add(extra)
return pep508s, extras
def convert_project_requirements_to_pep508(project):
reqs, _ = convert_requirements_to_pep508(project.get("dependencies", {}))
optional_reqs, extras = convert_requirements_to_pep508(
project.get("optional-dependencies", {})
)
reqs += optional_reqs
return reqs, extras
JSON 模式
对于初始验证,可以使用 JSON 模式。这不仅有助于工具进行一致的验证,还允许代码编辑器在用户构建依赖项列表时突出显示验证错误。
{
"$id": "spam",
"$schema": "https://json-schema.fullstack.org.cn/draft-07/schema#",
"title": "Project metadata",
"type": "object",
"definitions": {
"requirementTable": {
"title": "Full project dependency specification",
"type": "object",
"properties": {
"extras": {
"title": "Dependency extras",
"type": "array",
"items": {
"title": "Dependency extra",
"type": "string"
}
},
"markers": {
"title": "Dependency environment markers",
"type": "string"
}
},
"propertyNames": {
"enum": [
"extras",
"markers",
"version",
"url",
"git",
"hg",
"bzr",
"svn",
"for-extra"
]
},
"oneOf": [
{
"title": "Version requirement",
"properties": {
"version": {
"title": "Version",
"type": "string"
}
}
},
{
"title": "URL requirement",
"properties": {
"url": {
"title": "URL",
"type": "string",
"format": "uri"
}
},
"required": [
"url"
]
},
{
"title": "VCS requirement",
"properties": {
"revision": {
"title": "VCS repository revision",
"type": "string"
}
},
"oneOf": [
{
"title": "Git repository",
"properties": {
"git": {
"title": "Git URL",
"type": "string",
"format": "uri"
}
},
"required": [
"git"
]
},
{
"title": "Mercurial repository",
"properties": {
"hg": {
"title": "Mercurial URL",
"type": "string",
"format": "uri"
}
},
"required": [
"hg"
]
},
{
"title": "Bazaar repository",
"properties": {
"bzr": {
"title": "Bazaar URL",
"type": "string",
"format": "uri"
}
},
"required": [
"bzr"
]
},
{
"title": "Subversion repository",
"properties": {
"svn": {
"title": "Subversion URL",
"type": "string",
"format": "uri"
}
},
"required": [
"svn"
]
}
]
}
]
},
"requirementVersion": {
"title": "Version project dependency specification",
"type": "string"
},
"requirement": {
"title": "Project dependency specification",
"oneOf": [
{
"$ref": "#/definitions/requirementVersion"
},
{
"$ref": "#/definitions/requirementTable"
},
{
"title": "Multiple specifications",
"type": "array",
"items": {
"$ref": "#/definitions/requirementTable"
},
"minLength": 1
}
]
},
"optionalRequirementTable": {
"title": "Project optional dependency specification table",
"allOf": [
{
"$ref": "#/definitions/requirementTable"
},
{
"properties": {
"for-extra": {
"title": "Dependency's extra",
"type": "string"
}
},
"required": [
"for-extra"
]
}
]
},
"optionalRequirement": {
"title": "Project optional dependency specification",
"oneOf": [
{
"$ref": "#/definitions/optionalRequirementTable"
},
{
"title": "Multiple specifications",
"type": "array",
"items": {
"$ref": "#/definitions/optionalRequirementTable"
},
"minLength": 1
}
]
}
},
"properties": {
"dependencies": {
"title": "Project dependencies",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/requirement"
}
},
"optional-dependencies": {
"title": "Project dependencies",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/optionalRequirement"
}
}
}
}
示例
完整的示例
[project.dependencies]
flask = { }
django = { }
requests = { version = ">= 2.8.1, == 2.8.*", extras = ["security", "tests"], markers = "python_version < '2.7'" }
pip = { url = "https://github.com/pypa/pip/archive/1.3.1.zip" }
sphinx = { git = "ssh://git@github.com/sphinx-doc/sphinx.git" }
numpy = "~=1.18"
pytest = [
{ version = "<6", markers = "python_version < '3.5'" },
{ version = ">=6", markers = "python_version >= '3.5'" },
]
[project.optional-dependencies]
pytest-timout = { for-extra = "dev" }
pytest-mock = [
{ version = "<6", markers = "python_version < '3.5'", for-extra = "dev" },
{ version = ">=6", markers = "python_version >= '3.5'", for-extra = "dev" },
]
为了向 PEP 631 致敬,以下是 docker-compose 的等效依赖项规范
[project.dependencies]
cached-property = ">= 1.2.0, < 2"
distro = ">= 1.2.0, < 2"
docker = { extras = ["ssh"], version = ">= 4.2.2, < 5" }
docopt = ">= 0.6.1, < 1"
jsonschema = ">= 2.5.1, < 4"
PyYAML = ">= 3.10, < 6"
python-dotenv = ">= 0.13.0, < 1"
requests = ">= 2.20.0, < 3"
texttable = ">= 0.9.0, < 2"
websocket-client = ">= 0.32.0, < 1"
# Conditional
"backports.shutil_get_terminal_size" = { version = "== 1.0.0", markers = "python_version < '3.3'" }
"backports.ssl_match_hostname" = { version = ">= 3.5, < 4", markers = "python_version < '3.5'" }
colorama = { version = ">= 0.4, < 1", markers = "sys_platform == 'win32'" }
enum34 = { version = ">= 1.0.4, < 2", markers = "python_version < '3.4'" }
ipaddress = { version = ">= 1.0.16, < 2", markers = "python_version < '3.3'" }
subprocess32 = { version = ">= 3.5.4, < 4", markers = "python_version < '3.2'" }
[project.optional-dependencies]
PySocks = { version = ">= 1.5.6, != 1.5.7, < 2", for-extra = "socks" }
ddt = { version = ">= 1.2.2, < 2", for-extra = "tests" }
pytest = { version = "< 6", for-extra = "tests" }
mock = { version = ">= 1.0.1, < 4", markers = "python_version < '3.4'", for-extra = "tests" }
兼容性示例
本 PEP 的作者认识到各种工具都需要读取和写入这种依赖项规范格式。本节旨在提供与当前使用的标准 PEP 508 的直接比较和翻译示例。
注意
为了简单和清晰起见,TOML 允许您指定每个规范的各种方式没有在此处表示。这些示例使用标准的内联表示。
例如,虽然以下在 TOML 中被认为是等价的,但我们选择第二种形式作为本节中的示例。
aiohttp.version = "== 3.6.2"
aiohttp = { version = "== 3.6.2" }
版本约束依赖项
无版本约束
aiohttp
aiohttp = {}
简单版本约束
aiohttp >= 3.6.2, < 4.0.0
aiohttp = { version = ">= 3.6.2, < 4.0.0" }
注意
为了简洁起见,这也可以表示为字符串。
aiohttp = ">= 3.6.2, < 4.0.0"
直接引用依赖项
URL 依赖项
aiohttp @ https://files.pythonhosted.org/packages/97/d1/1cc7a1f84097d7abdc6c09ee8d2260366f081f8e82da36ebb22a25cdda9f/aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl
aiohttp = { url = "https://files.pythonhosted.org/packages/97/d1/1cc7a1f84097d7abdc6c09ee8d2260366f081f8e82da36ebb22a25cdda9f/aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl" }
VCS 依赖项
aiohttp @ git+ssh://git@github.com/aio-libs/aiohttp.git@master
aiohttp = { git = "ssh://git@github.com/aio-libs/aiohttp.git", revision = "master" }
环境标记
aiohttp >= 3.6.1; python_version >= '3.8'
aiohttp = { version = ">= 3.6.1", markers = "python_version >= '3.8'" }
上面示例的一个稍微扩展的版本,其中根据解释器版本需要特定版本的 aiohttp
。
aiohttp >= 3.6.1; python_version >= '3.8'
aiohttp >= 3.0.0, < 3.6.1; python_version < '3.8'
aiohttp = [
{ version = ">= 3.6.1", markers = "python_version >= '3.8'" },
{ version = ">= 3.0.0, < 3.6.1", markers = "python_version < '3.8'" }
]
包扩展
指定包扩展的依赖项
aiohttp >= 3.6.2; extra == 'http'
aiohttp = { version = ">= 3.6.2", for-extra = "http" }
使用依赖项中的扩展
aiohttp [speedups] >= 3.6.2
aiohttp = { version = ">= 3.6.2", extras = ["speedups"] }
复杂示例
版本约束
aiohttp [speedups] >= 3.6.2; python_version >= '3.8' and extra == 'http'
aiohttp = { version = ">= 3.6.2", extras = ["speedups"], markers = "python_version >= '3.8'", for-extra = "http" }
直接引用 (VCS)
aiohttp [speedups] @ git+ssh://git@github.com/aio-libs/aiohttp.git@master ; python_version >= '3.8' and extra == 'http'
aiohttp = { git = "ssh://git@github.com/aio-libs/aiohttp.git", revision = "master", extras = ["speedups"], markers = "python_version >= '3.8'", for-extra = "http" }
已拒绝的想法
切换到 dependencies
的数组
使用数组而不是表,以便每个元素都只是一个表(带有 name
键)并且没有需求表的数组。这在 TOML 格式中非常冗长且限制性强,并且对于给定发行版有多个需求的情况并不常见。
用 extras
替换 optional-dependencies
删除 optional-dependencies
表,转而同时在需求中包含 optional
键和一个 extras
表,该表指定项目扩展所需的(可选)需求。这减少了具有相同规范的表的数量(到 1),并允许为需求指定一次但在多个扩展中使用,但它使需求的一些属性(它属于哪个扩展)与其他属性分开,将必需和可选的依赖项组合在一起(可能混合),并且在发行版有多个需求时可能没有简单的方法来选择需求。由于 optional-dependencies
已经在 PEP 621 草案中使用,因此该方案被拒绝。
需求中的 direct
表格
将直接引用键包含在 direct
表中,将 VCS 指定为 vcs
键的值。这更加明确,并且更容易包含在 JSON 模式验证中,但被认为过于冗长且可读性不强。
包含哈希值
在直接引用需求中包含哈希值。这仅用于包锁定文件,并且在项目的元数据中没有真正的位置。
每个扩展的依赖项表格
使 optional-dependencies
成为每个扩展的依赖项表的表,表名是扩展的名称。这使得 optional-dependencies
与 dependencies
(需求表)具有不同的类型(需求表的表),这可能会让用户感到困惑,并且更难解析。
环境标记键
将每个 PEP 508 环境标记作为需求中的键(或子表键)。这可以说提高了可读性和易于解析性。 markers
键仍然允许用于更高级的规范,其中键指定的环境标记与结果进行 and
操作。这被推迟了,因为需要进行更多设计。
多个扩展可以满足一个需求
用 for-extras
替换 for-extra
键,其值为需求满足的扩展数组。这减少了一些重复,但在这种情况下,这种重复明确了哪些扩展具有哪些依赖项。
版权
本文档放置在公共领域或根据 CC0-1.0-Universal 许可证,以较宽松者为准。
来源: https://github.com/python/peps/blob/main/peps/pep-0633.rst
上次修改: 2023-09-09 17:39:29 GMT