Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 665 – Ein Dateiformat zum Auflisten von Python-Abhängigkeiten für die Reproduzierbarkeit einer Anwendung

Autor:
Brett Cannon <brett at python.org>, Pradyun Gedam <pradyunsg at gmail.com>, Tzu-ping Chung <uranusjr at gmail.com>
PEP-Delegate:
Paul Moore <p.f.moore at gmail.com>
Discussions-To:
Discourse thread
Status:
Abgelehnt
Typ:
Standards Track
Thema:
Packaging
Erstellt:
29. Juli 2021
Post-History:
29. Juli 2021, 03. Nov. 2021, 25. Nov. 2021
Ersetzt-Durch:
751
Resolution:
Discourse-Nachricht

Inhaltsverzeichnis

Hinweis

Dieses PEP wurde wegen des lauwarmen Empfangs in der Community und des Mangels an Unterstützung für Quellcodeverteilungen abgelehnt.

Zusammenfassung

Dieses PEP spezifiziert ein Dateiformat zur Angabe der Liste von Python-Paketinstallationsanforderungen für eine Anwendung und der Beziehung zwischen den angegebenen Anforderungen. Die Liste der Anforderungen wird als erschöpfend für das Installationsziel betrachtet und erfordert somit keine weiteren Informationen über die Plattform, für die installiert wird, und die Datei selbst. Das Dateiformat ist flexibel genug, um die Installation der Anforderungen auf verschiedenen Plattformen zu ermöglichen, was die Reproduzierbarkeit auf mehreren Plattformen aus derselben Datei ermöglicht.

Terminologie

Es gibt mehrere Begriffe, deren Definition vereinbart werden muss, um eine Diskussion über das Thema dieses PEPs zu erleichtern.

Ein Paket ist etwas, das man als Abhängigkeit installiert und über das Importsystem nutzt. Die Pakete auf PyPI sind ein Beispiel dafür.

Eine Anwendung oder App ist ein Endprodukt, von dem anderer externer Code nicht direkt über das Importsystem abhängt (d.h. sie sind eigenständig). Desktop-Anwendungen, Kommandozeilenwerkzeuge usw. sind Beispiele für Anwendungen.

Eine Lock-Datei zeichnet die Pakete auf, die für eine App installiert werden sollen. Traditionell wird die genaue Version des zu installierenden Pakets von einer Lock-Datei angegeben, aber angegebene Pakete werden nicht immer auf einer bestimmten Plattform installiert (gemäß einer Filterlogik, die in einem späteren Abschnitt beschrieben wird), was es der Lock-Datei ermöglicht, die Reproduzierbarkeit auf mehreren Plattformen zu beschreiben. Beispiele hierfür sind package-lock.json von npm, Poetry.lock von Poetry usw.

Locking ist die Handlung, die Eingabe der Pakete, von denen eine App abhängt, zu nehmen und daraus eine Lock-Datei zu erstellen.

Ein Locker ist ein Werkzeug, das eine Lock-Datei erstellt.

Ein Installer verbraucht eine Lock-Datei, um das zu installieren, was die Lock-Datei spezifiziert.

Motivation

Anwendungen wünschen sich reproduzierbare Installationen aus mehreren Gründen (wir kümmern uns nicht um Paketentwicklung, Integration in größere Systeme, die das Locking von Abhängigkeiten außerhalb der Python-Anwendung übernehmen würden, oder andere Situationen, in denen flexible Installationsanforderungen gegenüber strengen, reproduzierbaren Installationen gewünscht sind).

Erstens erleichtert Reproduzierbarkeit die Entwicklung. Wenn Sie und Ihre Mitentwickler auf einer bestimmten Plattform die gleichen Dateien erhalten, stellen Sie sicher, dass Sie alle auf die gleiche Erfahrung für die Anwendung hin entwickeln. Sie möchten auch, dass Ihre Benutzer die gleichen Dateien installieren, wie Sie es erwarten, um die Erfahrung zu garantieren, wie Sie sie für sie entwickelt haben.

Zweitens möchten Sie in der Lage sein, das, was über mehrere Plattformen hinweg installiert wird, zu reproduzieren. Dank Pythons Portabilität über Betriebssysteme, CPUs usw. ist es sehr einfach und oft wünschenswert, Anwendungen zu erstellen, die nicht auf eine einzelne Plattform beschränkt sind. Daher möchten Sie flexibel genug sein, um Unterschiede in Ihren Paketabhängigkeiten zwischen Plattformen zuzulassen, während Sie gleichzeitig Konsistenz und Reproduzierbarkeit auf jeder einzelnen Plattform haben.

Drittens ist Reproduzierbarkeit sicherer. Wenn Sie genau kontrollieren, welche Dateien installiert werden, können Sie sicherstellen, dass kein böswilliger Akteur versucht, bösartigen Code in Ihre Anwendung einzuschleusen (d. h. einige Supply-Chain-Angriffe). Durch die Verwendung einer Lock-Datei, die immer zu reproduzierbaren Installationen führt, können wir bestimmte Risiken vollständig vermeiden.

Viertens bietet die Verwendung des Wheel-Dateiformats Reproduzierbarkeit, ohne dass Build-Tools selbst Reproduzierbarkeit unterstützen müssen. Da Wheels statisch sind und während der Installation keinen Code ausführen, führen Wheels immer zu einem reproduzierbaren Ergebnis. Vergleichen Sie dies mit Quellcodeverteilungen (auch sdists genannt) oder Quellcodebäumen, die nur dann zu einer reproduzierbaren Installation führen, wenn ihr Build-Tool Reproduzierbarkeit unterstützt, aufgrund der inhärenten Codeausführung. Leider unterstützt die überwiegende Mehrheit der Build-Tools keine reproduzierbaren Builds, daher hilft dieses PEP, dieses Problem zu lindern, indem nur Wheels als Paketformat unterstützt werden.

