PEP 662 – Editable Installs über virtuelle Wheels
- Autor:
- Bernát Gábor <gaborjbernat at gmail.com>
- Sponsor:
- Brett Cannon <brett at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Thema:
- Packaging
- Erstellt:
- 28-Mai 2021
- Post-History:
- Resolution:
- Discourse thread
Inhaltsverzeichnis
Zusammenfassung
Dieses Dokument beschreibt Erweiterungen für die Kommunikation zwischen Build-Backend und Frontend (wie eingeführt durch PEP 517), um Projekten die Installation im Editable-Modus durch die Einführung von virtuellen Wheels zu ermöglichen.
Motivation
Während der Entwicklung bevorzugen viele Python-Benutzer die Installation ihrer Bibliotheken so, dass Änderungen am zugrundeliegenden Quellcode und an den Ressourcen in nachfolgenden Interpreter-Aufrufen automatisch widergespiegelt werden, ohne einen zusätzlichen Installationsschritt. Dieser Modus wird üblicherweise als "Entwicklungsmodus" oder "Editable Installs" bezeichnet. Derzeit gibt es keine standardisierte Methode, um dies zu erreichen, da dies aufgrund der Komplexität der tatsächlich beobachteten Verhaltensweisen explizit von PEP 517 ausgeschlossen wurde.
Derzeit führen Benutzer, um dieses Verhalten zu erzielen, eines der folgenden durch:
- Nur für Python-Code, indem die relevanten Quellverzeichnisse zu
sys.pathhinzugefügt werden (konfigurierbar über die Kommandozeile mittels der UmgebungsvariablePYTHONPATH). Beachten Sie, dass in diesem Fall die Benutzer die Projekt-Abhängigkeiten selbst installieren müssen und keine Entry Points oder Projektmetadaten generiert werden. - setuptools bietet den setup.py develop Mechanismus: dieser installiert eine
pth-Datei, die den Projekt-Root beim Interpreter-Start insys.patheinfügt, die Projekt-Metadaten generiert und auch die Projekt-Abhängigkeiten installiert. pip stellt das Aufrufen dieses Mechanismus über die pip install -e Kommandozeilenschnittstelle bereit. - flit bietet den flit install –symlink Befehl, der die Projektdateien in den
purelib-Ordner des Interpreters symlinkt, die Projekt-Metadaten generiert und auch Abhängigkeiten installiert. Beachten Sie, dass dies auch die Unterstützung von Ressourcen-Dateien ermöglicht.
Wie diese Beispiele zeigen, kann eine Editable Installation auf verschiedene Arten erreicht werden und derzeit gibt es keine standardisierte Methode dafür. Darüber hinaus ist unklar, wessen Verantwortung es ist, eine Editable Installation zu erreichen und zu definieren.
- ermöglicht es dem Build-Backend, diese zu definieren und zu materialisieren,
- ermöglicht es dem Build-Frontend, diese zu definieren und zu materialisieren,
- definiert und standardisiert explizit eine Methode aus den möglichen Optionen.
Der Autor dieses PEP ist der Meinung, dass es hier keine Einheitslösung gibt, jede Methode zur Erzielung des Editable-Effekts hat ihre Vor- und Nachteile. Daher lehnt dieses PEP Option drei ab, da es unwahrscheinlich ist, dass sich die Community auf eine einzige Lösung einigen wird. Darüber hinaus bleibt die Frage, ob das Frontend oder das Build-Backend für diese Verantwortung zuständig sein sollte. PEP 660 schlägt vor, dass das Build-Backend dafür verantwortlich ist, während das aktuelle PEP primär das Frontend vorschlägt, aber dem Backend dennoch erlaubt, die Kontrolle zu übernehmen, wenn es dies wünscht.
Begründung
PEP 517 hat "Editable Installs" aufgeschoben, da dies die Einführung weiter verzögert hätte und es keine Einigung darüber gab, wie Editable Installs erreicht werden sollten. Aufgrund der Popularität der Projekte setuptools und pip setzte sich der Status Quo durch, und das Backend konnte den Editable-Modus durch die Bereitstellung einer setup.py develop Implementierung erreichen, die der Benutzer über pip install -e auslösen konnte. Durch die Definition einer Editable-Schnittstelle zwischen dem Build-Backend und dem Frontend können wir die setup.py-Datei und ihre derzeitige Kommunikationsmethode eliminieren.
Terminologie und Ziele
Dieses PEP zielt darauf ab, die Rollen des Frontends und des Backends klar abzugrenzen und den Entwicklern beider Seiten maximale Möglichkeiten zu geben, wertvolle Funktionen für ihre Benutzer bereitzustellen. In diesem Vorschlag besteht die Rolle des Backends darin, das Projekt für eine Editable Installation vorzubereiten und dem Frontend dann genügend Informationen zur Verfügung zu stellen, damit das Frontend die Editable Installation manifestieren und erzwingen kann.
Die vom Backend an das Frontend gelieferten Informationen sind ein Wheel, das der bestehenden Spezifikation in PEP 427 folgt. Die Wheel-Metadaten über das Archiv selbst ({distribution}-{version}.dist-info/WHEEL) müssen auch den Schlüssel Editable mit dem Wert true enthalten.
Anstatt die Projektdateien innerhalb des Wheels bereitzustellen, muss es jedoch eine editable.json-Datei (auf der Root-Ebene des Wheels) enthalten, die die vom Frontend bereitzustellenden Dateien definiert. Der Inhalt dieser Datei ist als Zuordnung von absoluten Quellcodebaum-Pfaden zu relativen Ziel-Interpreter-Pfaden innerhalb einer Schema-Zuordnung formuliert.
Ein Wheel, das die beiden vorhergehenden Absätze erfüllt, ist ein virtuelles Wheel. Die Rolle des Frontends besteht darin, das virtuelle Wheel zu nehmen und das Projekt im Editable-Modus zu installieren. Wie es dies erreicht, liegt vollständig beim Frontend und gilt als Implementierungsdetail.
Der Editable-Installationsmodus impliziert, dass der Quellcode des installierten Projekts in einem lokalen Verzeichnis verfügbar ist. Sobald das Projekt im Editable-Modus installiert ist, werden einige Änderungen am Projektcode im lokalen Quellcodebaum wirksam, ohne dass ein neuer Installationsschritt erforderlich ist. Mindestens Änderungen am Text von Nicht-Generierungs-Dateien, die zum Zeitpunkt der Installation vorhanden waren, sollten beim anschließenden Import des Pakets reflektiert werden.
Einige Arten von Änderungen, wie das Hinzufügen oder Ändern von Entry Points oder neuen Abhängigkeiten, erfordern einen neuen Installationsschritt, um wirksam zu werden. Diese Änderungen werden typischerweise in Build-Backend-Konfigurationsdateien (wie pyproject.toml) vorgenommen. Diese Anforderung steht im Einklang mit der allgemeinen Erwartung der Benutzer, dass solche Modifikationen erst nach einer Neuinstallation wirksam werden.
Obwohl Benutzer erwarten, dass sich Editable Installs identisch zu Standardinstallationen verhalten, ist dies möglicherweise nicht immer möglich und steht im Konflikt mit anderen Erwartungen der Benutzer. Abhängig davon, wie ein Frontend den Editable-Modus implementiert, können einige Unterschiede sichtbar sein, wie z. B. das Vorhandensein zusätzlicher Dateien (im Vergleich zu einer typischen Installation), entweder im Quellcodebaum oder im Installationspfad des Interpreters.
Frontends sollten bestrebt sein, Unterschiede zwischen dem Verhalten von Editable- und Standardinstallationen zu minimieren und bekannte Unterschiede zu dokumentieren.
Zur Referenz funktioniert eine Nicht-Editable-Installation wie folgt:
- Der **Entwickler** verwendet ein Werkzeug, das wir hier als **Frontend** bezeichnen, um die Projektentwicklung voranzutreiben (z. B. pip). Wenn der Benutzer den Build und die Installation eines Projekts auslösen möchte, kommuniziert er mit dem **Frontend**.
- Das Frontend verwendet ein **Build-Frontend**, um den Build eines Wheels auszulösen (z. B. build). Das Build-Frontend verwendet PEP 517, um mit dem **Build-Backend** zu kommunizieren (z. B. setuptools) – wobei das Build-Backend in einer PEP 518-Umgebung installiert ist. Nach dem Aufruf gibt das Backend ein Wheel zurück.
- Das Frontend nimmt das Wheel und übergibt es an einen **Installer** (z. B. installer), um das Wheel in den Ziel-Python-Interpreter zu installieren.
Der Mechanismus
Dieses PEP fügt zwei optionale Hooks zur PEP 517-Backend-Schnittstelle hinzu. Einer der Hooks wird verwendet, um die Build-Abhängigkeiten einer Editable Installation anzugeben. Der andere Hook liefert über das Build-Frontend die notwendigen Informationen, die das Frontend zum Erstellen einer Editable Installation benötigt.
get_requires_for_build_editable
def get_requires_for_build_editable(config_settings=None):
...
Dieser Hook MUSS eine zusätzliche Sequenz von Strings zurückgeben, die PEP 508-Abhängigkeitsspezifikationen enthalten, zusätzlich zu denen, die in der pyproject.toml-Datei angegeben sind. Das Frontend muss sicherstellen, dass diese Abhängigkeiten in der Build-Umgebung verfügbar sind, in der der build_editable-Hook aufgerufen wird.
Wenn nicht definiert, ist die Standardimplementierung äquivalent zur Rückgabe von [].
prepare_metadata_for_build_editable
def prepare_metadata_for_build_editable(metadata_directory, config_settings=None):
...
Muss ein .dist-info-Verzeichnis erstellen, das Wheel-Metadaten enthält, innerhalb des angegebenen metadata_directory (d. h. ein Verzeichnis wie {metadata_directory}/{package}-{version}.dist-info/ erstellen). Dieses Verzeichnis MUSS ein gültiges .dist-info-Verzeichnis gemäß der Wheel-Spezifikation sein, mit der Ausnahme, dass es kein RECORD oder Signaturen enthalten muss. Der Hook KANN auch andere Dateien in dieses Verzeichnis erstellen, und ein Build-Frontend MUSS solche Dateien erhalten, aber ansonsten ignorieren; die Absicht hier ist, dass in Fällen, in denen die Metadaten von Build-Zeit-Entscheidungen abhängen, das Build-Backend diese Entscheidungen möglicherweise in einem geeigneten Format zur Wiederverwendung durch den eigentlichen Wheel-Build-Schritt aufzeichnen muss.
Dies muss den Basisnamen (nicht den vollständigen Pfad) des von ihm erstellten .dist-info-Verzeichnisses als Unicode-String zurückgeben.
Wenn ein Build-Frontend diese Informationen benötigt und die Methode nicht definiert ist, sollte es build_editable aufrufen und die resultierenden Metadaten direkt betrachten.
build_editable
def build_editable(self, wheel_directory, config_settings=None,
metadata_directory=None):
...
Muss eine .whl-Datei erstellen und diese im angegebenen wheel_directory platzieren. Es muss den Basisnamen (nicht den vollständigen Pfad) der von ihm erstellten .whl-Datei als Unicode-String zurückgeben. Die Wheel-Datei muss vom Typ virtuelles Wheel sein, wie im Terminologie-Abschnitt definiert.
Wenn das Build-Frontend zuvor prepare_metadata_for_build_editable aufgerufen hat und davon abhängt, dass das aus diesem Aufruf resultierende Wheel Metadaten hat, die mit diesem früheren Aufruf übereinstimmen, dann sollte es den Pfad zum erstellten .dist-info-Verzeichnis als Argument metadata_directory übergeben. Wenn dieses Argument übergeben wird, MUSS build_editable ein Wheel mit identischen Metadaten erstellen. Das vom Build-Frontend übergebene Verzeichnis muss identisch mit dem von prepare_metadata_for_build_editable erstellten Verzeichnis sein, einschließlich aller nicht erkannten Dateien, die es erstellt hat.
Backends, die den prepare_metadata_for_build_editable-Hook nicht bereitstellen, können entweder den Parameter metadata_directory für build_editable stillschweigend ignorieren oder eine Ausnahme auslösen, wenn er etwas anderes als None ist.
Das Quellcodeverzeichnis kann schreibgeschützt sein, in solchen Fällen kann das Backend einen Fehler auslösen, den das Frontend dem Benutzer anzeigen kann. Das Backend kann Zwischenartefakte in Cache-Speicherorten oder temporären Verzeichnissen speichern. Das Vorhandensein oder Fehlen von Caches darf keinen wesentlichen Unterschied zum Endergebnis des Builds machen.
Der Inhalt von editable.json MUSS gegen das folgende JSON-Schema verstoßen
{
"$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
}
}
}
Zum Beispiel:
{
"version": 1,
"scheme": {
"purelib": {"/src/tree/a.py": "tree/a.py"},
"platlib": {},
"data": {"/src/tree/py.typed": "tree/py.typed"},
"headers": {},
"scripts": {}
}
}
Die Schema-Pfade bilden absolute Projekt-Quellpfade auf relative Zielverzeichnispfade ab. Wir erlauben Backends, das Projektlayout vom Projektverzeichnis zu dem zu ändern, was der Interpreter sehen wird, indem wir die Zuordnung verwenden.
Wenn beispielsweise das Backend "purelib": {"/me/project/src": ""} zurückgibt, würde dies bedeuten, dass alle Dateien und Module innerhalb von /me/project/src am Stammverzeichnis des purelib-Pfads innerhalb des Zielinterpreters bereitgestellt werden.
Build-Frontend-Anforderungen
Das Build-Frontend ist für die Einrichtung der Umgebung verantwortlich, damit das Build-Backend das virtuelle Wheel generieren kann. Alle Empfehlungen aus PEP 517 für den build_wheel-Hook gelten auch hier.
Frontend-Anforderungen
Das Frontend muss das virtuelle Wheel genau so installieren, wie es in PEP 427 definiert ist. Darüber hinaus ist es dafür verantwortlich, auch die im editable.json definierten Dateien zu installieren. Die Art und Weise, wie es dies tut, bleibt dem Frontend überlassen, und es wird dem Frontend empfohlen, dem Benutzer genau die gewählte Methode und deren Einschränkungen mitzuteilen.
Das Frontend muss eine direct_url.json-Datei im .dist-info-Verzeichnis der installierten Distribution erstellen, konform mit PEP 610. Der url-Wert muss eine file://-URL sein, die auf das Projektverzeichnis (d. h. das Verzeichnis, das pyproject.toml enthält) zeigt, und der dir_info-Wert muss {'editable': true} sein.
Das Frontend kann sich auf den prepare_metadata_for_build_editable-Hook verlassen, wenn es im Editable-Modus installiert.
Wenn das Frontend zu dem Schluss kommt, dass es keine Editable Installation mit den vom Build-Backend bereitgestellten Informationen erreichen kann, sollte es fehlschlagen und eine Fehlermeldung ausgeben, um dem Benutzer klarzumachen, warum nicht.
Das Frontend kann eine oder mehrere Editable-Installationsmechanismen implementieren und dem Benutzer die Wahl überlassen, welche für den Anwendungsfall des Benutzers am optimalsten ist. Zum Beispiel könnte pip ein Flag für den Editable-Modus hinzufügen und dem Benutzer die Wahl zwischen pth-Dateien oder Symlinks überlassen (pip install -e . --editable-mode=pth vs. pip install -e . --editable-mode=symlink).
Beispielhafte Implementierungen für Editable Installs
Um zu zeigen, wie dieses PEP verwendet werden könnte, präsentieren wir nun einige Fallstudien. Beachten Sie, dass die angebotenen Lösungen rein zur Veranschaulichung dienen und für das Frontend/Backend nicht normativ sind.
Den Quellcodebaum direkt zum Interpreter hinzufügen
Dies ist eine der einfachsten Implementierungen, sie fügt den Quellcodebaum direkt zu den Schema-Pfaden des Interpreters hinzu. Die editable.json innerhalb des virtuellen Wheels könnte so aussehen:
{
{"version": 1, "scheme": {"purelib": {"<project dir>": "<project dir>"}}}
}
Das Frontend könnte dann entweder
- Das Quellcodeverzeichnis beim Start des Interpreters zu seinem
sys.pathhinzufügen. Dies geschieht durch Erstellung einerpth-Datei impurelib-Ordner des Zielinterpreters. setuptools tut dies heute und das ist es, was pip install -e übersetzt. Diese Lösung ist schnell und plattformübergreifend kompatibel. Allerdings wird der gesamte Quellcodebaum auf das System geladen, wodurch möglicherweise Module verfügbar sind, die in einem Standardinstallationsfall nicht verfügbar wären. - Das Verzeichnis oder die einzelnen Dateien darin symlinken. Diese Methode ist es, was flit über seinen flit install –symlink Befehl tut. Diese Lösung erfordert, dass die aktuelle Plattform Symlinks unterstützt. Dennoch ermöglicht sie potenziell das Symlinken einzelner Dateien, was das Problem der Einbeziehung von Dateien lösen könnte, die vom Quellcodebaum ausgeschlossen werden sollten.
Verwendung von benutzerdefinierten Import-Hooks
Für eine robustere und dynamischere Zusammenarbeit zwischen dem Build-Backend und dem Zielinterpreter können wir das Importsystem nutzen, das die Registrierung benutzerdefinierter Importer erlaubt. Weitere Einzelheiten finden Sie in PEP 302 und editables als Beispiel dafür. Das Backend kann während des Editable-Builds einen neuen Importer generieren (oder ihn als zusätzliche Abhängigkeit installieren) und ihn beim Interpreter-Start registrieren, indem es eine pth-Datei hinzufügt.
{
"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"
}
}
}
}
Das Backend hat hier einen Hook registriert, der aufgerufen wird, wenn ein neues Modul importiert wird, was dynamische und bedarfsgesteuerte Funktionalität ermöglicht. Mögliche Anwendungsfälle, bei denen dies nützlich ist:
- Ein Quellcodeverzeichnis bereitstellen, aber Modulausschlüsse berücksichtigen: Das Backend kann einen Import-Hook generieren, der die Ausschlussliste konsultiert, bevor ein Datei-Loader eine Datei im Quellcodeverzeichnis entdeckt.
- Für ein Projekt, es gebe zwei Module,
A.pyundB.py. Dies sind zwei separate Dateien im Quellcodeverzeichnis; beim Erstellen eines Wheels werden sie jedoch zu einer einzigen Mega-Dateiproject.pyzusammengeführt. In diesem Fall könnte das Backend mit diesem PEP einen Import-Hook generieren, der die Quelldateien beim Import liest und sie im Speicher zusammenführt, bevor sie als Modul materialisiert werden. - Automatische Aktualisierung veralteter C-Erweiterungen: Das Backend kann einen Import-Hook generieren, der den letzten Änderungszeitstempel einer C-Erweiterungs-Quelldatei prüft. Wenn dieser größer ist als die aktuelle C-Erweiterungs-Binärdatei, wird eine Aktualisierung ausgelöst, indem der Compiler vor dem Import aufgerufen wird.
Abgelehnte Ideen
Dieses PEP konkurriert mit PEP 660 und lehnt diesen Vorschlag ab, da wir der Meinung sind, dass der Mechanismus zur Erreichung einer Editable Installation beim Frontend und nicht beim Build-Backend liegen sollte. Darüber hinaus ermöglicht dieser Ansatz dem Ökosystem, alternative Mittel zur Erzielung des Editable-Installations-Effekts zu nutzen (z. B. das Einfügen von Pfaden in sys.path oder Symlinks anstelle der reinen Implikation des Loose-Wheel-Modus des in diesem PEP beschriebenen Backends).
Prominenterweise erlaubt PEP 660 nicht die Verwendung von Symlinks zur Bereitstellung von Code und Datendateien, ohne auch den Wheel-Dateistandard um die Unterstützung von Symlinks zu erweitern. Es ist unklar, wie das Wheel-Format erweitert werden könnte, um Symlinks zu unterstützen, die nicht auf Dateien innerhalb des Wheels selbst verweisen, sondern auf Dateien, die nur auf der lokalen Festplatte verfügbar sind. Es ist wichtig zu beachten, dass das Backend selbst (oder vom Backend generierter Code) diese Symlinks nicht generieren darf (z. B. beim Interpreter-Start), da dies mit der Buchhaltung des Frontends, welche Dateien deinstalliert werden müssen, kollidieren würde.
Schließlich fügt PEP 660 nur Unterstützung für purelib- und platlib-Dateien hinzu. Es vermeidet bewusst die Unterstützung anderer Arten von Informationen, die das Wheel-Format unterstützt: include, data und scripts. Mit diesem Weg kann das Frontend diese auf Best-Effort-Basis über den Symlink-Mechanismus unterstützen (obwohl diese Funktion nicht universell verfügbar ist – unter Windows muss sie aktiviert werden). Wir glauben, dass es vorteilhaft ist, Best-Effort-Unterstützung für diese Dateitypen hinzuzufügen, anstatt die Möglichkeit, sie überhaupt zu unterstützen, auszuschließen.
Urheberrecht
Dieses Dokument wird in die Public Domain oder unter die CC0-1.0-Universal-Lizenz gestellt, je nachdem, welche Lizenz permissiver ist.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0662.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT