PEP 662 – 透過虛擬輪實現可編輯安裝
- 作者:
- Bernát Gábor <gaborjbernat at gmail.com>
- 贊助人:
- Brett Cannon <brett at python.org>
- 討論於:
- Discourse 討論串
- 狀態:
- 已否決 (Rejected)
- 類型:
- 標準軌跡 (Standards Track)
- 主題:
- 套件封裝 (Packaging)
- 建立日期:
- 2021年5月28日
- 公告歷史:
- 決議:
- Discourse 討論串
摘要
本文件描述了對建構後端與前端通訊的擴充(由 PEP 517 引入),以透過引入虛擬輪的方式,允許專案以可編輯模式安裝。
動機
在開發期間,許多 Python 使用者偏好安裝他們的函式庫,以便對底層原始碼和資源的變更,能夠在後續的直譯器呼叫中自動反映,而無需額外的安裝步驟。此模式通常稱為「開發模式」或「可編輯安裝」。目前,沒有標準化的方法可以實現這一點,因為 PEP 517 由於實際觀察行為的複雜性而明確將其排除在外。
目前,使用者若要獲得此行為,會執行以下其中一種方式:
- 僅針對 Python 程式碼,透過將相關原始碼目錄加入
sys.path(可透過命令列介面,經由PYTHONPATH環境變數配置)。請注意在此情況下,使用者必須自行安裝專案依賴項,並且不會產生進入點或專案中繼資料。 - setuptools 提供了 setup.py develop 機制:它會安裝一個
pth檔案,該檔案會在直譯器啟動時將專案根目錄注入sys.path,產生專案中繼資料,並也安裝專案依賴項。pip 透過 pip install -e 命令列介面公開呼叫此機制。 - flit 提供了 flit install –symlink 命令,它會將專案檔案符號連結到直譯器的
purelib資料夾中,產生專案中繼資料,並也安裝依賴項。請注意,這也支援資源檔案。
這些範例顯示,可編輯安裝可以透過多種方式實現,目前並沒有標準方法。此外,目前尚不清楚實現和定義何謂可編輯安裝是誰的責任:
- 允許建構後端定義並具現化它,
- 允許建構前端定義並具現化它,
- 從所有可能選項中明確定義並標準化一種方法。
本 PEP 的作者認為在此沒有一體適用的解決方案,每種實現可編輯效果的方法都有其優點和缺點。因此,本 PEP 拒絕選項三,因為社群不太可能同意單一解決方案。此外,前端或建構後端應該擁有此責任的問題仍然存在。PEP 660 建議由建構後端來負責,而目前的 PEP 主要建議由前端負責,但如果後端希望如此,仍然允許其掌握控制權。
原理
PEP 517 推遲了「可編輯安裝」,因為這會進一步延遲其採用,並且對於如何實現可編輯安裝沒有達成共識。由於 setuptools 和 pip 專案的普及,現狀得以維持,後端可以透過提供一個 setup.py develop 實作來實現可編輯模式,使用者可以透過 pip install -e 觸發該實作。透過在建構後端和前端之間定義一個可編輯介面,我們可以消除 setup.py 檔案及其目前的通訊方法。
術語與目標
本 PEP 旨在明確劃分前端和後端的角色,並賦予每個開發者最大的能力,為其使用者提供有價值的特性。在此提案中,後端的角色是為可編輯安裝準備專案,然後向前端提供足夠的資訊,以便前端可以具現化並實施可編輯安裝。
後端提供給前端的資訊是一個遵循 PEP 427 中現有規範的 wheel。關於壓縮檔本身的 wheel 中繼資料({distribution}-{version}.dist-info/WHEEL)也必須包含鍵 Editable,其值為 true。
然而,它必須提供一個 editable.json 檔案(在 wheel 的根目錄中),而不是在 wheel 內提供專案檔案,該檔案定義了要由前端公開的檔案。此檔案的內容被定義為一個映射,將絕對原始碼樹路徑映射到方案映射中相對目標直譯器目標路徑。
滿足前兩段說明的 wheel 是一個虛擬輪。前端的角色是接收虛擬輪並以可編輯模式安裝專案。它實現此目的的方式完全取決於前端,並被視為實作細節。
可編輯安裝模式表示正在安裝的專案原始碼在本地目錄中可用。一旦專案以可編輯模式安裝,對本地原始碼樹中專案程式碼的某些變更將會生效,而無需新的安裝步驟。至少,對安裝時存在的非產生檔案文本的變更,應在後續匯入套件時反映出來。
某些類型的變更,例如新增或修改進入點或新的依賴項,需要新的安裝步驟才能生效。這些變更通常在建構後端配置檔案中進行(例如 pyproject.toml)。此要求與一般使用者預期一致,即此類修改僅在重新安裝後才會生效。
儘管使用者預期可編輯安裝與標準安裝的行為相同,這可能並非總是可行,並可能與其他使用者預期相衝突。取決於前端如何實作可編輯模式,可能會出現一些差異,例如額外檔案的存在(與典型安裝相比),無論是在原始碼樹中還是直譯器的安裝路徑中。
前端應努力將可編輯安裝與標準安裝之間的行為差異降至最低,並記錄已知的差異。
供參考,非可編輯安裝的運作方式如下:
機制
本 PEP 增加了兩個可選的掛鉤到 PEP 517 後端介面。其中一個掛鉤用於指定可編輯安裝的建構依賴項。另一個掛鉤則透過建構前端返回前端創建可編輯安裝所需的必要資訊。
get_requires_for_build_editable
def get_requires_for_build_editable(config_settings=None):
...
此掛鉤必須返回一個額外的字串序列,其中包含超出 pyproject.toml 檔案中指定內容的 PEP 508 依賴項規範。前端必須確保這些依賴項在呼叫 build_editable 掛鉤的建構環境中可用。
如果未定義,預設實作等同於返回 []。
prepare_metadata_for_build_editable
def prepare_metadata_for_build_editable(metadata_directory, config_settings=None):
...
必須在指定的 metadata_directory 內創建一個包含 wheel 中繼資料的 .dist-info 目錄(即,創建一個像 {metadata_directory}/{package}-{version}.dist-info/ 這樣的目錄)。此目錄必須是 wheel 規範中定義的有效 .dist-info 目錄,除了它不需要包含 RECORD 或簽名。此掛鉤也可以在此目錄內創建其他檔案,建構前端必須保留,但否則忽略此類檔案;這裡的意圖是,在中繼資料依賴於建構時決策的情況下,建構後端可能需要以某種方便的格式記錄這些決策,以便在實際的 wheel 建構步驟中重複使用。
這必須以 Unicode 字串形式回傳所建立 .dist-info 目錄的基底名稱(而非完整路徑)。
如果建構前端需要此資訊且此方法未定義,它應該呼叫 build_editable 並直接查看結果中繼資料。
build_editable
def build_editable(self, wheel_directory, config_settings=None,
metadata_directory=None):
...
必須建構一個 .whl 檔案,並將其放置在指定的 wheel_directory 中。它必須返回其創建的 .whl 檔案的基本名稱(而非完整路徑),作為一個 Unicode 字串。該 wheel 檔案必須是術語部分中定義的虛擬輪類型。
如果建構前端先前已呼叫 prepare_metadata_for_build_editable,並且依賴於此呼叫產生的 wheel 具有與先前呼叫相符的中繼資料,那麼它應該將創建的 .dist-info 目錄路徑作為 metadata_directory 參數提供。如果提供了此參數,則 build_editable 必須產生具有相同中繼資料的 wheel。建構前端傳入的目錄必須與 prepare_metadata_for_build_editable 創建的目錄相同,包括它創建的任何無法識別的檔案。
未提供 prepare_metadata_for_build_editable 掛鉤的後端,可以默默地忽略 build_editable 的 metadata_directory 參數,否則當它設置為 None 以外的任何值時,會引發異常。
原始碼目錄可能是唯讀的,在此類情況下,後端可能會引發一個錯誤,前端可以將其顯示給使用者。後端可以將中間產物儲存在快取位置或暫存目錄中。任何快取的存在與否,不應對建構的最終結果造成實質性差異。
editable.json 的內容必須符合以下 JSON 結構描述:
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://pypa.io/editables.json",
"type": "object",
"title": "Virtual wheel editable schema.",
"required": ["version", "scheme"],
"properties": {
"version": {
"$id": "#/properties/version",
"type": "integer",
"minimum": 1,
"maximum": 1,
"title": "The version of the schema."
},
"scheme": {
"$id": "#/properties/scheme",
"type": "object",
"title": "Files to expose.",
"required": ["purelib", "platlib", "data", "headers", "scripts"],
"properties": {
"purelib": { "$ref": "#/$defs/mapping" },
"platlib": { "$ref": "#/$defs/mapping" },
"data": { "$ref": "#/$defs/mapping" },
"headers": { "$ref": "#/$defs/mapping" },
"scripts": { "$ref": "#/$defs/mapping" }
},
"additionalProperties": true
}
},
"additionalProperties": true,
"$defs": {
"mapping": {
"type": "object",
"description": "A mapping of source to target paths. The source is absolute path, the destination is relative path.",
"additionalProperties": true
}
}
}
例如
{
"version": 1,
"scheme": {
"purelib": {"/src/tree/a.py": "tree/a.py"},
"platlib": {},
"data": {"/src/tree/py.typed": "tree/py.typed"},
"headers": {},
"scripts": {}
}
}
方案路徑將專案原始碼絕對路徑映射到目標目錄相對路徑。我們允許後端透過使用映射來更改專案布局,使其從專案原始碼目錄變為直譯器將看到的內容。
例如,如果後端返回 "purelib": {"/me/project/src": ""},這意味著將 /me/project/src 中的所有檔案和模組公開到目標直譯器內 purelib 路徑的根目錄。
建構前端的要求
建構前端負責設定環境,供建構後端產生虛擬輪。PEP 517 對於建構 wheel 掛鉤的所有建議也適用於此。
前端要求
前端必須完全按照 PEP 427 中定義的方式安裝虛擬輪。此外,它還負責安裝 editable.json 檔案中定義的檔案。它執行此操作的方式由前端決定,並鼓勵前端向使用者準確溝通所選擇的方法以及該解決方案將有哪些限制。
前端必須在已安裝發行版的 .dist-info 目錄中創建一個 direct_url.json 檔案,遵循 PEP 610。url 值必須是一個指向專案目錄(即,包含 pyproject.toml 的目錄)的 file:// URL,並且 dir_info 值必須是 {'editable': true}。
當以可編輯模式安裝時,前端可以依賴 prepare_metadata_for_build_editable 掛鉤。
如果前端斷定它無法根據建構後端提供的資訊實現可編輯安裝,它應該失敗並引發一個錯誤,向使用者闡明原因。
前端可以實作一個或多個可編輯安裝機制,並可以讓使用者選擇最適合其使用情境的一個。例如,pip 可以添加一個可編輯模式旗標,並允許使用者在 pth 檔案或符號連結之間進行選擇(例如 pip install -e . --editable-mode=pth 與 pip install -e . --editable-mode=symlink)。
可編輯實作範例
為了展示本 PEP 的潛在用途,我們現在將提出幾個案例研究。請注意,提供的解決方案純粹用於說明目的,對於前端/後端不具規範性。
將原始碼樹原封不動地加入直譯器
這是最簡單的實作之一,它將原始碼樹原封不動地加入直譯器的方案路徑中,虛擬輪中的 editable.json 可能看起來像:
{
{"version": 1, "scheme": {"purelib": {"<project dir>": "<project dir>"}}}
}
前端隨後可以選擇:
- 在目標直譯器啟動時,將原始碼目錄加入其
sys.path。這是透過在目標直譯器的purelib資料夾中創建一個pth檔案來完成的。setuptools 目前就是這樣做的,也是 pip install -e 的轉換方式。這種解決方案快速且跨平台兼容。然而,這會將整個原始碼樹放到系統上,可能會暴露在標準安裝情況下不可用的模組。 - 符號連結(Symlink)資料夾或其中的個別檔案。這是 flit 透過其 flit install –symlink 命令所採用的方法。這種解決方案要求目前平台支援符號連結。儘管如此,它仍然允許符號連結個別檔案,這可以解決包含應從原始碼樹中排除的檔案的問題。
使用自訂匯入器
為了在建構後端和目標直譯器之間實現更穩健和更動態的協作,我們可以利用允許註冊自訂匯入器的匯入系統。有關更多詳細資訊,請參閱 PEP 302,並以 editables 作為範例。後端可以在可編輯建構期間產生一個新的匯入器(或將其作為額外依賴項安裝),並透過添加一個 pth 檔案在直譯器啟動時註冊它。
{
"version": 1,
"scheme": {
"purelib": {
"<project dir>/.editable/_register_importer.pth": "<project dir>/_register_importer.pth".
"<project dir>/.editable/_editable_importer.py": "<project dir>/_editable_importer.py"
}
}
}
}
這裡的後端註冊了一個掛鉤,每當匯入新模組時就會呼叫,從而實現動態和按需功能。這可能有用的一些潛在用例:
- 公開一個原始碼資料夾,但遵守模組排除規則:後端可能會產生一個匯入掛鉤,該掛鉤會在允許原始碼檔案載入器在原始碼目錄中發現檔案之前,查閱排除表。
- 對於一個專案,假設有兩個模組,
A.py和B.py。這些是原始碼目錄中的兩個獨立檔案;然而,在建構 wheel 時,它們會合併成一個大型檔案project.py。在這種情況下,有了本 PEP,後端可以產生一個匯入掛鉤,該掛鉤會在匯入時讀取原始碼檔案並將它們合併到記憶體中,然後再將其具現化為模組。 - 自動更新過時的 C 擴充功能:後端可能會產生一個匯入掛鉤,該掛鉤會檢查 C 擴充功能原始碼檔案的最後修改時間戳。如果它比目前的 C 擴充功能二進位檔更新,則在匯入之前透過呼叫編譯器觸發更新。
遭否決的想法
本 PEP 與 PEP 660 競爭並拒絕了該提案,因為我們認為實現可編輯安裝的機制應該在前端而不是建構後端。此外,這種方法允許生態系統使用替代方法來實現可編輯安裝效果(例如,在 sys.path 上插入路徑或符號連結,而不是僅僅暗示該 PEP 描述的後端鬆散 wheel 模式)。
值得注意的是,PEP 660 不允許使用符號連結來公開程式碼和資料檔案,除非同時擴充 wheel 檔案標準以支援符號連結。目前尚不清楚如何擴充 wheel 格式以支援不指向 wheel 本身內部檔案,而是僅指向本地磁碟上可用檔案的符號連結。值得注意的是,後端本身(或後端生成的程式碼)不得生成這些符號連結(例如,在直譯器啟動時),因為這會與前端記錄需要解除安裝哪些檔案的方式衝突。
最後,PEP 660 僅支援 purelib 和 platlib 檔案。它特意避免支援 wheel 格式支援的其他類型資訊:include、data 和 scripts。透過這種方式,前端可以透過符號連結機制盡力支援這些(儘管此功能並非普遍可用 — 在 Windows 上需要啟用)。我們認為為這些檔案類型添加盡力支援是有益的,而不是完全排除支援它們的可能性。
版權
本文件已進入公有領域或遵循 CC0-1.0-Universal 授權,以較寬鬆者為準。
來源:https://github.com/python/peps/blob/main/peps/pep-0662.rst