Dieses PEP schlägt einen Standard für eine Lock-Datei vor, da die aktuellen Lösungen die genannten Ziele nicht erfüllen. Heute ist das Anforderungsdatei-Format von pip das, was einem Lock-Dateistandard am nächsten kommt. Leider führt dieses Format nicht zu inhärent reproduzierbaren Installationen (es erfordert optionale Funktionen sowohl in der Anforderungsdatei als auch im Installer selbst, die später diskutiert werden).

Die Community selbst hat auch den Bedarf an Lock-Dateien gezeigt, da mehrere Tools unabhängig voneinander ihre eigenen Lock-Dateiformate entwickelt haben

  1. PDM
  2. pip-tools
  3. Pipenv
  4. Poetry
  5. Pyflow

Leider verwenden diese Tools alle unterschiedliche Lock-Dateiformate. Das bedeutet, dass die Tool-Umgebung um diese Tools herum einzigartig sein muss. Dies wirkt sich auf Tools wie Code-Editoren und Hosting-Provider aus, die möglichst flexibel sein möchten, wenn es darum geht, den Anwendungscode eines Benutzers zu akzeptieren, aber auch eine Grenze haben, wie viele Entwicklungsressourcen sie für die Unterstützung eines weiteren Lock-Dateiformats aufwenden können. Ein standardisiertes Format würde es Tools ermöglichen, ihre Arbeit auf ein einziges Ziel zu konzentrieren und sicherzustellen, dass Workflow-Entscheidungen, die von Entwicklern außerhalb des Lock-Dateiformats getroffen werden, für z.B. Hosting-Provider keine Rolle spielen.

Andere Programmiersprachen-Communities haben ebenfalls die Nützlichkeit von Lock-Dateien gezeigt, indem sie ihre eigenen Lösungen für dieses Problem entwickelt haben. Einige dieser Communities umfassen

  1. Dart
  2. npm/Node
  3. Go
  4. Rust

Der Trend in Programmiersprachen im letzten Jahrzehnt scheint in Richtung der Bereitstellung einer Lock-Dateilösung zu gehen.

Begründung

Dateiformat

Wir wollten, dass das Dateiformat leicht als Diff zu lesen ist, wenn eine Änderung an der Lock-Datei überprüft wird. Daher und dank PEP 518 und pyproject.toml haben wir uns für das TOML-Dateiformat entschieden.

Sicher durch Design

Wenn man das Anforderungsdatei-Format als das Nächstliegende betrachtet, das wir als Lock-Dateistandard haben, gibt es einige Probleme mit dem Dateiformat in Bezug auf die Sicherheit. Erstens verlangt das Dateiformat einfach nicht, dass Sie die genaue Version eines Pakets angeben. Deshalb gibt es Tools wie pip-tools, die Benutzern von Anforderungsdateien helfen.

Zweitens müssen Sie opt-in sein, um anzugeben, welche Dateien installiert werden dürfen, indem Sie das Argument --hash für eine bestimmte Abhängigkeit verwenden. Dies ist auch mit pip-tools optional, da es das CLI-Argument --generate-hashes erfordert. Dies erfordert --require-hashes für pip, um sicherzustellen, dass keine Abhängigkeiten einen Hash zum Überprüfen fehlen.

Drittens, selbst wenn Sie kontrollieren, welche Dateien installiert werden dürfen, verhindert dies nicht die Installation anderer Pakete. Wenn eine Abhängigkeit nicht in der Anforderungsdatei aufgeführt ist, sucht pip gerne nach einer Datei, um diesen Bedarf zu decken. Sie müssen --no-deps als Argument für pip angeben, um unbeabsichtigte Abhängigkeitsauflösungen außerhalb der Anforderungsdatei zu verhindern.

Viertens erlaubt das Format die Installation einer Quellcodeverteilungsdatei (auch „sdist“ genannt). Von Natur aus erfordert die Installation eines sdists die Ausführung von beliebigem Python-Code, was bedeutet, dass keine Kontrolle darüber besteht, welche Dateien installiert werden dürfen. Nur durch Angabe von --only-binary :all: können Sie sicherstellen, dass pip für jedes Paket nur eine Wheel-Datei verwendet.

Zusammenfassend lässt sich sagen, dass ein Benutzer, damit eine Anforderungsdatei so sicher ist wie das, was vorgeschlagen wird, immer die folgenden Schritte ausführen sollte

  1. Verwenden Sie pip-tools und seinen Befehl pip-compile --generate-hashes
  2. Installieren Sie die Anforderungsdatei mit pip install --require-hashes --no-deps --only-binary :all:

Kritischerweise sind all diese Flags und sowohl die Spezifität als auch die Erschöpfung dessen, was installiert werden soll, was pip-tools bietet, optional für Anforderungsdateien.

Daher ist der in diesem PEP vorgeschlagene Ansatz von Grund auf sicher und bekämpft einige Supply-Chain-Angriffe. Hashes für Dateien, die zur Installation verwendet würden, sind erforderlich. Sie können nur von Wheels installieren, um eindeutig zu definieren, welche Dateien im Dateisystem platziert werden. Installer müssen zu einer deterministischen Installation aus einer Lock-Datei für eine bestimmte Plattform führen. All dies führt zu einer reproduzierbaren Installation, die Sie als vertrauenswürdig einstufen können (wenn Sie die Lock-Datei und deren Inhalt überprüft haben).

Plattformübergreifend

Verschiedene Projekte, die bereits eine Lock-Datei haben, wie z.B. PDM und Poetry, bieten eine Lock-Datei, die plattformübergreifend ist. Dies ermöglicht eine einzelne Lock-Datei, die auf mehreren Plattformen funktioniert und dennoch zu den exakt gleichen Top-Level-Anforderungen führt, die überall installiert werden, wobei die Installation auf jeder Plattform konsistent/eindeutig ist.

