Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

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 依赖项主题 中的讨论。这包含了 PipfilePoetryDart 的依赖项Rust 的 Cargo 的元素。一份 比较文档 展示了此格式与 PEP 508 样式规范之间的优缺点。

在使用相同发行版名称的多个需求的规范中(其中环境标记选择合适的依赖项),选择的解决方案类似于 Poetry 的解决方案,其中允许使用需求数组。

直接引用键与 PEP 610PEP 440 紧密结合并利用它们,以减少打包生态系统中的差异并依赖于规范中的先前工作。

规范

PEP 621 一样,如果元数据指定不正确,则工具必须引发错误。元数据必须符合 TOML 规范。

为了避免混淆,因为本文档是指定依赖项的规范,所以“需求”一词表示 PEP 508 依赖项规范。

以下表格将添加到 PEP 621 中指定的 project 表格中。

依赖项

格式:表格

此表格中的键是所需发行版的名称。值可以具有以下类型之一

  • 字符串:需求仅由版本需求定义,与需求表中的 version 具有相同的规范,但允许使用空字符串 "" 对版本不加限制。
  • 表格:需求表。
  • 数组:需求表的数组。将空数组 [] 指定为值是错误的。

需求表

需求表的键如下(所有键都是可选的)

  • version(字符串):PEP 440 版本规范,它是由版本规范子句组成的逗号分隔列表。字符串必须是非空的。
  • extras(字符串数组):发行版的 PEP 508 扩展声明列表。列表必须是非空的。
  • markers(字符串):PEP 508 环境标记表达式。字符串必须是非空的。
  • url(字符串):要安装并满足需求的工件的 URL。请注意,file:// 是用于从本地文件系统检索包的前缀。
  • githgbzrsvn(字符串):要克隆的 VCS 存储库的 URL(如 PEP 440 中指定),其树将被安装以满足需求。其他 VCS 键将通过对 PEP 610 的修订来添加,但是工具可以选择在修订被接受之前使用其命令行命令来支持其他 VCS。
  • revision(字符串):要安装之前签出的指定 VCS 存储库的特定修订版的标识符。仅当使用 githgbzrsvn 或其他 VCS 键之一来识别要安装的发行版时,用户才必须提供此标识符。修订版标识符在 PEP 610 中进行了建议。

最多可以同时指定以下键中的一个,因为它们在需求中在逻辑上相互冲突:versionurlgithgbzrsvn 以及任何其他 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-dependenciesdependencies(需求表)具有不同的类型(需求表的表),这可能会让用户感到困惑,并且更难解析。

环境标记键

将每个 PEP 508 环境标记作为需求中的键(或子表键)。这可以说提高了可读性和易于解析性。 markers 键仍然允许用于更高级的规范,其中键指定的环境标记与结果进行 and 操作。这被推迟了,因为需要进行更多设计。

多个扩展可以满足一个需求

for-extras 替换 for-extra 键,其值为需求满足的扩展数组。这减少了一些重复,但在这种情况下,这种重复明确了哪些扩展具有哪些依赖项。


来源: https://github.com/python/peps/blob/main/peps/pep-0633.rst

上次修改: 2023-09-09 17:39:29 GMT