PEP 722 – 單檔案指令碼的依賴項規範
- 作者:
- Paul Moore <p.f.moore at gmail.com>
- PEP 代理人:
- Brett Cannon <brett at python.org>
- 討論至:
- Discourse 帖子
- 狀態:
- 已拒絕
- 型別:
- 標準跟蹤
- 主題:
- 打包
- 建立日期:
- 2023年7月19日
- 釋出歷史:
- 2023年7月19日
- 取代者:
- 723
- 決議:
- 2023年10月21日
摘要
此 PEP 規範了一種在單檔案 Python 指令碼中包含第三方依賴項的格式。
動機
並非所有 Python 程式碼都組織成一個“專案”,即擁有自己的目錄,包含 pyproject.toml 檔案,並構建成可安裝的分發包。Python 也經常被用作指令碼語言,Python 指令碼是 shell 指令碼、批處理檔案等的(更好的)替代品。在用於建立指令碼時,Python 程式碼通常儲存為單個檔案,通常位於專門用於此類“實用指令碼”的目錄中,這些目錄可能是多種語言的混合體,而 Python 只是其中一種可能性。此類指令碼可能會被共享,通常透過電子郵件或指向 Github gist 等 URL 的連結來共享。但它們通常不是在正常工作流程中被“分發”或“安裝”的。
以這種方式使用 Python 作為指令碼語言時的一個問題是如何在包含指令碼所需的所有第三方依賴項的環境中執行該指令碼。目前沒有標準工具來解決這個問題,而本 PEP不試圖定義一個。然而,任何解決此問題的工具都需要知道指令碼需要哪些第三方依賴項。透過定義一種儲存此類資料的標準格式,現有工具以及任何未來工具都將能夠獲取該資訊,而無需使用者在指令碼中包含特定於工具的元資料。
基本原理
由於關鍵要求是編寫單檔案指令碼,並透過提供指令碼副本即可簡單共享,因此 PEP 定義了一種機制,用於將依賴項資料嵌入到指令碼本身,而不是外部檔案。
我們定義了依賴項塊的概念,其中包含有關指令碼依賴哪些第三方包的資訊。
為了識別依賴項塊,指令碼可以像讀取文字檔案一樣讀取。這是故意的,因為 Python 語法會隨著時間而改變,因此嘗試將指令碼解析為 Python 程式碼需要選擇特定的 Python 語法版本。此外,很可能至少一些工具不會用 Python 編寫,並且期望它們實現 Python 解析器將負擔過重。
但是,為了避免對核心 Python 進行更改,該格式被設計為對 Python 解析器顯示為註釋。有可能編寫一個依賴項塊不被解釋為註釋的程式碼(例如,將其嵌入到 Python 多行字串中),但此類用法是不被鼓勵的,並且可以很容易地避免,除非您故意嘗試建立一個病態的示例。
對其他語言如何允許指令碼指定其依賴項的審查表明,“結構化註釋”是一種常用的方法。
規範
本節內容將作為標題為“在指令碼檔案中嵌入元資料”的文件釋出在 Python 打包使用者指南的 PyPA 規範部分。
任何 Python 指令碼都可以包含一個依賴項塊。透過將指令碼作為文字檔案讀取(即,不將檔案解析為 Python 原始碼)來識別依賴項塊,查詢形式為
# Script Dependencies:
雜湊字元必須位於行首,前面沒有任何空格。“Script Dependencies”文字不區分大小寫,空格表示任意空格(但至少必須有一個空格)。以下正則表示式可以識別依賴項塊標題行
(?i)^#\s+script\s+dependencies:\s*$
讀取依賴項塊的工具可以遵循標準的 Python 編碼宣告。如果它們選擇不這樣做,則必須將檔案作為 UTF-8 處理。
在標題行之後,檔案中的所有行直到第一個不以 # 開頭的行都被視為依賴項行,並按以下方式處理
- 開頭的
#符號將被剝離。 - 如果行包含“ # ”(空格井號空格)序列,則這些字元及其後的所有字元都將被丟棄。這允許依賴項塊包含行內註釋。
- 丟棄剩餘文字的開頭和結尾處的空格。
- 如果行現在為空,則忽略它。
- 該行的內容現在必須是一個有效的 PEP 508 依賴項說明符。
行內註釋中 # 前後需要空格是為了區分它們與 PEP 508 URL 說明符(可能包含井號,但周圍沒有空格)的一部分。
消費者必須至少驗證所有依賴項是否以 PEP 508 中定義的name開頭,並且可以驗證所有依賴項是否完全符合 PEP 508。如果發現無效的說明符,它們必須以錯誤形式失敗。
示例
以下是一個包含嵌入式依賴項塊的指令碼示例
# In order to run, this script needs the following 3rd party libraries
#
# Script Dependencies:
# requests
# rich # Needed for the output
#
# # Not needed - just to show that fragments in URLs do not
# # get treated as comments
# pip @ https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686
import requests
from rich.pretty import pprint
resp = requests.get("https://peps.python.club.tw/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])
向後相容性
由於依賴項塊採用結構化註釋的形式,因此可以在不改變現有程式碼含義的情況下新增它們。
可能已經存在一個註釋,其形式與依賴項塊匹配。雖然“Script Dependencies”的標識標題文字旨在最大限度地減少此風險,但仍然有可能。
在極少數情況下,現有註釋可能被錯誤地解釋為依賴項塊,可以透過在程式碼更早的位置新增一個實際的依賴項塊(如果指令碼沒有依賴項,則該塊可以為空)來解決。
安全隱患
如果使用自動安裝依賴項的工具執行包含依賴項塊的指令碼,這可能會導致任意程式碼被下載並安裝到使用者的環境中。
這裡的風險是使用該工具執行指令碼的功能的一部分,因此應該由該工具本身解決。此 PEP 引入的唯一額外風險是,當執行一個不受信任的帶有依賴項塊的指令碼時,可能會安裝一個潛在惡意的依賴項。此風險透過在執行程式碼之前審查程式碼的正常良好實踐來解決。
如何教授此內容
該格式旨在接近開發人員在說明性註釋中指定指令碼依賴項的方式。必需的結構是故意最小化的,以便易於學習格式規則。
使用者需要知道如何編寫 Python 依賴項說明符。這由 PEP 508 涵蓋,但對於簡單的示例(這對於沒有經驗的使用者來說預計是常態),語法只是一個包名,或者是一個名稱和版本限制,這是相當容易理解的語法。
使用者還將知道如何使用解釋依賴項資料的工具執行指令碼。這不在此 PEP 的範圍內,因為此類工具如何使用由該工具自行負責記錄。
請注意,核心 Python 直譯器不解釋依賴項塊。這可能是一個讓初學者感到困惑的地方,他們嘗試執行 python some_script.py 並且不明白為什麼會失敗。然而,這與當前的現狀沒有區別,在現狀下,執行一個缺少依賴項的指令碼也會產生錯誤。
總的來說,我們假設,如果初學者收到一個帶有依賴項的指令碼(無論是否在依賴項塊中指定),提供指令碼的人應該解釋如何執行該指令碼,如果這涉及使用指令碼執行器工具,則應予以說明。
建議
本節是非規範性的,僅描述使用依賴項塊時的“最佳實踐”。
雖然允許工具對需求進行最小程度的驗證,但實際上它們應該儘可能多地進行“健全性檢查”驗證,即使它們無法完全檢查 PEP 508 語法。這有助於確保未正確終止的依賴項塊能夠儘早報告。介於僅檢查需求是否以名稱開頭和完整 PEP 508 驗證之間的折衷方案是檢查裸露名稱,或名稱後跟可選空格,然後是 [(額外)、@(urlspec)、;(標記)或 (<!=>~(版本)之一。
一般來說,指令碼應該將依賴項塊放在檔案的頂部,緊隨任何 shebang 行之後,或緊隨指令碼文件字串之後。特別是,依賴項塊應始終放在檔案中的任何可執行程式碼之前。這使得人類讀者易於找到它。
參考實現
用 Python 實現此提案的程式碼相當直接,因此可以將其作為參考實現包含在此處。
import re
import tokenize
from packaging.requirements import Requirement
DEPENDENCY_BLOCK_MARKER = r"(?i)^#\s+script\s+dependencies:\s*$"
def read_dependency_block(filename):
# Use the tokenize module to handle any encoding declaration.
with tokenize.open(filename) as f:
# Skip lines until we reach a dependency block (OR EOF).
for line in f:
if re.match(DEPENDENCY_BLOCK_MARKER, line):
break
# Read dependency lines until we hit a line that doesn't
# start with #, or we are at EOF.
for line in f:
if not line.startswith("#"):
break
# Remove comments. An inline comment is introduced by
# a hash, which must be preceded and followed by a
# space.
line = line[1:].split(" # ", maxsplit=1)[0]
line = line.strip()
# Ignore empty lines
if not line:
continue
# Try to convert to a requirement. This will raise
# an error if the line is not a PEP 508 requirement
yield Requirement(line)
被拒絕的想法
為何不包含其他元資料?
此提案所解決的核心用例是識別獨立指令碼成功執行所需的依賴項。這是一個常見的實際問題,目前透過指令碼執行器工具使用特定於實現的儲存資料的方式來解決。標準化儲存格式透過不將指令碼繫結到特定執行器來提高互操作性。
雖然可以說其他形式的元資料可能在獨立指令碼中有用,但目前這種需求在很大程度上是理論上的。在實際操作中,指令碼要麼不使用其他元資料,要麼使用現有的、廣泛使用的(因此是事實上的標準)格式來儲存它們。例如,需要 README 風格文字的指令碼通常使用標準的 Python 模組文件字串,而需要宣告版本的指令碼則使用具有 __version__ 變數的常見約定。
在此 PEP 的討論中提出的一種情況是,能夠宣告指令碼執行所需的最低 Python 版本,類似於軟體包的 Requires-Python 核心元資料項。與包不同,指令碼通常只由一個使用者或在一個環境中執行,在這些環境中,多個 Python 版本是不常見的。因此,這種元資料的需求在指令碼情況下不那麼關鍵。作為進一步證據,目前可用的兩個主要指令碼執行器 pipx 和 pip-run 並沒有提供在此類指令碼中包含此資料的手段。
建立標準的“元資料容器”格式可以統一各種方法,但實際上並不需要統一,而且中斷可能會延遲採用,或者更有可能導致指令碼作者忽略該標準。
因此,本提案選擇僅關注一個有明確需求、不存在現有標準或通用實踐的用例。
為何不每行使用一個標記?
除了使用帶有標題的註釋塊之外,另一種可能性是在每行使用一個標記,類似於
# Script-Dependency: requests
# Script-Dependency: click
雖然這使得單獨解析行更容易,但它存在一些問題。首先是它相當冗長,可讀性較差。這明顯受所選關鍵字的影響,但所有建議的選項(在作者看來)都比塊註釋形式的可讀性差。
更重要的是,這種形式故意使得無法要求所有依賴項說明符都集中在一個塊中。因此,人類讀者在不仔細檢查整個檔案的情況下,無法確定他們已經識別了所有依賴項。有關此問題的更多討論,請參閱下面的問題“為何不允許使用多個依賴項塊併合並它們?”。
最後,正如參考實現所示,解析“註釋塊”形式在實踐中並不比解析此形式困難得多。
為何不使用一種獨特的註釋形式來表示依賴項塊?
本提案的一個早期版本使用了 ## 來標識依賴項塊。然而,不幸的是,flake8 linter 實現了一條規則,要求註釋必須在開頭的 # 符號後有一個空格。雖然 PEP 作者認為該規則是錯誤的,但它是預設啟用的,因此在遇到依賴項塊時會導致檢查失敗。
此外,black 格式化程式雖然允許 ## 形式,但對於大多數其他形式的註釋會在 # 之後新增空格。這意味著如果我們選擇 #% 等替代方案,自動重新格式化將損壞依賴項塊。包含空格的形式,例如 # # 是可能的,但對於普通使用者來說不夠自然(省略空格是一個顯而易見的錯誤)。
雖然有可能更改 linter 和格式化程式以識別新標準,但擁有專用字首的好處似乎不足以證明遷移成本或使用者可能使用舊工具的風險是值得的。
為何不允許使用多個依賴項塊併合並它們?
因為人類讀者很容易忽略第二個依賴項塊的事實。這可能導致指令碼執行器意外下載額外的包,甚至可能是一種將惡意包偷渡到使用者機器上的方法(透過將第二個依賴項塊“隱藏”在指令碼正文中)。
雖然“不執行不受信任的程式碼”的原則在此處適用,但其好處不足以彌補風險。
為何不使用更標準的資料格式(例如 TOML)?
首先,另一種格式的唯一實際選擇是 TOML。Python 打包已將 TOML 標準化用於結構化資料,使用不同的格式,如 YAML 或 JSON,將增加複雜性和混亂,而沒有實際的好處。
因此,問題本質上是“為什麼不使用 TOML?”
“依賴項塊”格式背後的關鍵思想是定義一種在指令碼中自然地讀取為註釋的內容。依賴項資料對工具和人類讀者都有用,因此擁有人類可讀的格式是有益的。另一方面,TOML 必然有其自身的語法,這會分散人們對底層資料的注意力。
重要的是要記住,編寫 Python 指令碼的開發人員通常不是 Python 或 Python 打包方面的專家。他們通常是系統管理員或資料分析師,可能只是將 Python 用作“更好的批處理檔案”。對於這些使用者來說,TOML 格式極有可能不熟悉,其語法對他們來說是晦澀的,並且不太直觀。這些開發人員可能會從 Stack Overflow 等來源複製程式碼說明符,而沒有真正理解它們。將這種要求嵌入 TOML 結構中是一種額外的複雜性——重要的是要記住,目標是讓這些使用者輕鬆使用第三方庫。
此外,TOML 本質上是一種靈活的格式,旨在支援非常通用的資料結構。在 TOML 中編寫簡單字串列表有許多方法,對於沒有經驗的使用者來說,不清楚使用哪種形式。
另一個潛在的問題是,使用通用的 TOML 解析器有時會導致可衡量的效能開銷。啟動時間通常被認為是執行小型指令碼時的一個問題,因此這對於以高效能為目標的指令碼執行器來說可能是一個問題。
最後,將會有期望寫入依賴項資料的工具——例如,一個 IDE,它有一個功能可以在您引用庫函式時自動新增 import 和依賴項說明符。雖然存在允許編輯 TOML 資料的庫,但它們並不總是擅長保留使用者的佈局。即使存在能有效完成此工作的庫,期望所有工具都使用此類庫也是對支援此 PEP 的程式碼的重大負擔。
透過選擇一種簡單的、基於行的格式,不帶引號規則,依賴項資料易於閱讀(對人類和工具)且易於編寫。該格式不具備像 TOML 那樣的靈活性,但用例根本不需要那種靈活性。
為何不使用(可能受限的)Python 語法?
這通常涉及將依賴項儲存為具有約定名稱的(執行時)列表變數,例如
__requires__ = [
"requests",
"click",
]
其他建議包括靜態多行字串,或將依賴項包含在指令碼的文件字串中。
此提案最顯著的問題是它要求所有依賴項資料的使用者都實現 Python 解析器。即使語法受到限制,指令碼的其餘部分也將使用完整的 Python 語法,並且嘗試定義一個可以在與周圍程式碼隔離的情況下成功解析的語法可能會非常困難且容易出錯。
此外,Python 的語法在每個版本中都會發生變化。如果提取依賴項資料需要 Python 解析器,則解析器需要知道指令碼是為哪個 Python 版本編寫的,而通用工具擁有能夠處理多個 Python 版本的解析器的開銷是無法承受的。
即使上述問題得到解決,該格式也會給人一種資料可以在執行時更改的印象。然而,通常並非如此,試圖這樣做的程式碼將遇到意外和令人困惑的行為。
最後,沒有證據表明在執行時提供依賴項資料具有任何實際用途。如果發現此類用途,透過解析源來獲取資料很簡單——read_dependency_block(__file__)。
不過,值得注意的是,pip-run 工具確實實現了(此方法的擴充套件形式)。關於 pip-run 設計的進一步討論可在專案的問題跟蹤器上找到。
為何不將 pyproject.toml 檔案嵌入到指令碼中?
首先,pyproject.toml 是基於 TOML 的格式,因此之前圍繞 TOML 作為格式的所有顧慮都適用。然而,pyproject.toml 是 Python 打包所使用的標準,重用現有標準是一個合理的建議,值得單獨解決。
第一個問題是,該建議很少意味著支援指令碼的全部 pyproject.toml。指令碼不打算“構建”成任何型別的可分發構件,如 wheel(關於這一點,請參見下文)。因此,pyproject.toml 的 [build-system] 部分意義不大。雖然 pyproject.toml 的工具特定部分可能對指令碼有用,但尚不清楚像 ruff 這樣的工具是否會希望以這種方式支援每個檔案配置,這會導致使用者期望它工作但它不起作用時產生混亂。此外,這種工具特定的配置對於大型專案中的單個檔案同樣有用,因此我們必須考慮將 pyproject.toml 嵌入到具有自己 pyproject.toml 的大型專案中的單個檔案中意味著什麼。
此外,pyproject.toml 目前側重於將要構建成 wheel 的專案。正在進行討論,關於如何使用 pyproject.toml 來處理不打算構建為 wheel 的專案,並且在這些問題得到解決之前(這可能需要一些 PEP),討論將 pyproject.toml 嵌入到指令碼中似乎還為時過早,指令碼絕對不打算以這種方式構建和分發。
因此,結論是(在某些情況下明確陳述,在其他情況下不那麼明確)本提案旨在嵌入部分 pyproject.toml。通常是來自 PEP 621 的 [project] 部分,甚至是該部分中的 dependencies 項。
此時,第一個問題是,將提案框架化為“嵌入 pyproject.toml”,我們將鼓勵前面段落討論的混淆——開發人員將期望 pyproject.toml 的全部功能,並且在存在差異和限制時感到困惑。因此,最好將此建議視為使用嵌入式 TOML 格式的建議,但專門重用了 pyproject.toml 特定部分的結構。問題就變成了我們如何描述該結構,而不引起熟悉 pyproject.toml 的人的混淆。如果我們引用 pyproject.toml 進行描述,那麼連結仍然存在。但如果我們單獨描述它,人們會因該結構“相似但不同”的性質而感到困惑。
同樣重要的是要記住,此提案的目標受眾是僅將 Python 用作“更好的批處理檔案”解決方案的開發人員。這些開發人員通常不熟悉 Python 打包及其約定,並且常常是對打包解決方案的“複雜性”和“難度”最持批評態度的人。因此,基於現有解決方案的提案很可能不受該受眾歡迎,並且很容易導致人們繼續使用現有的臨時解決方案,而忽略了旨在讓他們生活更輕鬆的標準。
為何不從 import 語句推斷需求?
該想法是自動識別原始檔中的 import 語句,並將它們轉換為需求列表。
然而,這由於幾個原因而不可行。首先,上面關於使語法易於解析的必要性的觀點,對於所有 Python 版本,以及對於其他語言編寫的工具,同樣適用。
其次,PyPI 和其他符合 Simple Repository API 的包儲存庫不提供從匯入的模組名解析包名的機制(另請參閱相關的討論)。
第三,即使儲存庫提供了此資訊,相同的匯入名也可能對應 PyPI 上的多個包。有人可能會爭辯說,只有當有多個專案提供相同的匯入名時,才需要區分想要哪個包。然而,這很容易讓任何人無意中或惡意地破壞正在工作的指令碼,透過上傳一個提供與現有專案相同匯入名的包到 PyPI。另一種選擇是,在候選者中,選擇第一個在索引中註冊的包,這在流行的包與現有的晦澀包具有相同匯入名的情況下會令人困惑,如果現有包是惡意上傳的,其匯入名足夠通用,很可能被重複使用,那麼甚至是有害的。
一個相關的想法是將需求作為註釋附加到 import 語句,而不是將它們收集在一個塊中,其語法如下:
import numpy as np # requires: numpy
import rich # requires: rich
這仍然存在解析困難。此外,在多行匯入的情況下,註釋的位置是模稜兩可的,並且可能看起來很醜。
from PyQt5.QtWidgets import (
QCheckBox, QComboBox, QDialog, QDialogButtonBox,
QGridLayout, QLabel, QSpinBox, QTextEdit
) # requires: PyQt5
此外,這種語法在所有情況下都不能如直觀預期那樣執行。考慮
import platform
if platform.system() == "Windows":
import pywin32 # requires: pywin32
在這裡,使用者的意圖是該包僅在 Windows 上需要,但指令碼執行器無法理解這一點(正確的方法是 requires: pywin32 ; sys_platform == 'win32')。
(感謝 Jean Abou-Samra 對此點的清晰討論)
為何不直接在執行時管理環境?
執行帶有依賴項的指令碼的另一種方法是簡單地在執行時管理這些依賴項。這可以透過使用使包可用的庫來完成。有許多選項可以實現此類庫,例如直接將它們安裝到使用者的環境中,或透過修改 sys.path 使它們可以從本地快取中獲得。
這些方法與此 PEP 並不衝突。API,如
env_mgr.install("rich")
env_mgr.install("click")
import rich
import click
...
當然是可行的。然而,這樣的庫可以在不需要任何新標準的情況下編寫,據 PEP 作者所知,這種情況尚未發生。這表明這種方法不像最初看起來那麼有吸引力。還有使 env_mgr 庫首先可用的引導問題。最後,這種方法實際上並沒有提供任何互操作性優勢,因為它不使用標準形式的依賴項列表,因此其他工具無法訪問資料。
無論如何,這樣的庫仍然可以從此 PEP 中受益,因為它可以包含讀取指令碼依賴項塊中要安裝的包的 API。這將提供相同的功能,同時允許與其他支援此規範的工具進行互操作。
# Script Dependencies:
# rich
# click
env_mgr.install_dependencies(__file__)
import rich
import click
...
為何不直接設定一個帶有 pyproject.toml 的 Python 專案?
同樣,一個關鍵問題是此提案的目標受眾是編寫不打算分發的指令碼的人。有時指令碼會被“共享”,但這比“分發”要不正式得多——通常涉及透過帶有如何執行它的書面說明的電子郵件傳送指令碼,或將連結傳遞給 gist。
期望這些使用者學習 Python 打包的複雜性是一個重大的複雜性提升,幾乎肯定會給人一種“Python 對指令碼來說太難了”的印象。
此外,如果這裡的期望是 pyproject.toml 將以某種方式設計用於就地執行指令碼,那麼這是標準的一項新功能,目前尚不存在。至少,在關於使用 pyproject.toml 處理不打算分發為 wheels 的專案的當前 Discourse 討論得到解決(這可能需要一些 PEP),否則這不是一個合理的建議。即使如此,它也未能解決“在 gist 或電子郵件中傳送指令碼”的用例。
為何不使用 requirements 檔案來管理依賴項?
將您的需求放在 requirements 檔案中,不需要 PEP。您可以立即這樣做,實際上很多臨時解決方案可能就是這樣做的。然而,沒有標準,就無法知道如何定位指令碼的依賴項資料。此外,requirements 檔案格式是 pip 特有的,因此依賴它的工具依賴於 pip 的實現細節。
因此,為了建立一個標準,需要兩件事
- 標準化 requirements 檔案格式的替代方案。
- 關於如何定位給定指令碼的 requirements 檔案的標準。
第一項是一項重大工程。它已經多次討論過,但到目前為止還沒有人嘗試過實際去做。最可能的方法是為當前使用 requirements 檔案解決的各個用例制定標準。一種選擇是此 PEP 簡單地定義一種新檔案格式,該格式只是一個包含 PEP 508 要求的文字檔案,每行一個。這樣就只剩下如何定位該檔案的問題了。
這裡的“明顯”解決方案是做類似的事情,將檔名命名為與指令碼相同,但帶有 .reqs 副檔名(或類似的)。然而,這仍然需要兩個檔案,而目前只需要一個檔案,因此不符合“更好的批處理檔案”模型(shell 指令碼和批處理檔案通常是獨立的)。這要求開發人員記住將這兩個檔案放在一起,而這並不總是可能的。例如,系統管理策略可能要求某個目錄中的所有檔案都必須可執行(例如,Linux 檔案系統標準要求 /usr/bin)。而一些共享指令碼的方法(例如,將其釋出在像 Github gist 這樣的文字檔案共享服務上,或企業內部網)可能不允許從指令碼的位置推匯出關聯的 requirements 檔案的位置(像 pipx 這樣的工具支援直接從 URL 執行指令碼,因此“下載並解壓指令碼及其依賴項的 zip 包”可能不是一個適用的要求)。
但本質上,這裡的問題是明確規定的要求是該格式支援將依賴項資料儲存在指令碼檔案本身中。不這樣做(不儲存在指令碼中)的解決方案只是忽略了這一要求。
指令碼是否應該能夠指定包索引?
依賴項元資料是關於程式碼依賴於哪個包,而不是從哪裡獲取該包。指令碼的元資料與分發包的元資料(在 pyproject.toml 中定義)之間沒有區別。在這兩種情況下,依賴項都以“抽象”形式給出,而沒有指定如何獲取它們。
當然,一些使用依賴項資訊的工具可能需要定位具體的依賴項工件——例如,如果它們期望建立一個包含這些依賴項的環境。但它們選擇如何做到這一點將與工具的 UI 緊密相關,本 PEP 不試圖規定工具的 UI。
關於這一點,以及特別是 pip-run 工具所做的 UI 選擇,在之前提到的 pip-run 問題中有更多討論。
本地依賴項怎麼辦?
這些可以透過新增依賴項的位置到 sys.path 來處理,而無需特殊的元資料和工具。此 PEP 對於這種情況根本不需要。另一方面,如果“本地依賴項”是實際已本地釋出的發行版,則它們可以像通常一樣用 PEP 508 要求指定,並透過在執行工具時使用工具的 UI 來指定本地包索引。
未解決的問題
此時無。
版權
本文件置於公共領域或 CC0-1.0-Universal 許可證下,以更寬鬆者為準。
來源:https://github.com/python/peps/blob/main/peps/pep-0722.rst