Warum das nützlich ist, sehen wir an einem Beispiel im Zusammenhang mit PyWeek (einem wochenlangen Spielentwicklungswettbewerb). Angenommen, Sie entwickeln unter Linux, während jemand, mit dem Sie eine Partnerschaft eingehen, macOS verwendet. Nehmen wir nun an, die Juroren verwenden Windows. Wie stellen Sie sicher, dass alle die gleichen Top-Level-Abhängigkeiten verwenden, während Sie gleichzeitig plattformspezifische Anforderungen zulassen (z.B. ein Paket benötigt unter Windows ein Hilfspaket)?

Mit einer plattformübergreifenden Lock-Datei können Sie sicherstellen, dass die wichtigsten Anforderungen plattformübergreifend konsistent erfüllt werden. Sie können dann auch sicherstellen, dass alle Benutzer auf derselben Plattform die gleiche reproduzierbare Installation erhalten.

Einfacher Installer

Die Trennung der Zuständigkeiten zwischen Locker und Installer ermöglicht es einem Installer, eine viel einfachere Aufgabe zu erfüllen. Somit ist es nicht nur einfacher, Installer zu schreiben, sondern es trägt auch dazu bei, dass Installer eindeutige, reproduzierbare Installationen korrekt erstellen.

Der Installer kann auch weniger Rechenleistung/Energie für die Installation aufwenden. Dies ist nicht nur für schnellere Installationen von Vorteil, sondern auch im Hinblick auf den Energieverbrauch, da von Installern erwartet wird, dass sie häufiger ausgeführt werden als Locker.

Dies hat zu einem Design geführt, bei dem der Locker mehr Arbeit im Voraus leisten muss, was den Installern zugutekommt. Es bedeutet auch, dass die Komplexität von Paketabhängigkeiten in Lock-Dateien einfacher und leichter verständlich ist, um Mehrdeutigkeiten zu vermeiden.

Spezifikation

Details

Lock-Dateien MÜSSEN das TOML-Dateiformat verwenden. Dies verhindert nicht nur die Notwendigkeit eines weiteren Dateiformats im Python-Packaging-Ökosystem dank seiner Übernahme durch PEP 518 für pyproject.toml, sondern trägt auch dazu bei, Lock-Dateien besser lesbar zu machen.

Lock-Dateien MÜSSEN mit .pylock.toml enden. Der .toml-Teil unterscheidet das Format der Datei eindeutig und hilft Tools wie Code-Editoren, die Datei entsprechend zu unterstützen. Der .pylock-Teil unterscheidet die Datei von anderen TOML-Dateien, die der Benutzer hat, um die Logik für Tools zu erleichtern, Funktionalität speziell für Python-Lock-Dateien zu erstellen, anstatt für TOML-Dateien im Allgemeinen.

Die folgenden Abschnitte sind die Top-Level-Schlüssel des TOML-Dateidatenformats. Alle Felder, die nicht als erforderlich aufgeführt sind, gelten als optional.

version

Dieses Feld ist erforderlich.

Die Version der verwendeten Lock-Datei. Der Schlüssel MUSS eine Zeichenkette sein, die aus einer Zahl besteht, die dem gleichen Format wie der Schlüssel Metadata-Version in der Core-Metadaten-Spezifikation folgt.

Der Wert MUSS auf "1.0" gesetzt werden, bis ein zukünftiges PEP einen anderen Wert zulässt. Die Einführung eines neuen optionalen Schlüssels in das Dateiformat SOLLTE die Nebenversion erhöhen. Die Einführung eines neuen erforderlichen Schlüssels oder die Änderung des Formats MUSS die Hauptversion erhöhen. Wie mit anderen Szenarien umzugehen ist, bleibt eine Entscheidung pro PEP.

Installer MÜSSEN den Benutzer warnen, wenn die Lock-Datei eine Version angibt, deren Hauptversion unterstützt, aber deren Nebenversion nicht unterstützt/nicht erkannt wird (z.B. der Installer unterstützt "1.0", aber die Lock-Datei gibt "1.1" an).

Installer MÜSSEN einen Fehler auslösen, wenn die Lock-Datei eine Hauptversion angibt, die nicht unterstützt wird (z.B. der Installer unterstützt "1.9", aber die Lock-Datei gibt "2.0" an).

erstellt-am

Dieses Feld ist erforderlich.

Der Zeitstempel, wann die Lock-Datei generiert wurde (unter Verwendung des nativen Zeitstempeltyps von TOML). Er MUSS in der Zeitzone UTC aufgezeichnet werden, um Mehrdeutigkeiten zu vermeiden.

Wenn die Umgebungsvariable SOURCE_DATE_EPOCH gesetzt ist, MUSS sie vom Locker als Zeitstempel verwendet werden. Dies erleichtert die Reproduzierbarkeit der Lock-Datei selbst.

[tool]

Tools können ihre eigenen Untertabellen unter der tool-Tabelle erstellen. Die Regeln für diese Tabelle entsprechen denen für pyproject.toml und seine [tool]-Tabelle aus der Build-System-Deklarationsspezifikation.

[metadata]

Diese Tabelle ist erforderlich.

Eine Tabelle, die Daten für die gesamte Lock-Datei enthält.

metadata.marker

Ein Schlüssel, der eine Zeichenkette mit einem Umgebungskennzeichen speichert, wie in der Abhängigkeitskennzeichnungs-Spezifikation angegeben.

Der Locker KANN ein Umgebungskennzeichen angeben, das alle Einschränkungen spezifiziert, unter denen die Lock-Datei generiert wurde.

Wenn der Installer für eine Umgebung installiert, die das angegebene Umgebungskennzeichen nicht erfüllt, MUSS der Installer einen Fehler auslösen, da die Lock-Datei die Zielinstallationsumgebung nicht unterstützt.

metadata.tag

Ein Schlüssel, der eine Zeichenkette speichert, die Plattformkompatibilitätstags (d. h. Wheel-Tags) angibt. Der Tag KANN ein komprimierter Tag-Satz sein.

Wenn der Installer für eine Umgebung installiert, die den angegebenen Tag (Satz) nicht erfüllt, MUSS der Installer einen Fehler auslösen, da die Lock-Datei die Zielinstallationsumgebung nicht unterstützt.

metadata.requires

Dieses Feld ist erforderlich.

Eine Array von Zeichenketten gemäß der Abhängigkeitskennzeichnungs-Spezifikation. Dieses Array stellt die Top-Level-Paketabhängigkeiten der Lock-Datei und somit die Wurzel des Abhängigkeitsgraphen dar.

metadata.requires-python

Eine Zeichenkette, die die unterstützten Python-Version(en) für diese Lock-Datei angibt. Sie folgt dem gleichen Format wie das Feld Requires-Python in der Core-Metadaten-Spezifikation.

[[package._name_._version_]]

Dieses Array ist erforderlich.

Ein Array pro Paket und Version, das Einträge für die potenziellen (Wheel-)Dateien enthält, die installiert werden sollen (wie durch _name_ und _version_ dargestellt).

Locker MÜSSEN den Namen eines Projekts gemäß der Simple Repository API normalisieren. Wenn Extras als Teil des zu installierenden Projekts angegeben werden, sind die Extras im Schlüsselnamen enthalten und müssen in lexikographischer Reihenfolge sortiert werden.

Innerhalb der Datei SOLLTEN die Tabellen für die Projekte sortiert werden nach

  1. Projekt-/Schlüsselname in lexikographischer Reihenfolge
  2. Paketversion, neueste/höchste zu ältere/niedrigste gemäß der Versionenspezifikations-Spezifikation
  3. Optionale Abhängigkeiten (Extras) in lexikographischer Reihenfolge
  4. Dateiname basierend auf dem filename-Feld (weiter unten erläutert)

Diese Empfehlungen sollen helfen, Diff-Änderungen zwischen Tool-Ausführungen zu minimieren.

package._name_._version_.filename

Dieses Feld ist erforderlich.

Eine Zeichenkette, die den Basisnamen der Datei repräsentiert, wie er in einem Array-Eintrag dargestellt wird (d.h. was os.path.basename()/pathlib.PurePath.name repräsentiert). Dieses Feld ist erforderlich, um Installer zu vereinfachen, da der Dateiname benötigt wird, um Wheel-Tags aufzulösen, die aus dem Dateinamen abgeleitet werden. Es stellt auch sicher, dass die Zuordnung des Array-Eintrags zur Datei, für die er bestimmt ist, immer klar ist.

[package._name_._version_.hashes]

Diese Tabelle ist erforderlich.

Eine Tabelle mit Schlüsseln, die einen Hash-Algorithmus angeben, und Werten als Hash für die Datei, die durch diesen Eintrag in der package._name_._version_-Tabelle repräsentiert wird.

Locker SOLLTEN Hashes in lexikographischer Reihenfolge auflisten. Dies dient dazu, Diff-Größen und die Wahrscheinlichkeit zu minimieren, Hash-Wertänderungen zu übersehen.

Ein Installer DARF nur eine Datei installieren, die einem der angegebenen Hashes entspricht.

package._name_._version_.url

Eine Zeichenkette, die eine URL repräsentiert, von der die Datei bezogen werden kann.

Der Installer KANN beliebige Schemas für URLs unterstützen. Eine URL ohne Schema MUSS als lokaler Dateipfad angenommen werden (sowohl relativ zur Lock-Datei als auch absolut). Installer MÜSSEN mindestens HTTPS-URLs sowie lokale Dateipfade unterstützen.

Ein Installer KANN sich entscheiden, die URL nicht zur Beschaffung einer Datei zu verwenden, wenn eine Datei, die dem angegebenen Hash entspricht, auf alternative Weise gefunden werden kann (z.B. im Dateisystem in einem Cache-Verzeichnis).

package._name_._version_.direct

Ein Boolescher Wert, der angibt, ob ein Installer das Projekt als "direkt" installiert betrachten soll, wie es in der Direct URL Origin of Installed Distributions Spec festgelegt ist.

Wenn der Schlüssel true ist, MUSS der Installer der Direct URL Origin of Installed Distributions Spec folgen, um die Installation als "direkt" aufzuzeichnen.

package._name_._version_.requires-python

Eine Zeichenkette, die die unterstützten Python-Version(en) für diese Datei angibt. Sie folgt dem gleichen Format wie das Feld Requires-Python in der Core-Metadaten-Spezifikation.

package._name_._version_.requires

Ein Array von Zeichenketten gemäß der Abhängigkeitskennzeichnungs-Spezifikation, die die Abhängigkeiten dieser Datei darstellen.

Beispiel

version = "1.0"
created-at = 2021-10-19T22:33:45.520739+00:00

[tool]
# Tool-specific table.

[metadata]
requires = ["mousebender", "coveragepy[toml]"]
marker = "sys_platform == 'linux'"  # As an example for coverage.
requires-python = ">=3.7"

[[package.attrs."21.2.0"]]
filename = "attrs-21.2.0-py2.py3-none-any.whl"
hashes.sha256 = "149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"
url = "https://files.pythonhosted.org/packages/20/a9/ba6f1cd1a1517ff022b35acd6a7e4246371dfab08b8e42b829b6d07913cc/attrs-21.2.0-py2.py3-none-any.whl"
requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"

[[package.attrs."21.2.0"]]
# If attrs had another wheel file (e.g. that was platform-specific),
# it could be listed here.

[[package."coveragepy[toml]"."6.2.0"]]
filename = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
hashes.sha256 = "c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"
url = "https://files.pythonhosted.org/packages/da/64/468ca923e837285bd0b0a60bd9a287945d6b68e325705b66b368c07518b1/coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
requires-python = ">=3.6"
requires = ["tomli"]

[[package."coveragepy[toml]"."6.2.0"]]
filename = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl "
hashes.sha256 = "276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"
url = "https://files.pythonhosted.org/packages/17/d6/a29f2cccacf2315150c31d8685b4842a6e7609279939a478725219794355/coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl"
requires-python = ">=3.6"
requires = ["tomli"]

# More wheel files for `coverage` could be listed for more
# extensive support (i.e. all Linux-based wheels).

[[package.mousebender."2.0.0"]]
filename = "mousebender-2.0.0-py3-none-any.whl"
hashes.sha256 = "a6f9adfbd17bfb0e6bb5de9a27083e01dfb86ed9c3861e04143d9fd6db373f7c"
url = "https://files.pythonhosted.org/packages/f4/b3/f6fdbff6395e9b77b5619160180489410fb2f42f41272994353e7ecf5bdf/mousebender-2.0.0-py3-none-any.whl"
requires-python = ">=3.6"
requires = ["attrs", "packaging"]

[[package.packaging."20.9"]]
filename = "packaging-20.9-py2.py3-none-any.whl"
hashes.blake-256 = "3e897ea760b4daa42653ece2380531c90f64788d979110a2ab51049d92f408af"
hashes.sha256 = "67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
url = "https://files.pythonhosted.org/packages/3e/89/7ea760b4daa42653ece2380531c90f64788d979110a2ab51049d92f408af/packaging-20.9-py2.py3-none-any.whl"
requires-python = ">=3.6"
requires = ["pyparsing"]

[[package.pyparsing."2.4.7"]]
filename = "pyparsing-2.4.7-py2.py3-none-any.whl"
hashes.sha256 = "ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
url = "https://files.pythonhosted.org/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl"
direct = true  # For demonstration purposes.
requires-python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"

[[package.tomli."2.0.0"]]
filename = "tomli-2.0.0-py3-none-any.whl"
hashes.sha256 = "b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"
url = "https://files.pythonhosted.org/packages/e2/9f/5e1557a57a7282f066351086e78f87289a3446c47b2cb5b8b2f614d8fe99/tomli-2.0.0-py3-none-any.whl"
requires-python = ">=3.7"

Erwartungen an Locker

Locker MÜSSEN Lock-Dateien erstellen, für die ein topologischer Sortierung der Pakete, die für die Installation auf der angegebenen Plattform in Frage kommen, einen Graphen ergibt, in dem nur eine Version eines jeden Pakets für die Installation in Frage kommt und für jedes Paket mindestens eine kompatible Datei zur Installation vorhanden ist. Dies führt zu einer Lock-Datei für jede unterstützte Plattform, bei der die einzige Entscheidung, die ein Installer treffen kann, darin besteht, welches "am besten passende" Wheel installiert wird (was weiter unten besprochen wird).

Von den Lockern wird erwartet, dass sie metadata.marker, metadata.tag und metadata.requires-python entsprechend sowie Umgebungskennzeichen, die über requires angegeben werden, und Python-Versionsanforderungen über requires-python verwenden, um dieses Ergebnis für Installer zu erzwingen. Anders ausgedrückt wird erwartet, dass die in der Lock-Datei verwendeten Informationen nicht "rein" aus der Eingabe des Lockers stammen, sondern stattdessen nach Bedarf geändert werden, um den Zielen des Lockers zu dienen.

Erwartungen an Installer

Der erwartete Algorithmus zur Auflösung dessen, was installiert werden soll, ist

  1. Erstellen Sie einen Abhängigkeitsgraphen basierend auf den Daten in der Lock-Datei mit metadata.requires als Start-/Wurzelpunkt.
  2. Eliminieren Sie alle Dateien, die von der angegebenen Plattform nicht unterstützt werden.
  3. Eliminieren Sie alle irrelevanten Kanten zwischen Paketen basierend auf der Auswertung von Kennzeichen für requires.
  4. Lösen Sie einen Fehler aus, wenn eine Paketversion vom Wurzelknoten des Abhängigkeitsgraphen aus noch erreichbar ist, aber keine kompatible Datei hat.
  5. Überprüfen Sie, ob für alle verbleibenden Pakete nur noch eine zu installierende Version vorhanden ist, lösen Sie andernfalls einen Fehler aus.
  6. Installieren Sie das am besten passende Wheel-Datei für jedes verbleibende Paket.

Installer MÜSSEN einem deterministischen Algorithmus folgen, um zu bestimmen, welches "am besten passende Wheel-Datei" ist. Eine einfache Lösung hierfür ist die Verwendung des packaging-Projekts und seines packaging.tags-Moduls, um die Priorität von Wheel-Dateien zu bestimmen.

Installer MÜSSEN die Installation in einer leeren Umgebung unterstützen. Installer KÖNNEN die Installation in einer Umgebung unterstützen, die bereits installierte Pakete enthält (und was auch immer damit verbunden ist, unterstützt zu werden).

(Mögliche) Tool-Unterstützung

Das pip-Team hat gesagt, dass es an der Unterstützung dieses PEPs interessiert ist, wenn es akzeptiert wird. Der aktuelle Vorschlag für pip könnte sogar die Notwendigkeit von pip-tools ersetzen.

PDM hat ebenfalls erklärt, dass es das PEP unterstützen würde, wenn es akzeptiert wird.

Pyflow hat gesagt, dass sie die Idee des PEPs mögen.

Poetry hat erklärt, dass es das PEP nicht unterstützen würde, da Poetry sdists-Dateien, Verzeichnisse und VCS-Abhängigkeiten unterstützt, die nicht unterstützt werden. Die Anforderung auf Dateiebene zu protokollieren, was absichtlich geschieht, um besser widerzuspiegeln, was bei Abhängigkeiten passieren kann, widerspricht dem Design von Poetry. Dies schließt auch den Export in die Lock-Datei dieses PEPs aus, da Poetry die Informationen in der poetry.lock-Datei in ein anderes Format exportiert und sdists und Quellcodebäume in Poetry.lock-Dateien enthalten sind. Somit ist es keine saubere Übersetzung von Poetrys Lock-Datei in das Lock-Dateiformat dieses PEPs.

Abwärtskompatibilität

Da es keine bestehende Spezifikation für Lock-Dateien gibt, gibt es keine expliziten Abwärtskompatibilitätsprobleme.

Für bestehende Tools, die ihre eigene Lock-Datei haben, wird einige Aktualisierung erforderlich sein. Die meisten dokumentieren den Namen der Lock-Datei, aber nicht ihren Inhalt. Für Projekte, die ihre Lock-Datei nicht in die Versionskontrolle einchecken, müssen sie das Äquivalent ihrer .gitignore-Datei aktualisieren. Für Projekte, die ihre Lock-Datei in die Versionskontrolle einchecken, müssen die eingecheckten Dateien aktualisiert werden.

Für Projekte, die ihr Lock-Dateiformat dokumentieren, wie z.B. pipenv, werden sie sehr wahrscheinlich eine Major-Version-Release benötigen, die das Lock-Dateiformat ändert.

Migrationsplan

Im Allgemeinen könnte dieses PEP als erfolgreich betrachtet werden, wenn

  1. Zwei bestehende Tools zu Lockern würden (z.B. pip-tools, PDM, pip via pip freeze).
  2. Pip ein Installer würde.
  3. Eine wichtige, nicht-Python-spezifische Plattform würde das Dateiformat unterstützen (z.B. ein Cloud-Provider).

Dies würde Interoperabilität, Benutzerfreundlichkeit und Akzeptanz in der Programmier-Community/Wirtschaft zeigen.

Hinsichtlich eines Übergangsplans gibt es potenziell mehrere Schritte, die zu diesem gewünschten Ergebnis führen könnten. Nachfolgend finden Sie einen etwas idealisierten Plan, der dazu führen würde, dass dieses PEP breit genutzt wird.

Benutzerfreundlichkeit

Erstens könnte ein pip freeze-Äquivalent-Tool entwickelt werden, das eine Lock-Datei erstellt. Obwohl installierte Pakete allein nicht genügend Informationen liefern, um statisch eine Lock-Datei zu erstellen, könnte ein Benutzer lokale Verzeichnisse und Index-URLs angeben, um eine solche zu konstruieren. Dies würde dann zu Lock-Dateien führen, die strenger als eine Anforderungsdatei sind, indem die Lock-Datei auf die aktuelle Plattform beschränkt wird. Dies würde es den Leuten auch ermöglichen, zu sehen, ob ihre Umgebung reproduzierbar wäre.

Zweitens sollte ein eigenständiger Installer entwickelt werden. Da die Anforderungen an einen Installer viel einfacher sind als die, die pip bietet, sollte es angemessen sein, einen unabhängig entwickelten Installer zu haben.

Drittens könnte ein Tool entwickelt werden, um eine angepinnte Anforderungsdatei, wie sie von pip-tools ausgegeben wird, zu konvertieren. Ähnlich wie beim oben beschriebenen pip freeze-Äquivalent könnten einige Benutzereingaben erforderlich sein. Aber dieses Tool könnte als Übergangsschritt für jeden dienen, der eine geeignete Anforderungsdatei hat. Dies könnte auch als Test dienen, bevor pip-tools möglicherweise ein --lockfile-Flag zur Nutzung dieses PEPs erhält.

All dies könnte erforderlich sein, bevor das PEP vom bedingten Akzeptanz zum vollen Akzeptanz übergeht (und der Community die Chance gibt zu testen, ob dieses PEP potenziell nützlich ist).

Interoperabilität

An diesem Punkt wäre das Ziel, die Interoperabilität zwischen den Tools zu erhöhen.

Erstens würde pip ein Installer werden. Indem der am weitesten verbreitete Installer das Format unterstützt, können Leute auf der Locker-Seite innovativ sein, während sie wissen, dass die Leute über die notwendigen Werkzeuge verfügen, um eine Lock-Datei tatsächlich zu konsumieren.

Zweitens wird pip ein Locker. Wieder einmal würde Pips Reichweite das Format sehr schnell für die überwiegende Mehrheit der Python-Benutzer zugänglich machen.

Drittens unterstützt ein Projekt mit einem bereits vorhandenen Lock-File-Format mindestens den Export in das Lock-File-Format (z. B. PDM oder Pyflow). Dies würde zeigen, dass das Format die Bedürfnisse anderer Projekte erfüllt.

Akzeptanz

Mit den Werkzeugen, die in der gesamten Community verfügbar sind, würde die Akzeptanz dadurch gezeigt, dass auch diejenigen, die nicht ausschließlich an die Python-Community gebunden sind, das Dateiformat unterstützen, basierend auf dem, was sie für die Wünsche ihrer Nutzer halten.

Erstens, Werkzeuge, die mit Anforderungsdateien arbeiten, wie z. B. Code-Editoren, die eine gleichwertige Unterstützung für Lock-Dateien haben.

