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

Python 增強提案 (Python Enhancement Proposals)

PEP 621 – 在 pyproject.toml 中儲存專案詮釋資料

作者:
Brett Cannon <brett at python.org>, Dustin Ingram <di at python.org>, Paul Ganssle <paul at ganssle.io>, Pradyun Gedam <pradyunsg at gmail.com>, Sébastien Eustace <sebastien at eustace.io>, Thomas Kluyver <thomas at kluyver.me.uk>, Tzu-ping Chung <uranusjr at gmail.com>
討論於:
Discourse 討論串
狀態:
最終 (Final)
類型:
標準軌跡 (Standards Track)
主題:
套件封裝 (Packaging)
建立日期:
2020年6月22日
公告歷史:
2020年6月22日, 2020年10月18日, 2020年10月24日, 2020年10月31日
決議:
Discourse 訊息

目錄

重要資訊

本 PEP 是一份歷史文件。最新且權威的規範 pyproject.toml 規範,維護於 PyPA 規範頁面

×

關於如何提出變更,請參閱 PyPA 規範更新流程

摘要

本 PEP 規定了如何將專案的 核心詮釋資料 寫入 pyproject.toml 檔案中,以供封裝工具使用。

動機

本 PEP 的主要動機包括:

  • 鼓勵使用者以靜態方式指定核心詮釋資料,以提升速度、簡化規範、確保無歧義,並使構建後端能夠進行確定性的處理
  • 提供一種與特定工具無關的詮釋資料指定方式,以便於學習並在不同構建後端之間轉換
  • 允許在構建後端之間共享更多關於專案詮釋資料中「枯燥部分」的程式碼

特別提到對靜態詮釋資料的動機,這長期以來一直是封裝生態系統的總體目標。因此,使其易於以靜態方式指定詮釋資料非常重要。這也意味著增加將資料指定為動態的成本是可以接受的,因為使用者應該傾向於提供靜態詮釋資料。

要求區分靜態和動態詮釋資料,也有助於消除未指定詮釋資料時的歧義。當任何詮釋資料「可能」是動態時,意味著你永遠不知道詮釋資料的缺失是有意為之,還是為了稍後提供。透過要求指定動態詮釋資料,可以在詮釋資料未指定時釐清其意圖。

本 PEP 並試圖標準化構建後端所需的所有可能詮釋資料,僅涵蓋 核心詮釋資料 規範所包含的內容,這些內容在各個專案中非常普遍,且從靜態並一致地指定中受益匪淺。這意味著構建後端仍然可以自由地針對諸如「如何指定要包含在 wheel 中的檔案」等模式進行創新。此外,亦包含了預留機制,供使用者和構建後端在選擇部分拒絕採用本 PEP 時使用(相較於完全拒絕本 PEP,這也是一種可能)。

本 PEP 也不打算以任何方式更改底層的 核心詮釋資料。此類考量應在獨立的 PEP 中進行,該 PEP 可能會導致對本規範所定義內容的更改或補充。

原理

本 PEP 作者遵循的設計準則為:

  • pyproject.toml 中合理地定義盡可能多的 核心詮釋資料
  • 以靜態方式定義詮釋資料,並為那些希望稍後透過構建後端動態定義詮釋資料的人提供預留機制
  • 在合理的情況下使用熟悉的名稱,但也願意使用更現代的術語
  • 在 TOML 檔案中盡量符合人體工學,而不是在合理的情況下鏡像反映構建後端指定低階詮釋資料的方式
  • 向封裝生態系統中其他已使用 TOML 進行詮釋資料管理的構建後端學習
  • 不要試圖標準化那些在底層尚無既定標準的事物
  • 使用本 PEP 指定詮釋資料時,它被視為權威的

規範

在指定專案詮釋資料時,工具必須遵守並尊重本 PEP 中指定的詮釋資料。如果詮釋資料指定不當,工具必須拋出錯誤以通知使用者其錯誤之處。

使用本 PEP 指定的資料被視為權威的。工具不得移除、新增或更改已靜態指定的資料。只有當欄位被標記為 dynamic 時,工具才可以提供「新」值。

詳情

表格名稱

工具必須在名為 [project] 的表格中指定本 PEP 定義的欄位。任何工具不得在此表格中新增未經本 PEP 或後續 PEP 定義的欄位。對於希望將自己的設定儲存在 pyproject.toml 中的工具,它們可以使用 PEP 518 中定義的 [tool] 表格。缺少 [project] 表格隱含地表示構建後端將動態提供所有欄位。

名稱

專案名稱。

工具必須要求使用者靜態定義此欄位。

為了內部一致性,工具在讀取名稱後,應立即按照 PEP 503 的規定對其進行標準化。

version

專案版本,符合 PEP 440

使用者應優先指定已標準化的版本。

說明

專案的摘要描述。

readme

專案的完整描述(即 README)。

該欄位接受字串或表格。如果是字串,則它是包含完整描述之文字檔的相對路徑。工具必須假定檔案編碼為 UTF-8。如果檔案路徑以不區分大小寫的 .md 結尾,則工具必須假定內容類型為 text/markdown。如果檔案路徑以不區分大小寫的 .rst 結尾,則工具必須假定內容類型為 text/x-rst。如果工具識別出的副檔名多於本 PEP,它們可以在不將此欄位指定為 dynamic 的情況下,為使用者推斷內容類型。對於所有未識別的後綴且未提供內容類型的情況,工具必須拋出錯誤。

readme 欄位也可以採用表格形式。file 鍵的值為字串,表示包含完整描述之檔案的相對路徑。text 鍵的值為字串,即完整描述。這些鍵是互斥的,因此如果詮釋資料同時指定了這兩個鍵,工具必須拋出錯誤。

readme 欄位中指定的表格還具有 content-type 欄位,該欄位採用一個字串來指定完整描述的內容類型。如果詮釋資料未在表格中指定此欄位,工具必須拋出錯誤。如果詮釋資料未指定 charset 參數,則假定為 UTF-8。工具可以選擇支援其他編碼。工具可以支援其能夠轉換為 核心詮釋資料 所支援內容類型的替代內容類型。否則,對於不支援的內容類型,工具必須拋出錯誤。

requires-python

專案的 Python 版本要求。

license

該表格可能具有兩個鍵中的一個。file 鍵的值為字串,是包含專案授權條款檔案的相對路徑。工具必須假定檔案編碼為 UTF-8。text 鍵的值為字串,即專案的授權內容,其意義與 核心詮釋資料 中的 License 欄位相同。這些鍵是互斥的,因此如果詮釋資料同時指定了這兩個鍵,工具必須拋出錯誤。

license 鍵保留一個實用的字串值,是為了讓未來的 PEP 能指定對 SPDX 表達式的支援(同樣的邏輯適用於任何指定 filetext 代表什麼授權的「類型」欄位)。

authors/maintainers

  • 格式:包含字串鍵和值的內聯表格陣列
  • 核心詮釋資料Author/Author-email/Maintainer/Maintainer-email (連結)
  • 同義詞

被視為專案「作者」的人員或組織。其確切含義可自由解釋——它可以列出原始或主要作者、目前的維護者或套件擁有者。

「維護者」(maintainers) 欄位與「作者」類似,其確切含義同樣可自由解釋。

這些欄位接受一個包含 2 個鍵的表格陣列:nameemail。兩個值都必須是字串。name 值必須是有效的電子郵件名稱(即在 RFC 822 中,電子郵件地址前可放置的任何名稱),且不得包含逗號。email 值必須是有效的電子郵件地址。這兩個鍵都是選填的。

