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

Python Enhancement Proposals

PEP 650 – Spezifikation von Installer-Anforderungen für Python-Projekte

Autor:
Vikram Jayanthi <vikramjayanthi at google.com>, Dustin Ingram <di at python.org>, Brett Cannon <brett at python.org>
Discussions-To:
Discourse thread
Status:
Zurückgezogen
Typ:
Standards Track
Thema:
Packaging
Erstellt:
16-Jul-2020
Post-History:
14-Jan-2021

Inhaltsverzeichnis

Zusammenfassung

Python-Paket-Installer sind nicht vollständig interoperabel. Während pip der am weitesten verbreitete Installer und ein De-facto-Standard ist, sind auch andere Installer wie Poetry oder Pipenv beliebt, da sie einzigartige Funktionen bieten, die für bestimmte Workflows optimal sind und nicht direkt mit der Funktionsweise von pip übereinstimmen.

Während die Fülle an Installer-Optionen gut für Endbenutzer mit spezifischen Bedürfnissen ist, erschwert die mangelnde Interoperabilität zwischen ihnen die Unterstützung aller potenziellen Installer. Insbesondere fehlt eine Standard-Anforderungsdatei zur Deklaration von Abhängigkeiten, was bedeutet, dass jedes Werkzeug explizit verwendet werden muss, um Abhängigkeiten zu installieren, die in seinem jeweiligen Format angegeben sind. Andernfalls müssen Werkzeuge eine Anforderungsdatei ausgeben, was zu potenziellem Informationsverlust für den Installer und einem zusätzlichen Export-Schritt im Workflow eines Entwicklers führt.

Durch die Bereitstellung einer standardisierten API, die zum Aufrufen eines kompatiblen Installers verwendet werden kann, können wir dieses Problem lösen, ohne individuelle Belange, einzigartige Anforderungen und Inkompatibilitäten zwischen verschiedenen Installern und ihren Lock-Dateien lösen zu müssen.

Installer, die die Spezifikation implementieren, können auf eine einheitliche Weise aufgerufen werden, sodass Benutzer ihren bevorzugten Installer so verwenden können, als würden sie ihn direkt aufrufen.

Terminologie

Installer-Schnittstelle
Die Schnittstelle, über die ein *Installer-Backend* und ein *universeller Installer* interagieren.
Universeller Installer
Ein Installer, der ein *Installer-Backend* aufrufen kann, indem er die optionalen Aufrufmethoden der *Installer-Schnittstelle* verwendet. Dies kann auch als Installer-Frontend betrachtet werden, ähnlich dem build-Projekt für PEP 517.
Installer-Backend
Ein Installer, der die *Installer-Schnittstelle* implementiert und somit von einem *universellen Installer* aufgerufen werden kann. Ein *Installer-Backend* kann auch ein *universeller Installer* sein, dies ist jedoch nicht zwingend erforderlich. Im Vergleich zu PEP 517 wäre dies Flit. *Installer-Backends* können Wrapper-Pakete um einen unterstützenden Installer sein, z.B. könnte Poetry sich entscheiden, diese API nicht zu unterstützen, aber ein Paket könnte als Wrapper fungieren, um Poetry nach Bedarf aufzurufen, um mit Poetry eine Installation durchzuführen.
Abhängigkeitsgruppe
Eine Menge von Abhängigkeiten, die zusammenhängen und gleichzeitig für einen bestimmten Zweck installiert werden müssen. Zum Beispiel könnte eine Abhängigkeitsgruppe "test" die Abhängigkeiten enthalten, die zur Ausführung der Testsuite erforderlich sind. Wie Abhängigkeitsgruppen spezifiziert werden, liegt im Ermessen des *Installer-Backends*.

Motivation

Diese Spezifikation ermöglicht es jedem, *Installer-Backends* aufzurufen und mit ihnen zu interagieren, die die spezifizierte Schnittstelle implementieren, was eine universell unterstützte Schicht über bestehenden Tool-spezifischen Installationsprozessen ermöglicht.

Dies würde wiederum die Nutzung aller Installer ermöglichen, die die spezifizierte Schnittstelle implementieren, in Umgebungen, die einen einzelnen *universellen Installer* unterstützen, solange dieser Installer diese Spezifikation ebenfalls implementiert.