Zweitens würden auch Verbraucher von Anforderungsdateien wie Cloud-Anbieter Lock-Dateien akzeptieren.

Zu diesem Zeitpunkt wäre die PEP weit genug verbreitet, um in Bezug auf die allgemeine Akzeptanz auf dem gleichen Niveau wie Anforderungsdateien zu liegen, und möglicherweise mehr, wenn Projekte ihre eigenen Lock-Dateien zugunsten dieser PEP aufgegeben hätten.

Sicherheitsimplikationen

Ein Lock-File sollte keine Sicherheitsprobleme verursachen, sondern stattdessen helfen, sie zu lösen. Durch die Anforderung der Aufzeichnung von Hashes für Dateien kann ein Lock-File helfen, Manipulationen am Code zu verhindern, da die Hash-Details aufgezeichnet wurden. Die ausschließliche Verwendung von Wheel-Dateien bedeutet, dass im Voraus bekannt ist, welche Dateien installiert werden, und dies ist reproduzierbar. Ein Lock-File hilft auch, unerwartete Paketaktualisierungen zu verhindern, die bösartig sein könnten.

Wie man das lehrt

Die Vermittlung dieser PEP wird sehr stark von den Lockern und Installern abhängen, die für den täglichen Gebrauch verwendet werden. Konzeptionell könnten die Benutzer jedoch lernen, dass eine Lock-Datei festlegt, was für die Funktionsfähigkeit eines Projekts installiert werden sollte. Die Vorteile von Konsistenz und Sicherheit sollten hervorgehoben werden, um den Benutzern zu helfen, zu erkennen, warum sie sich für Lock-Dateien interessieren sollten.

Referenzimplementierung

Ein Proof-of-Concept-Locker ist unter https://github.com/frostming/pep665_poc zu finden. Ein Installer wurde noch nicht implementiert, aber das Design dieser PEP legt nahe, dass der Locker der schwierigere Aspekt der Implementierung ist.

Abgelehnte Ideen

Dateiformate außer TOML

JSON wurde kurzzeitig in Betracht gezogen, aber wegen

  1. TOML, das bereits für pyproject.toml verwendet wird
  2. TOML ist lesbarer
  3. TOML führt zu besseren Diffs

wurde die Entscheidung getroffen, TOML zu verwenden. Es gab einige Bedenken hinsichtlich des Fehlens eines TOML-Parsers in der Standardbibliothek von Python, aber die meisten Paketierungswerkzeuge verwenden bereits einen TOML-Parser dank pyproject.toml, sodass dieses Problem kein Hindernis darstellte. Einige haben sich auch in der Vergangenheit gegen diese Bedenken gewandt, indem sie argumentierten, dass, wenn Paketierungswerkzeuge sich davor scheuen, Abhängigkeiten zu installieren und das Gefühl haben, kein Paket bündeln zu können, das Paketierungsökosystem weitaus größere Probleme zu beheben hat als die Notwendigkeit, von einem Drittanbieter-TOML-Parser abhängig zu sein.

Alternative Namensschemata

Die Angabe eines Verzeichnisses, in das Dateien installiert werden sollen, wurde in Erwägung gezogen, aber letztendlich verworfen, da die Leute diese Idee ablehnten.

Es wurde auch vorgeschlagen, keine spezielle Dateinamenerweiterung zu verwenden, aber es wurde entschieden, dass dies die Erkennbarkeit durch Werkzeuge zu sehr beeinträchtigen würde.

Unterstützung einer einzelnen Lock-Datei

Zu einem Zeitpunkt wurde die Idee in Erwägung gezogen, nur eine einzige Lock-Datei zu unterstützen, die alle möglichen Lock-Informationen enthielt. Aber es wurde schnell klar, dass der Versuch, ein Datenformat zu entwickeln, das sowohl ein Lock-File-Format, das mehrere Umgebungen unterstützt, als auch strenge Lock-Ergebnisse für reproduzierbare Builds umfassen könnte, sehr komplex und umständlich werden würde.

Die Idee, ein Verzeichnis von Lock-Dateien sowie eine einzelne Lock-Datei mit dem Namen pyproject-lock.toml zu unterstützen, wurde ebenfalls in Erwägung gezogen. Aber jede mögliche Einfachheit durch das Überspringen des Verzeichnisses im Falle einer einzelnen Lock-Datei schien unnötig. Der Versuch, geeignete Logiken dafür zu definieren, was die Datei pyproject-lock.toml sein sollte und was in pyproject-lock.d gehen sollte, schien unnötig kompliziert.

Verwendung einer flachen Liste anstelle eines Abhängigkeitsgraphen

Die erste Version dieser PEP schlug vor, dass die Lock-Datei kein Konzept eines Abhängigkeitsgraphen hat. Stattdessen würde die Lock-Datei genau auflisten, was für eine bestimmte Plattform installiert werden sollte, so dass Installierer keine Entscheidungen darüber treffen müssen, *was* installiert werden soll, sondern nur validieren, dass die Lock-Datei für die Zielplattform funktioniert.

Diese Idee wurde schließlich aufgrund der Anzahl von Kombinationen potenzieller PEP 508-Umgebungsmarker abgelehnt. Es wurde entschieden, dass der Versuch, von Lockern alle möglichen Kombinationen als einzelne Lock-Dateien generieren zu lassen, wenn ein Projekt plattformübergreifend sein soll, zu viel wäre.

Wheel-Tags im Dateinamen verwenden

Anstatt des Feldes metadata.tag gab es den Vorschlag, die Tags in den Dateinamen zu kodieren. Aber aufgrund der Hinzufügung des Feldes metadata.marker und was zu tun ist, wenn keine Tags benötigt wurden, wurde die Idee fallen gelassen.

Alternative Namen für requires

