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

Python 增强提案

PEP 633 – 在 pyproject.toml 中使用展开的 TOML 表指定依赖项

作者:
Laurie Opperman <laurie_opperman at hotmail.com>, Arun Babu Neelicattu <arun.neelicattu at gmail.com>
发起人:
Brett Cannon <brett at python.org>
讨论至:
Discourse 帖子
状态:
已拒绝
类型:
标准跟踪
主题:
打包
创建日期:
2020年9月2日
发布历史:
2020年9月2日
决议:
Discourse 消息

目录

拒绝通知

此 PEP 已被 PEP 631 取代,原因是 PEP 631 的受欢迎程度、与现有 PEP 508 字符串的用法一致性,以及与现有打包工具集的兼容性。

摘要

此 PEP 规定了如何使用 PEP 621 中定义的字段,在 pyproject.toml 文件中为打包相关工具编写项目依赖项,作为 PEP 631 中定义的基于 PEP 508 的方法的一种替代方案。

动机

使用 TOML 表和其他数据类型来表示依赖项而不是 PEP 508 字符串有许多优点:

  • 通过 TOML 语法进行轻松的初始验证。
  • 使用模式(例如 JSON Schema)进行轻松的二次验证。
  • 用户有可能猜测给定功能的键,而不是死记硬背语法。
  • 使用多种其他流行语言的用户可能已经熟悉 TOML 语法。
  • TOML 直接表示与 JSON 中相同的数据结构,因此是 Python 字面量的子集,用户可以理解值的层次结构和类型。

基本原理

大部分内容取自 PEP 621 依赖项话题 中的讨论。其中包含了 PipfilePoetryDart 的依赖项Rust 的 Cargo 的元素。一份 比较文档 显示了此格式与 PEP 508 风格的说明符之间的优缺点。

在指定具有相同发行版名称的多个依赖项(其中环境标记选择适当的依赖项)时,所选解决方案类似于 Poetry,允许使用依赖项的数组。

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

规范

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

为了减少与本文件作为指定依赖项的规范相混淆,使用“requirement”(依赖项)一词来表示 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 Schema

对于初始验证,可以使用 JSON Schema。这不仅有助于工具进行一致的验证,还能让代码编辑器在用户构建依赖项列表时突出显示验证错误。

{
    "$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 格式中非常冗长且受限,而且一个发行版有多个依赖项的情况并不常见。

optional-dependencies 替换为 extras

移除 optional-dependencies 表,改为在依赖项中包含一个 optional 键,以及一个 extras 表,该表指定了项目额外依赖项所需的(可选)依赖项。这减少了具有相同规范的表数量(至 1 个),并允许依赖项被指定一次但用于多个额外依赖项,但将某些依赖项的属性(它属于哪个额外依赖项)分开,将必需和可选依赖项分组在一起(可能混合),并且可能没有简单的方法来选择一个依赖项,当一个发行版有多个依赖项时。此提议被拒绝,因为 optional-dependencies 已在 PEP 621 草案中使用。

依赖项中的 direct

将直接引用键包含在 direct 表中,将 VCS 指定为 vcs 键的值。这更明确,更容易包含在 JSON Schema 验证中,但被认为过于冗长且可读性不高。

包含哈希

在直接引用依赖项中包含哈希。这仅用于包锁定文件,在项目的元数据中没有实际位置。

每个额外依赖项的依赖项表

optional-dependencies 设为一个依赖项表表,每个表对应一个额外依赖项,表名为该额外依赖项的名称。这使得 optional-dependenciesdependencies(依赖项表)具有不同的类型(表套表),这可能会让用户感到困惑且更难解析。

环境标记键

将每个 PEP 508 环境标记作为依赖项中的键(或子表键)。这可以提高可读性和解析的简便性。仍然允许使用 markers 键来进行更高级的规范,其中键指定的环境标记会与结果进行“与”运算。这被推迟了,因为需要更多的设计。

一个依赖项可以满足的多个额外依赖项

for-extra 键替换为 for-extras,其值为依赖项所满足的额外依赖项的数组。这减少了一些重复,但在这种情况下,这种重复使得哪个额外依赖项具有哪些依赖项变得更加明确。


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

最后修改:2025-02-01 08:55:40 GMT