Im Folgenden identifizieren wir verschiedene Anwendungsfälle, die für Stakeholder in der Python-Community und für alle, die mit Python-Paket-Installern interagieren, relevant sind. Für Entwickler oder Unternehmen würde diese PEP zu erhöhter Funktionalität und Flexibilität bei Python-Paket-Installern führen.

Anbieter

Anbieter sind die Parteien (Organisation, Person, Community etc.), die einen Dienst oder ein Softwarewerkzeug liefern, das mit der Python-Paketierung und folglich mit Python-Paket-Installern interagiert. Zwei verschiedene Arten von Anbietern werden berücksichtigt.

Plattform-/Infrastrukturanbieter

Plattformanbieter (Cloud-Umgebungen, Anwendungs-Hosting usw.) und Infrastrukturdienstleister müssen Paket-Installer unterstützen, damit ihre Benutzer Python-Abhängigkeiten installieren können. Die meisten unterstützen nur pip, es gibt jedoch eine Benutzeranforderung nach anderen Python-Installern. Die meisten Anbieter wollen nicht mehr als einen Installer unterstützen, da dies die Komplexität ihrer Software oder ihres Dienstes erhöht und Ressourcen bindet.

Über diese Spezifikation können wir einen von Anbietern unterstützten *universellen Installer* ermöglichen, den vom Benutzer gewünschten *Installer-Backend* aufzurufen, ohne dass die Plattform des Anbieters spezifische Kenntnisse über dieses Backend benötigt. Das bedeutet, wenn Poetry die von dieser PEP vorgeschlagene Installer-Backend-API implementieren würde (oder ein anderes Paket Poetry als Wrapper zur Bereitstellung der API nutzen würde), dann würden Plattformanbieter Poetry implizit unterstützen.

IDE-Anbieter

Integrierte Entwicklungsumgebungen können mit der Installation und Verwaltung von Python-Paketen interagieren. Die meisten unterstützen nur pip als Python-Paket-Installer, und Benutzer müssen Workarounds finden, um ihre Abhängigkeiten mit anderen Paket-Installern zu installieren. Ähnlich wie bei PaaS- und IaaS-Anbietern wollen IDE-Anbieter keine Unterstützung für N verschiedene Python-Installer pflegen. Stattdessen könnten Implementierer der Installer-Schnittstelle (*Installer-Backends*) von der IDE aufgerufen werden, indem diese als *universeller Installer* fungiert.

Entwickler

Entwickler sind Teams, Personen oder Gemeinschaften, die Python-Paket-Installer und Python-Pakete programmieren und nutzen. Drei verschiedene Arten von Entwicklern werden berücksichtigt.

Entwickler, die PaaS- & IaaS-Anbieter nutzen

Die meisten PaaS- und IaaS-Anbieter unterstützen nur einen Python-Paket-Installer: pip. (Einige Ausnahmen sind Herokus Python buildpack, das pip und Pipenv unterstützt). Dies diktiert die Installer, die Entwickler bei der Arbeit mit diesen Anbietern verwenden können, was möglicherweise nicht optimal für ihre Anwendung oder ihren Workflow ist.

Installer, die diese PEP übernehmen, um *Installer-Backends* zu werden, würden es Benutzern ermöglichen, Plattformen/Infrastrukturen von Drittanbietern zu nutzen, ohne sich Gedanken darüber machen zu müssen, welchen Python-Paket-Installer sie verwenden müssen, solange der Anbieter einen *universellen Installer* verwendet.

Entwickler, die IDEs nutzen

Die meisten IDEs unterstützen nur pip oder wenige Python-Paket-Installer. Folglich müssen Entwickler Workarounds oder "hacky" Methoden verwenden, um ihre Abhängigkeiten zu installieren, wenn sie einen nicht unterstützten Paket-Installer verwenden.

Wenn die IDE einen *universellen Installer* verwendet/bereitstellt, könnte jeder vom Entwickler gewünschte *Installer-Backend* zur Installation von Abhängigkeiten verwendet werden, was ihm jegliche Zusatzarbeit zur Installation seiner Abhängigkeiten abnimmt, um enger in den Workflow der IDE integriert zu werden.

Entwickler, die mit anderen Entwicklern arbeiten

Entwickler möchten in der Lage sein, den Installer ihrer Wahl zu verwenden, wenn sie mit anderen Entwicklern arbeiten, müssen aber derzeit ihre Installer-Wahl zur Kompatibilität der Abhängigkeitsinstallation synchronisieren. Wenn alle bevorzugten Installer stattdessen die spezifizierte Schnittstelle implementieren würden, würde dies die gemeinsame Nutzung von Installern ermöglichen, sodass Entwickler einen Installer unabhängig von der Präferenz ihres Kollaborateurs wählen können.

Aktualisierer & Paket-Infrastrukturanbieter

Paket-Aktualisierer und Paket-Infrastrukturen in CI/CD wie Dependabot, PyUP usw. unterstützen derzeit nur wenige Installer. Sie arbeiten, indem sie die Installer-spezifischen Abhängigkeitsdateien direkt (wie requirements.txt oder poetry.lock) mit relevanten Paketinformationen wie Upgrades, Downgrades oder neuen Hashes parsen und bearbeiten. Ähnlich wie bei Plattform- und IDE-Anbietern wollen die meisten dieser Anbieter keine N verschiedenen Python-Paket-Installer unterstützen, da dies die Unterstützung für N verschiedene Dateitypen erfordern würde.

Derzeit müssen diese Dienste/Bots die Unterstützung für jeden Paket-Installer einzeln implementieren. Unvermeidlich werden zuerst die beliebtesten Installer unterstützt, und weniger beliebte Tools werden oft nie unterstützt. Durch die Implementierung dieser Spezifikation können diese Dienste/Bots jeden (konformen) Installer unterstützen und Benutzern die Auswahl des Tools ihrer Wahl ermöglichen. Dies ermöglicht mehr Innovation in diesem Bereich, da Plattformen und IDEs nicht mehr gezwungen sind, vorzeitig einen "Gewinner" auszuwählen.

Open-Source-Community

Die Spezifikation von Installer-Anforderungen und die Übernahme dieser PEP reduziert die Reibung zwischen Python-Paket-Installern und den Workflows von Benutzern. Folglich reduziert sie die Reibung zwischen Python-Paket-Installern und 3rd-Party-Infrastrukturen/Technologien wie PaaS oder IDEs. Insgesamt ermöglicht sie einfachere Entwicklung, Bereitstellung und Wartung von Python-Projekten, da die Python-Paketinstallation einfacher und interoperabler wird.

Die Spezifikation von Anforderungen und die Erstellung einer Schnittstelle für Installer kann auch das Innovationstempo rund um Installer erhöhen. Dies würde es Installern ermöglichen, zu experimentieren und einzigartige Funktionalitäten hinzuzufügen, ohne dass der Rest des Ökosystems dasselbe tun muss. Die Unterstützung wird für einen neuen Installer unabhängig von der hinzugefügten Funktionalität und dem Format, in dem er Abhängigkeiten schreibt, einfacher und wahrscheinlicher, während der Zeit- und Ressourcenaufwand dafür reduziert wird.

Spezifikation

Ähnlich wie PEP 517 Build-Systeme spezifiziert, werden die Informationen des Install-Systems in der Datei pyproject.toml unter der Tabelle install-system gespeichert.

[install-system]

Die Tabelle install-system wird verwendet, um install-system-relevante Daten und Informationen zu speichern. Es gibt mehrere obligatorische Schlüssel für diese Tabelle: requires und install-backend. Der Schlüssel requires enthält die Mindestanforderungen für die Ausführung des *Installer-Backends*, die vom *universellen Installer* installiert werden. Der Schlüssel install-backend enthält den Namen des Entry Points des Installer-Backends. Dies ermöglicht es dem *universellen Installer*, die Anforderungen für die Ausführung des *Installer-Backends* selbst zu installieren (nicht die Anforderungen, die das *Installer-Backend* selbst installieren wird) und das *Installer-Backend* aufzurufen.

Wenn einer der obligatorischen Schlüssel fehlt oder leer ist, SOLLTE der *universelle Installer* einen Fehler auslösen.

Alle Paketnamen, die mit dieser Schnittstelle interagieren, werden angenommen, dass sie dem Format der „Dependency specification for Python Software Packages“ von PEP 508 folgen.

Ein Beispiel für die Tabelle install-system

#pyproject.toml
[install-system]
#Eg : pipenv
requires = ["pipenv"]
install-backend = "pipenv.api:main"

Installer-Anforderungen

Die vom Schlüssel requires spezifizierten Anforderungen müssen innerhalb der von PEP 517 festgelegten Grenzen liegen. Insbesondere sind Abhängigkeitszyklen nicht gestattet, und der *universelle Installer* SOLLTE die Installation der Abhängigkeiten verweigern, wenn ein Zyklus erkannt wird.