Einige andere Namen für das, was zu requires wurde, waren installs, needs und dependencies. Anfangs wählte diese PEP needs, nachdem sie einen Python-Anfänger gefragt hatte, welchen Begriff er bevorzugt. Aber basierend auf dem Feedback zu einem früheren Entwurf dieser PEP wurde requires als Begriff gewählt.

Akzeptanz von PEP 650

PEP 650 war ein früherer Versuch, dieses Problem anzugehen, indem eine API für Installer anstelle einer Standardisierung eines Lock-File-Formats (wie bei PEP 517) spezifiziert wurde. Die erste Reaktion auf PEP 650 könnte als mild/lauwarm betrachtet werden. Die Leute schienen sich konsequent darüber zu wundern, welche Werkzeuge welche Funktionalität zur Implementierung der PEP bereitstellen sollten. Außerdem incurred es potenziell mehr Overhead, da die Ausführung von Python-APIs für alle Aktionen im Zusammenhang mit der Paketierung erforderlich wäre.

Diese PEP wählt die Standardisierung auf ein Artefakt statt auf eine API (wie bei PEP 621). Dies würde mehr Werkzeugintegrationen ermöglichen, da die Notwendigkeit entfällt, speziell Python zu verwenden, um Dinge wie das Erstellen einer Lock-Datei, deren Aktualisierung oder sogar die Installation von in einer Lock-Datei aufgeführten Paketen durchzuführen. Es ermöglicht auch eine einfachere Introspektion, indem es die Details des Abhängigkeitsgraphen in einem lesbaren Format zwingt. Es ermöglicht auch einen einfacheren Wissensaustausch, indem standardisiert wird, was die Leute mehr wissen müssen (z. B. werden Tutorials zwischen Werkzeugen portabler, wenn es darum geht, das von ihnen erzeugte Artefakt zu verstehen). Es ist auch einfach der Ansatz, den andere Sprachgemeinschaften verfolgt haben und mit dem sie zufrieden zu sein scheinen.

Die Akzeptanz dieser PEP würde bedeuten, dass PEP 650 abgelehnt wird.

Anforderungen pro Paket statt pro Datei festlegen

Ein früherer Entwurf dieser PEP spezifizierte Abhängigkeiten auf Paketebene anstatt pro Datei. Während dies traditionell die Arbeitsweise von Paketierungssystemen war, spiegelte es tatsächlich nicht genau wider, wie Dinge spezifiziert werden. Als solches wurde diese PEP anschließend aktualisiert, um die Granularität widerzuspiegeln, auf der Abhängigkeiten wirklich spezifiziert werden können.

Festlegen, wo Locker Eingaben sammeln

Diese PEP spezifiziert nicht, wie ein Locker seine Eingaben erhält. Ein erster Vorschlag war, PEP 621 teilweise wiederzuverwenden, aber aufgrund von Meinungsverschiedenheiten darüber, wie flexibel die potenziellen Eingaben bei der Spezifikation von Dingen wie Indizes usw. sein sollten, wurde entschieden, dass dies am besten einer separaten PEP überlassen wird.

Quellcodeverteilungen und Quellcodebäume als opt-in, unterstütztes Dateiformat zulassen

Nach ausführlicher Diskussion wurde entschieden, dass diese PEP keine Quellcode-Distributionen (auch bekannt als sdists) oder Quellcode-Bäume als akzeptables Format für Code unterstützt. Die Einführung von sdists und Quellcode-Bäumen in diese PEP würde die Ziele der Reproduzierbarkeit und Sicherheit sofort aufheben, da Code zum Erstellen des sdist oder Quellcode-Baums ausgeführt werden müsste. Es würde auch die Komplexität für (zumindest) Installer erheblich erhöhen, da die dynamische Build-Natur von sdists und Quellcode-Bäumen bedeutet, dass der Installer die vollständige Auflösung aller Anforderungen, die sdists dynamisch produzieren, sowohl aus Sicht des Builds als auch der Installation bewältigen müsste.

Aufgrund all dessen wurde entschieden, dass es am besten ist, eine separate Diskussion über die Unterstützung von sdists und Quellcode-Bäumen zu führen, *nachdem* diese PEP akzeptiert/abgelehnt wurde. Da das vorgeschlagene Dateiformat mit Versionen versehen ist, ist die Einführung von sdists und Quellcode-Baum-Unterstützung in einer späteren PEP machbar.

Es sei jedoch darauf hingewiesen, dass diese PEP eine Out-of-Band-Lösung *nicht* stoppen wird, die in Verbindung mit dieser PEP entwickelt werden kann. Das Erstellen von Wheel-Dateien aus sdists und deren Auslieferung mit Code bei der Bereitstellung, damit sie in die Lock-Datei aufgenommen werden können, ist eine Option. Eine andere ist die Verwendung einer Anforderungsdatei *nur* für sdists und Quellcode-Bäume, und dann die Verlassung auf eine Lock-Datei für alle Wheels.

Offene Fragen

Keine.

Danksagungen

Vielen Dank an Frost Ming von PDM und Sébastien Eustace von Poetry für die Bereitstellung von Beiträgen zur dynamischen Auflösung von PEP 508-Anforderungen zur Laufzeit.

Vielen Dank an Kushal Das dafür, dass er sichergestellt hat, dass reproduzierbare Builds ein Anliegen dieser PEP bleiben.

Vielen Dank an Andrea McInnes für die anfängliche Klärung der "Bikeshedding"-Diskussion und die Wahl der Farbe needs (zu diesem Zeitpunkt schlossen sich die Leute stattdessen der Farbe requires an).


Quelle: https://github.com/python/peps/blob/main/peps/pep-0665.rst

Zuletzt geändert: 2024-07-26 12:58:25 GMT