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

Python 增強提案 (Python Enhancement Proposals)

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 討論串
狀態:
已否決 (Rejected)
類型:
標準軌跡 (Standards Track)
主題:
套件封裝 (Packaging)
建立日期:
2020年9月2日
公告歷史:
2020年9月2日
決議:
Discourse 訊息

目錄

拒絕通知

此 PEP 已被否決,轉而支持 PEP 631,原因在於其普及性、與現有 PEP 508 字串用法的一致性,以及與現有打包工具套件的相容性。

摘要

此 PEP 規定如何在 pyproject.toml 檔案中撰寫專案的依賴項,供打包相關工具使用,其中欄位定義在 PEP 621 中。這是一種替代方案,用以取代在 PEP 631 中定義的基於 PEP 508 的方法。

動機

使用 TOML 表和其他資料型別來表示依賴項,而非 PEP 508 字串,具有多項優點。

  • 透過 TOML 語法,易於進行初步驗證。
  • 使用結構描述,例如 JSON 結構描述,易於進行次要驗證。
  • 使用者可能可以猜測給定功能的鍵,而非記憶語法。
  • 其他多種流行語言的使用者可能已熟悉 TOML 語法。
  • TOML 直接表示與 JSON 中相同的資料結構,因此是 Python 字面值的子集,這樣使用者就能理解層次結構和值的型別

原理

大部分內容取自 PEP 621 依賴項主題中的討論。其中包含來自 PipfilePoetryDart 的依賴項Rust 的 Cargo 的元素。一份 比較文件顯示了此格式與 PEP 508 風格指定器之間的優缺點。

在規範具有相同發行名稱的多個依賴項時 (其中環境標記選擇適當的依賴項),所選擇的解決方案與 Poetry 的類似,即允許使用依賴項陣列。

直接參考鍵與 PEP 610PEP 440 緊密對齊並利用其內容,以減少打包生態系統中的差異,並依賴先前的規範工作。

規範

PEP 621 中所述,如果中繼資料指定不當,則工具必須 (MUST) 引發錯誤。中繼資料必須 (MUST) 符合 TOML 規範。

為減少本文件作為指定依賴項的規範所引起的混淆,「依賴項」(requirement) 一詞在此表示 PEP 508 的依賴項規範。

以下表格被新增至 PEP 621 中指定的 project 表。

dependencies

格式:表

此表中的鍵是所需發行套件的名稱。值可以具有以下型別之一

  • 字串:依賴項僅由版本需求定義,規範與依賴項表中的 version 相同,但允許空字串 "" 對版本不施加任何限制。
  • 表:一個依賴項表。
  • 陣列:一個依賴項表陣列。將空陣列 [] 指定為值是錯誤的。

依賴項表

依賴項表的鍵如下 (所有皆為選用)

  • version (字串):一個 PEP 440 版本指定器,它是一個以逗號分隔的版本指定子句列表。此字串必須 (MUST) 非空。
  • extras (字串陣列):此發行套件的 PEP 508 額外功能宣告列表。此列表必須 (MUST) 非空。
  • markers (字串):一個 PEP 508 環境標記表達式。此字串必須 (MUST) 非空。
  • url (字串):要安裝並滿足依賴項的構件 (artifact) 的 URL。請注意,file:// 是用於從本地檔案系統檢索套件的前綴。
  • githgbzrsvn (字串):一個 VCS 儲存庫的 URL (如 PEP 440 中指定) 用於複製,其樹狀結構將被安裝以滿足依賴項。未來的 VCS 鍵將透過 PEP 610 的修訂來添加,然而,在修訂被接受之前,工具可以 (MAY) 選擇使用其命令列指令來支援其他 VCS。
  • revision (字串):在安裝前要檢出的指定 VCS 儲存庫的特定修訂版本識別碼。使用者必須 (MUST) 僅在 githgbzrsvn 或其他 VCS 鍵用於識別要安裝的發行套件時提供此項。PEP 610 中建議了修訂版本識別碼。

以下鍵中最多只能同時指定一個,因為它們在依賴項中邏輯上相互衝突:versionurlgithgbzrsvn,以及任何其他 VCS 鍵。

空的依賴項表 {} 對依賴項不施加任何限制,與空字串 "" 的作用相同。

任何未在本文件中指定的鍵,必須 (MUST) 導致解析錯誤。

選用依賴項

格式:表

此表中的鍵是某個額外功能所需的發行套件名稱。值可以具有以下型別之一

  • 表:一個依賴項表。
  • 陣列:一個依賴項表陣列。

這些依賴項表具有 與上方相同的規範,並新增以下必填鍵

  • 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": "http://json-schema.org/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 鍵,其中值是一個依賴項所滿足的額外功能陣列。這減少了一些重複,但在這種情況下,該重複明確了哪些額外功能具有哪些依賴項。


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

上次修改時間:2025-02-01 08:55:40 GMT