Zusätzliche Parameter oder Tool-spezifische Daten

Zusätzliche Parameter oder Tool-Daten (*Installer-Backend*) können ebenfalls in der Datei pyproject.toml gespeichert werden. Dies würde in der Tabelle „tool.*“ erfolgen, wie von PEP 518 spezifiziert. Wenn das *Installer-Backend* beispielsweise Poetry ist und Sie mehrere Abhängigkeitsgruppen angeben möchten, könnten die Tabellen tool.poetry wie folgt aussehen:

[tool.poetry.dev-dependencies]
dependencies = "dev"

[tool.poetry.deploy]
dependencies = "deploy"

Daten können auch auf andere Weise gespeichert werden, wie es das Installer-Backend für richtig hält (z. B. eine separate Konfigurationsdatei).

Installer-Schnittstelle

Die *Installer-Schnittstelle* enthält obligatorische und optionale Hooks. Konforme *Installer-Backends* MÜSSEN die obligatorischen Hooks implementieren und DÜRFEN die optionalen Hooks implementieren. Ein *universeller Installer* KANN jeden der *Installer-Backend*-Hooks selbst implementieren, um sowohl als *universeller Installer* als auch als *Installer-Backend* zu fungieren, dies ist jedoch nicht erforderlich.

Alle Hooks akzeptieren **kwargs beliebige Parameter, die ein *Installer-Backend* benötigt, die nicht bereits spezifiziert sind, was Abwärtskompatibilität ermöglicht. Wenn unerwartete Parameter an das *Installer-Backend* übergeben werden, sollte es diese ignorieren.

Die folgenden Informationen sind analog zum entsprechenden Abschnitt in PEP 517. Die Hooks können mit Schlüsselwortargumenten aufgerufen werden, daher sollten *Installer-Backends*, die sie implementieren, darauf achten, dass ihre Signaturen sowohl der Reihenfolge als auch den Namen der obigen Argumente entsprechen.

Alle Hooks KÖNNEN beliebige informative Texte nach stdout und stderr ausgeben. Sie DÜRFEN NICHT von stdin lesen, und der *universelle Installer* KANN stdin vor dem Aufruf der Hooks schließen.

Der *universelle Installer* kann stdout und/oder stderr vom Backend erfassen. Wenn das Backend erkennt, dass ein Ausgabestrom kein Terminal/eine Konsole ist (z. B. nicht sys.stdout.isatty()), SOLLTE es sicherstellen, dass alle Ausgaben, die es in diesen Strom schreibt, in UTF-8 kodiert sind. Der *universelle Installer* DARF NICHT fehlschlagen, wenn die erfasste Ausgabe kein gültiges UTF-8 ist, aber er KANN in diesem Fall nicht alle Informationen erhalten (z. B. kann er mit dem Ersetzungsfehlerhandler in Python dekodieren). Wenn der Ausgabestrom ein Terminal ist, ist das *Installer-Backend* für die genaue Darstellung seiner Ausgabe verantwortlich, wie bei jedem Programm, das in einem Terminal ausgeführt wird.

Wenn ein Hook eine Ausnahme auslöst oder den Prozess zum Abbruch bringt, deutet dies auf einen Fehler hin.

Obligatorische Hooks

invoke_install

Installiert die Abhängigkeiten

def invoke_install(
    path: Union[str, bytes, PathLike[str]],
    *,
    dependency_group: str = None,
    **kwargs
) -> int:
    ...
  • path : Ein absoluter Pfad, von dem aus das *Installer-Backend* aufgerufen werden sollte (z. B. das Verzeichnis, in dem sich pyproject.toml befindet).
  • dependency_group : Ein optionales Flag, das eine Abhängigkeitsgruppe angibt, die das *Installer-Backend* installieren soll. Die Installation schlägt fehl, wenn die Abhängigkeitsgruppe nicht existiert. Ein Benutzer kann alle Abhängigkeitsgruppen finden, indem er get_dependency_groups() aufruft, wenn Abhängigkeitsgruppen vom *Installer-Backend* unterstützt werden.
  • **kwargs : Beliebige Parameter, die ein *Installer-Backend* benötigt, die nicht bereits spezifiziert sind, ermöglicht Abwärtskompatibilität.
  • Gibt zurück: Ein Exit-Code (int). 0 bei Erfolg, jeder positive Integer bei Misserfolg.