使用資料填充 核心詮釋資料 的方式如下:

  1. 如果僅提供 name,則該值將放入對應的 Author/Maintainer 中。
  2. 如果僅提供 email,則該值將放入對應的 Author-email/Maintainer-email 中。
  3. 如果同時提供 emailname,則該值將放入對應的 Author-email/Maintainer-email 中,格式為 {name} <{email}>(並附帶適當的引號,例如使用 email.headerregistry.Address)。
  4. 多個值應以逗號分隔。

keywords

專案關鍵字。

classifiers

適用於該專案的 Trove 分類器

urls

URL 表格,其中鍵為 URL 標籤,值為 URL 本身。

進入點 (Entry points)

  • 格式:表格([project.scripts][project.gui-scripts][project.entry-points]
  • 核心詮釋資料:不適用;進入點規範
  • 同義詞
    • Flit: [tool.flit.scripts] 表格用於主控台腳本,[tool.flit.entrypoints] 用於其餘部分 (連結)
    • Poetry: [tool.poetry.scripts] 表格用於主控台腳本 (連結)
    • Setuptools: entry_points (連結)

有三個與進入點相關的表格。[project.scripts] 表格對應於 進入點規範 中的 console_scripts 群組。表格的鍵是進入點的名稱,值是物件參照。

[project.gui-scripts] 表格對應於 進入點規範 中的 gui_scripts 群組。其格式與 [project.scripts] 相同。

[project.entry-points] 表格是多個表格的集合。每個子表格的名稱是一個進入點群組。鍵和值的語義與 [project.scripts] 相同。使用者不得建立巢狀子表格,而應將進入點群組保持在單層深度。

如果詮釋資料定義了 [project.entry-points.console_scripts][project.entry-points.gui_scripts] 表格,構建後端必須拋出錯誤,因為它們在 [project.scripts][project.gui-scripts] 面前會產生歧義。

dependencies/optional-dependencies

  • 格式:PEP 508 字串陣列(dependencies)以及值為 PEP 508 字串陣列的表格(optional-dependencies
  • 核心詮釋資料Requires-DistProvides-Extra (連結, 連結)
  • 同義詞
    • Flit: requires 用於必要依賴項,requires-extra 用於可選依賴項 (連結)
    • Poetry: [tool.poetry.dependencies] 用於依賴項(必要和開發用),[tool.poetry.extras] 用於可選依賴項 (連結)
    • Setuptools: install_requires 用於必要依賴項,extras_require 用於可選依賴項 (連結)

專案的(可選)依賴項。

對於 dependencies,它是一個鍵,其值為字串陣列。每個字串代表專案的一個依賴項,並且必須格式化為有效的 PEP 508 字串。每個字串直接映射到 核心詮釋資料 中的一個 Requires-Dist 條目。

對於 optional-dependencies,它是一個表格,其中每個鍵指定一個額外選項,其值為字串陣列。陣列中的字串必須是有效的 PEP 508 字串。鍵必須是 Provides-Extra 核心詮釋資料 的有效值。因此,陣列中的每個值都成為匹配的 Provides-Extra 詮釋資料的對應 Requires-Dist 條目。

dynamic

指定本 PEP 列出的哪些欄位是刻意未指定的,以便其他工具可以/將會動態提供此類詮釋資料。這明確劃分了哪些詮釋資料是刻意未指定且預期保持未指定狀態,與稍後透過工具提供的情況。

  • 構建後端必須遵守靜態指定的詮釋資料(這意味著詮釋資料未在 dynamic 中列出該欄位)。
  • 如果詮釋資料在 dynamic 中指定了 name,構建後端必須拋出錯誤。
  • 如果 核心詮釋資料 規範將欄位列為「必要」(Required),則詮釋資料必須以靜態方式指定該欄位或將其列在 dynamic 中(否則構建後端必須拋出錯誤,即必填欄位不應以某種方式未列在 [project] 表格中)。
  • 如果 核心詮釋資料 規範將欄位列為「選填」(Optional),如果預期構建後端稍後將提供該欄位的資料,則詮釋資料可以將其列在 dynamic 中。
  • 如果詮釋資料既以靜態方式指定了某個欄位,又將其列在 dynamic 中,構建後端必須拋出錯誤。
  • 如果詮釋資料未將欄位列在 dynamic 中,則構建後端不得代表使用者填寫必要的詮釋資料(即 dynamic 是允許工具填寫詮釋資料的唯一方式,且使用者必須同意填寫)。
  • 如果詮釋資料在 dynamic 中指定了欄位,但構建後端無法為其提供資料,構建後端必須拋出錯誤。

範例

[project]
name = "spam"
version = "2020.0.0"
description = "Lovely Spam! Wonderful Spam!"
readme = "README.rst"
requires-python = ">=3.8"
license = {file = "LICENSE.txt"}
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
authors = [
  {email = "hi@pradyunsg.me"},
  {name = "Tzu-ping Chung"}
]
maintainers = [
  {name = "Brett Cannon", email = "brett@python.org"}
]
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python"
]

dependencies = [
  "httpx",
  "gidgethub[httpx]>4.0.0",
  "django>2.1; os_name != 'nt'",
  "django>2.0; os_name == 'nt'"
]

[project.optional-dependencies]
test = [
  "pytest < 5.0.0",
  "pytest-cov[all]"
]

[project.urls]
homepage = "https://example.com"
documentation = "https://readthedocs.org"
repository = "https://github.com"
changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"

[project.scripts]
spam-cli = "spam:main_cli"

[project.gui-scripts]
spam-gui = "spam:main_gui"

[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"

回溯相容性

由於這提供了一種指定專案 核心詮釋資料 的新方式,並且使用的是根據 PEP 518 規定屬於保留命名空間的新表格名稱,因此不存在向後相容性的擔憂。

安全性影響

由於本 PEP 涵蓋了如何靜態定義專案詮釋資料,因此沒有直接的安全問題。任何安全問題都將源於工具如何使用這些詮釋資料並選擇如何處理它們。

參考實作

目前沒有任何實作本 PEP 的構建後端提供概念驗證。

否決的想法

其他表格名稱

[build-system] 下的任何內容

人們擔心使用此表格名稱會加劇構建詮釋資料與專案詮釋資料之間的混淆,例如透過使用 [build-system.metadata] 作為表格。

[package]

沒有獲得強有力的支持。

[metadata]

這是繼 [project] 之後最強力的競爭者,但最終達成共識,認為對於某些子表格,例如 [project.urls],使用 [project] 閱讀起來更佳。

對詮釋資料提供者的支援

最初曾有一個提案,在由本 PEP 指定的靜態詮釋資料與 PEP 517 指定的 prepare_metadata_for_build_wheel() 之間增加一個中間層。其想法是,如果專案希望插入構建後端與詮釋資料之間,將會有一個掛鉤來實現。

最終,作者們認為這個想法過於複雜,且會使 PEP 偏離其設計目標,即推動人們盡可能以靜態方式定義核心詮釋資料。

要求標準化的專案名稱

雖然讓工具僅使用 PEP 503 中規定的標準化名稱會使事情變得更簡單,但該想法最終被否決,因為它會傷害那些轉向使用本 PEP 的專案。

指定構建時要包含的檔案

在設計討論中,作者們很快決定本 PEP 應僅關注專案詮釋資料,而非構建詮釋資料。因此,指定哪些檔案應該放入原始碼發行版或 wheel 檔案中不在本 PEP 的範圍內。

[project.urls] 表格命名為 [project.project-urls]

這個建議歸功於對應的 核心詮釋資料Project-Url。但一旦選擇了 [project] 作為整體的表格名稱,「project」一詞的冗餘使用建議使用目前更短的名稱會更合適。

使用獨立的 url/home-page 欄位

雖然 核心詮釋資料 支援它,但既擁有專案 URL 的單一欄位,又支援完整表格似乎是多餘且令人困惑的。

dynamic 欄位僅要求指定缺失的必要欄位

作者們考慮了這樣一個想法:dynamic 欄位僅要求列出缺失的必要欄位,並使列出選填欄位成為選填。然而,最終這違背了推廣盡可能以靜態方式指定資訊的設計目標。

readme 欄位的不同結構

readme 欄位有一個建議的 readme_content_type 欄位,但作者們認為字串/表格混合形式對於常見情況更實用,同時又能適應更複雜的情況。對於使用 long_description 和相應的 long_description_content_type 欄位也是如此。

表格格式中的 file 鍵最初被提議為 path,但 file 對應於 setuptools 的 file 鍵,且沒有強有力的理由選擇其中一個而非另一個。

允許 readme 欄位預設為 text/plain

作者們考慮允許未指定的內容類型預設為 text/plain,但決定在這種情況下最好明確說明,以防止 PyPI 上意外出現不正確的渲染,並迫使使用者清楚表達其意圖。

dependencies/optional-dependencies 的其他名稱

作者們最初提議使用 requires/extra-requires 作為名稱,但在調查了其他封裝生態系統顯示 Python 是例外之後,決定採用目前的名稱。

  1. npm
  2. Rust
  3. Dart
  4. Swift
  5. Ruby

使用目前的名稱有助於減少來自其他生態系統的人們的困惑,且不使用對新程式設計師來說必然陌生的術語。它還防止了與 PEP 518 中規定的 [build-system] 表格中的 requires 產生潛在混淆。

捨棄 maintainers 以便與 authors 統一

由於 核心詮釋資料AuthorsMaintainers 欄位之間的差異未指定且模糊,本 PEP 最初提議將它們統一為一個 authors 欄位。其他生態系統選擇「author」作為術語,因此想法是在核心詮釋資料中標準化為 Author,作為列出維護專案人員的地方。

然而,最終決定堅持遵守核心詮釋資料被認為對於幫助本 PEP 的接受更為重要,而不是試圖為某些核心詮釋資料引入新的解釋。

支援 project.entry-points 的任意深度表格

人們擔心將 project.entry-points 的子表格深度保持在 1 會讓使用者在使用點號名稱且不習慣使用引號的表格名稱(例如 project.entry-points."spam.magical")時產生混淆。但是,支援任意深度——例如 project.entry-points.spam.magical——將排除未來任何形式的展開表格格式。這也會使構建後端的工作複雜化,因為它們必須確保遍歷完整的表格結構,而不是單層結構,並在值類型不適當時拋出錯誤。

使用結構化的 TOML 字典來指定依賴項

指定專案依賴項的格式是數據格式方面爭議最激烈的議題。它導致了 PEP 631PEP 633 的建立,分別代表了本 PEP 中所呈現的內容,以及更廣泛地使用 TOML 字典。關於這些 PEP 的決定可以在 https://discuss.python.org/t/how-to-specify-dependencies-pep-508-strings-or-a-table-in-toml/5243/38 找到。

作者們曾短暫考慮支援兩種格式,但決定這會導致混淆,因為人們需要熟悉兩種格式而非僅一種。

要求構建後端在產生 sdist 時更新 pyproject.toml

撰寫本 PEP 時,sdists 不需要像本 PEP 那樣具有靜態、權威的詮釋資料。當時考慮將本 PEP 作為將此類詮釋資料納入 sdists 的一種方式。然而,更新 pyproject.toml 的想法普遍不受歡迎,因此該想法被拒絕,轉而支持另外尋求標準化 sdists 中的詮釋資料。

允許工具增加/擴充資料

在本 PEP 的早期版本中,允許工具擴充欄位的資料。例如,構建後端可以取得版本號並在構建 wheel 時新增本地版本。工具也可以為諸如授權條款或支援的 Python 版本等內容新增更多的 Trove 分類器。

然而,最終認為從更嚴格的限制開始,並根據實際使用情況考慮放寬資料的靜態程度會更好。

待決問題

目前沒有。


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

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