Der *universelle Installer* verwendet den Exit-Code, um zu bestimmen, ob die Installation erfolgreich war, und SOLLTE den Exit-Code selbst zurückgeben.

Optionale Hooks

invoke_uninstall

Deinstalliert die spezifizierten Abhängigkeiten

def invoke_uninstall(
    path: Union[str, bytes, PathLike[str]],
    *,
    dependency_group: str = None,
    **kwargs
) -> int:
    ...
  • path : Ein absoluter Pfad, von dem aus das *Installer-Backend* aufgerufen werden sollte (z. B. das Verzeichnis, in dem sich pyproject.toml befindet).
  • dependency_group : Ein optionales Flag, das eine Abhängigkeitsgruppe angibt, die das *Installer-Backend* deinstallieren soll.
  • **kwargs : Beliebige Parameter, die ein *Installer-Backend* benötigt, die nicht bereits spezifiziert sind, ermöglicht Abwärtskompatibilität.
  • Gibt zurück: Ein Exit-Code (int). 0 bei Erfolg, jeder positive Integer bei Misserfolg.

Der *universelle Installer* MUSS das *Installer-Backend* am selben Pfad aufrufen, an dem der *universelle Installer* selbst aufgerufen wurde.

Der *universelle Installer* verwendet den Exit-Code, um zu bestimmen, ob die Deinstallation erfolgreich war, und SOLLTE den Exit-Code selbst zurückgeben.

get_dependencies_to_install

Gibt die Abhängigkeiten zurück, die von invoke_install(...) installiert würden. Dies ermöglicht Paket-Aktualisierern (z. B. Dependabot), die zu installierenden Abhängigkeiten abzurufen, ohne die Abhängigkeitsdatei zu parsen.

def get_dependencies_to_install(
    path: Union[str, bytes, PathLike[str]],
    *,
    dependency_group: str = None,
    **kwargs
) -> Sequence[str]:
    ...
  • path : Ein absoluter Pfad, von dem aus das *Installer-Backend* aufgerufen werden sollte (z. B. das Verzeichnis, in dem sich pyproject.toml befindet).
  • dependency_group : Gibt eine Abhängigkeitsgruppe an, für die die von invoke_install(...) zu installierenden Abhängigkeiten abgerufen werden sollen.
  • **kwargs : Beliebige Parameter, die ein *Installer-Backend* benötigt, die nicht bereits spezifiziert sind, ermöglicht Abwärtskompatibilität.
  • Gibt zurück: Eine Liste von Abhängigkeiten (PEP 508-Strings), die installiert werden sollen.

Wenn die Gruppe angegeben ist, MUSS das *Installer-Backend* die Abhängigkeiten zurückgeben, die der angegebenen Abhängigkeitsgruppe entsprechen. Wenn die angegebene Gruppe nicht existiert oder Abhängigkeitsgruppen vom *Installer-Backend* nicht unterstützt werden, MUSS das *Installer-Backend* einen Fehler auslösen.

Wenn die Gruppe nicht angegeben ist und das *Installer-Backend* das Konzept einer Standard-/nicht spezifizierten Gruppe bietet, KANN das *Installer-Backend* die Abhängigkeiten für die Standard-/nicht spezifizierte Gruppe zurückgeben, muss aber andernfalls einen Fehler auslösen.

get_dependency_groups

Gibt die verfügbaren Abhängigkeitsgruppen zurück, die installiert werden können. Dies ermöglicht es *universellen Installern*, alle Abhängigkeitsgruppen aufzulisten, die dem *Installer-Backend* bekannt sind.

def get_dependency_groups(
    path: Union[str, bytes, PathLike[str]],
    **kwargs
) -> AbstractSet[str]:
    ...
  • path : Ein absoluter Pfad, von dem aus das *Installer-Backend* aufgerufen werden sollte (z. B. das Verzeichnis, in dem sich pyproject.toml befindet).
  • **kwargs : Beliebige Parameter, die ein *Installer-Backend* benötigt, die nicht bereits spezifiziert sind, ermöglicht Abwärtskompatibilität.
  • Gibt zurück: Eine Menge bekannter Abhängigkeitsgruppen als Strings. Die leere Menge repräsentiert keine Abhängigkeitsgruppen.

update_dependencies

Gibt eine Abhängigkeitsdatei basierend auf der eingegebenen Paketliste aus.

def update_dependencies(
    path: Union[str, bytes, PathLike[str]],
    dependency_specifiers: Iterable[str],
    *,
    dependency_group=None,
    **kwargs
) -> int:
    ...
  • path : Ein absoluter Pfad, von dem aus das *Installer-Backend* aufgerufen werden sollte (z. B. das Verzeichnis, in dem sich pyproject.toml befindet).
  • dependency_specifiers : Ein Iterable von Abhängigkeiten als PEP 508-Strings, die aktualisiert werden, z. B. : ["requests==2.8.1", ...]. Optional für eine bestimmte Abhängigkeitsgruppe.
  • dependency_group : Die Abhängigkeitsgruppe, für die die Liste der Pakete bestimmt ist.
  • **kwargs : Beliebige Parameter, die ein *Installer-Backend* benötigt, die nicht bereits spezifiziert sind, ermöglicht Abwärtskompatibilität.
  • Gibt zurück: Ein Exit-Code (int). 0 bei Erfolg, jeder positive Integer bei Misserfolg.

Beispiel

Betrachten wir die Implementierung eines *Installer-Backends*, das pip und seine Anforderungen für *Abhängigkeitsgruppen* verwendet. Eine Implementierung könnte (sehr grob) wie folgt aussehen:

import subprocess
import sys


def invoke_install(path, *, dependency_group=None, **kwargs):
    try:
        return subprocess.run(
            [
                sys.executable,
                "-m",
                "pip",
                "install",
                "-r",
                dependency_group or "requirements.txt",
            ],
            cwd=path,
        ).returncode
    except subprocess.CalledProcessError as e:
        return e.returncode

Wenn wir dieses Paket pep650pip nennen würden, könnten wir in pyproject.toml angeben:

[install-system]
  #Eg : pipenv
  requires = ["pep650pip", "pip"]
  install-backend = "pep650pip:main"

Begründung

Alle Hooks akzeptieren **kwargs, um Abwärtskompatibilität zu ermöglichen und Tool-spezifische *Installer-Backend*-Funktionalität zu erlauben, die vom Benutzer die Bereitstellung zusätzlicher Informationen erfordert, die vom Hook nicht gefordert werden.

Während *Installer-Backends* Python-Pakete sein müssen, ist das, was sie bei der Ausführung tun, ein Implementierungsdetail dieses Tools. Zum Beispiel kann ein *Installer-Backend* als Wrapper für einen Plattform-Paketmanager (z. B. apt) fungieren.

Die Schnittstelle versucht in keiner Weise zu spezifizieren, *wie* *Installer-Backends* funktionieren sollen. Dies geschieht bewusst, damit *Installer-Backends* innovativ sein und Probleme auf ihre eigene Weise lösen können. Dies bedeutet auch, dass diese PEP keine Haltung zu OS-Packaging einnimmt, da dies in den Zuständigkeitsbereich eines *Installer-Backends* fallen würde.

Das Definieren der API in Python bedeutet, dass *einige* Python-Code ausgeführt werden muss. Das schließt jedoch nicht aus, dass Nicht-Python-*Installer-Backends* verwendet werden (z. B. mamba), da diese als Subprozess von Python-Code ausgeführt werden könnten.

Abwärtskompatibilität

Diese PEP hätte keine Auswirkungen auf bestehenden Code und Funktionalität, da sie einem *universellen Installer* nur neue Funktionalität hinzufügt. Jeder bestehende Installer sollte seine bestehende Funktionalität und Anwendungsfälle beibehalten, wodurch keine Kompatibilitätsprobleme mit älteren Versionen entstehen. Nur Code, der beabsichtigt, diese neue Funktionalität zu nutzen, wird motiviert sein, Änderungen an seinem bestehenden Code vorzunehmen.

Sicherheitsimplikationen

Ein böswilliger Benutzer hat durch die Hinzufügung standardisierter Installer-Spezifikationen keine erhöhte Fähigkeit oder einfacheren Zugang zu irgendetwas. Der Installer, der von einem *universellen Installer* über die in dieser PEP spezifizierte Schnittstelle aufgerufen werden könnte, würde vom Benutzer explizit deklariert werden. Wenn der Benutzer einen bösartigen Installer gewählt hat, dann ist das Aufrufen mit einem *universellen Installer* nicht anders, als wenn der Benutzer den Installer direkt aufruft. Ein bösartiger Installer, der ein *Installer-Backend* ist, gibt ihm keine zusätzlichen Berechtigungen oder Fähigkeiten.

Abgelehnte Ideen

Eine standardisierte Lock-Datei

Eine standardisierte Lock-Datei würde viele der gleichen Probleme lösen, die auch die Spezifikation von Installer-Anforderungen löst. Zum Beispiel würde sie es PaaS/IaaS ermöglichen, nur einen Installer zu unterstützen, der die standardisierte Lock-Datei lesen könnte, unabhängig vom Installer, der sie erstellt hat. Das Problem mit einer standardisierten Lock-Datei sind die unterschiedlichen Bedürfnisse zwischen Python-Paket-Installern sowie ein grundlegendes Problem bei der Erstellung reproduzierbarer Umgebungen über die Lock-Datei (einer der Hauptvorteile).

Bedürfnisse und Informationen, die in Abhängigkeitsdateien zwischen Installern gespeichert werden, unterscheiden sich erheblich und sind von der Installer-Funktionalität abhängig. Zum Beispiel benötigt ein Python-Paket-Installer wie Poetry Informationen für alle Python-Versionen und Plattformen und berechnet entsprechende Hashes, während pip dies nicht tut. Zusätzlich könnte pip nicht garantieren, die gleiche Umgebung wiederherzustellen (exakt dieselben Abhängigkeiten zu installieren), da dies außerhalb seines Funktionsumfangs liegt. Dies macht eine standardisierte Lock-Datei schwerer zu implementieren und erscheint es sinnvoller, Lock-Dateien Tool-spezifisch zu gestalten.

Installer-Backends sollten die Erstellung von virtuellen Umgebungen unterstützen

Da *Installer-Backends* sehr wahrscheinlich ein Konzept von virtuellen Umgebungen und deren Installation haben, wurde kurz erwogen, ihnen auch die Unterstützung für die Erstellung virtueller Umgebungen zu geben. Letztendlich wurde dies jedoch als orthogonale Idee betrachtet.

Offene Fragen

Sollte das Argument dependency_group ein Iterable akzeptieren?

Dies würde die Angabe nicht überlappender Abhängigkeitsgruppen in einem einzigen Aufruf ermöglichen, z. B. die Gruppen „docs“ und „test“, die unabhängige Abhängigkeiten haben, die ein Entwickler jedoch möglicherweise gleichzeitig während der Entwicklung installieren möchte.

Wird das Installer-Backend In-Process ausgeführt?

Wenn das *Installer-Backend* In-Process ausgeführt wird, vereinfacht dies erheblich die Kenntnis, für welche Umgebung installiert wird/wird, da die laufende Python-Umgebung auf entsprechende Informationen abgefragt werden kann.

Die Ausführung außerhalb des Prozesses minimiert potenzielle Probleme mit Kollisionen zwischen der zu installierenden Umgebung und dem *Installer-Backend* (und möglicherweise dem *universellen Installer*).

Sicherstellen, dass die Ergebnisse der vorgeschlagenen Schnittstelle in andere Teile einfließen?

Z.B. können die Ergebnisse von get_dependencies_to_install() und get_dependency_groups() an invoke_install() übergeben werden. Dies würde Abweichungen zwischen den Ergebnissen verschiedener Teile der vorgeschlagenen Schnittstelle verhindern, macht aber mehr von der Schnittstelle obligatorisch statt optional.

Auslösen von Ausnahmen anstelle von Exit-Codes für Fehlerbedingungen

Es wurde vorgeschlagen, dass die API anstelle der Rückgabe eines Exit-Codes Ausnahmen auslösen sollte. Wenn Sie diese PEP als Hilfe zur Übersetzung aktueller Installer in *Installer-Backends* betrachten, dann ergibt die Verwendung von Exit-Codes Sinn. Es gibt auch den Punkt, dass die APIs keinen spezifischen Rückgabewert haben, sodass die Weitergabe eines Exit-Codes das, was die Funktionen zurückgeben, nicht beeinträchtigt.

Vergleichen Sie dies mit dem Auslösen von Ausnahmen im Fehlerfall. Dies könnte einen strukturierteren Ansatz zur Fehlerbehandlung bieten, obwohl zur Erfassung von Fehlern Ausnahmetypen als Teil der Schnittstelle spezifiziert werden müssten.


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

Zuletzt geändert: 2025-02-01 08:55:40 GMT