PEP 735 – Abhängigkeitsgruppen in pyproject.toml
- Autor:
- Stephen Rosen <sirosen0 at gmail.com>
- Sponsor:
- Brett Cannon <brett at python.org>
- PEP-Delegate:
- Paul Moore <p.f.moore at gmail.com>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Typ:
- Standards Track
- Thema:
- Packaging
- Erstellt:
- 20-Nov-2023
- Post-History:
- 14-Nov-2023, 20-Nov-2023
- Resolution:
- 10-Oct-2024
Inhaltsverzeichnis
- Zusammenfassung
- Motivation
- Begründung
- Spezifikation
- Referenzimplementierung
- Abwärtskompatibilität
- Sicherheitsimplikationen
- Wie man das lehrt
- Abgelehnte Ideen
- Warum nicht jede Abhängigkeitsgruppe als Tabelle definieren?
- Warum keine spezielle String-Syntax zur Erweiterung von Abhängigkeitsspezifizierern definieren?
- Warum nicht mehr Nicht-PEP 508 Abhängigkeitsspezifizierer zulassen?
- Warum heißt die Tabelle nicht
[run],[project.dependency-groups], …? - Warum ist Pips geplante Implementierung von
--only-depsnicht ausreichend? - Warum ist <Umgebungsmanager> keine Lösung?
- Zurückgestellte Ideen
- Anhang A: Vorherige Arbeiten in nicht-pythonischen Sprachen
- Anhang B: Vorherige Arbeiten in Python
- Anhang C: Anwendungsfälle
- Urheberrecht
Zusammenfassung
Diese PEP spezifiziert einen Mechanismus zur Speicherung von Paket-Anforderungen in pyproject.toml-Dateien, sodass sie in keiner erstellten Distribution des Projekts enthalten sind.
Dies eignet sich zur Erstellung benannter Gruppen von Abhängigkeiten, ähnlich wie requirements.txt-Dateien, die von Launchern, IDEs und anderen Werkzeugen gefunden und anhand ihres Namens identifiziert werden können.
Die hier definierte Funktion wird als „Abhängigkeitsgruppen“ bezeichnet.
Motivation
Es gibt zwei Hauptanwendungsfälle, für die die Python-Community keine standardisierte Antwort hat:
- Wie sollen Entwicklung Abhängigkeiten für Pakete definiert werden?
- Wie sollen Abhängigkeiten für Projekte definiert werden, die keine Distributionen erstellen (Nicht-Paket-Projekte)?
Zur Unterstützung dieser beiden Bedürfnisse gibt es zwei gängige Lösungen, die diesem Vorschlag ähneln:
requirements.txt-Dateien- Paket-Extras
Sowohl requirements.txt-Dateien als auch extras haben Einschränkungen, die dieser Standard zu überwinden sucht.
Beachten Sie, dass die beiden oben genannten Anwendungsfälle zwei verschiedene Arten von Projekten beschreiben, die diese PEP unterstützen möchte.
- Python-Pakete, wie z.B. Bibliotheken
- Nicht-Paket-Projekte, wie z.B. Data Science Projekte
Mehrere motivierende Anwendungsfälle sind im Anhang Anwendungsfälle im Detail aufgeführt.
Einschränkungen von requirements.txt-Dateien
Viele Projekte können eine oder mehrere requirements.txt-Dateien definieren und sie entweder im Stammverzeichnis des Projekts (z.B. requirements.txt und test-requirements.txt) oder in einem Verzeichnis (z.B. requirements/base.txt und requirements/test.txt) anordnen. Es gibt jedoch wesentliche Probleme bei der Verwendung von Anforderungsdateien auf diese Weise:
- Es gibt keine standardisierte Namenskonvention, so dass Werkzeuge diese Dateien anhand ihres Namens entdecken oder verwenden können.
requirements.txt-Dateien sind *nicht standardisiert*, sondern bieten Optionen fürpip.
Daher ist es schwierig, Werkzeugverhalten basierend auf requirements.txt-Dateien zu definieren. Sie sind nicht einfach zu entdecken oder nach Namen zu identifizieren, und ihr Inhalt kann eine Mischung aus Paket-Spezifizierern und zusätzlichen pip-Optionen enthalten.
Das Fehlen eines Standards für den Inhalt von requirements.txt bedeutet auch, dass sie nicht auf andere Werkzeuge portierbar sind, die sie verarbeiten möchten, abgesehen von pip.
Darüber hinaus erfordern requirements.txt-Dateien eine Datei pro Abhängigkeitsliste. Für einige Anwendungsfälle macht dies die Grenzkosten von Abhängigkeitsgruppierungen hoch im Verhältnis zu ihrem Nutzen. Eine knappere Deklaration ist für Projekte mit einer Anzahl kleiner Abhängigkeitsgruppen von Vorteil.
Im Gegensatz dazu werden Abhängigkeitsgruppen an einem bekannten Ort in pyproject.toml mit vollständig standardisiertem Inhalt definiert. Sie werden nicht nur sofort nutzbar sein, sondern auch als Ausgangspunkt für zukünftige Standards dienen.
Einschränkungen von extras
extras sind zusätzliche Paketmetadaten, die in der Tabelle [project.optional-dependencies] deklariert werden. Sie bieten Namen für Listen von Paket-Spezifizierern, die als Teil der Metadaten eines Pakets veröffentlicht werden und die ein Benutzer unter diesem Namen anfordern kann, wie z.B. pip install 'foo[bar]', um foo mit dem bar-Extra zu installieren.
Da extras Paketmetadaten sind, ist nicht garantiert, dass sie statisch definiert sind und möglicherweise ein Build-System zur Auflösung benötigen. Darüber hinaus deutet die Definition von [project.optional-dependencies] für viele Werkzeuge darauf hin, dass ein Projekt ein Paket ist, und kann Werkzeugverhalten auslösen, wie z.B. die Validierung der [project]-Tabelle.
Für Projekte, die Pakete sind, sind extras eine gängige Lösung zur Definition von Entwicklungsabhängigkeiten, aber auch unter diesen Umständen haben sie Nachteile:
- Da ein
extraoptionale *zusätzliche* Abhängigkeiten definiert, ist es nicht möglich, einextrazu installieren, ohne das aktuelle Paket und seine Abhängigkeiten zu installieren. - Da sie für Benutzer installierbar sind, sind
extrasTeil der öffentlichen Schnittstelle von Paketen. Daextrasveröffentlicht werden, sind Paketentwickler oft besorgt darüber, sicherzustellen, dass ihre Entwicklungs-Extras nicht mit benutzerorientierten Extras verwechselt werden.
Begründung
Diese PEP definiert die Speicherung von Anforderungsdaten in Listen innerhalb einer Tabelle [dependency-groups]. Dieser Name wurde gewählt, um dem kanonischen Namen der Funktion („Dependency Groups“) zu entsprechen.
Dieses Format sollte so einfach und erlernbar wie möglich sein und ein Format haben, das in vielen Fällen requirements.txt-Dateien sehr ähnlich ist. Jede Liste in [dependency-groups] wird als Liste von Paket-Spezifizierern definiert. Zum Beispiel:
[dependency-groups]
test = ["pytest>7", "coverage"]
Es gibt eine Reihe von Anwendungsfällen für requirements.txt-Dateien, die Daten erfordern, die nicht in PEP 508 Abhängigkeitsspezifizierern ausgedrückt werden können. Solche Felder sind in Abhängigkeitsgruppen nicht gültig. Die Einbeziehung vieler Daten und Felder, die pip unterstützt, wie z.B. Index-Server, Hashes und Pfadabhängigkeiten, erfordert neue Standards. Dieser Standard lässt Raum für neue Standards und Entwicklungen, versucht aber nicht, alle gültigen requirements.txt-Inhalte zu unterstützen.
Die einzige Ausnahme ist das Flag -r, das requirements.txt-Dateien verwenden, um eine Datei in eine andere einzubinden. Abhängigkeitsgruppen unterstützen einen „include“-Mechanismus, der in seiner Bedeutung ähnlich ist und es einer Abhängigkeitsgruppe erlaubt, eine andere zu erweitern.
Abhängigkeitsgruppen haben zwei zusätzliche Funktionen, die requirements.txt-Dateien ähneln:
- sie werden nicht als separate Metadaten in irgendeiner erstellten Distribution veröffentlicht
- Die Installation einer Abhängigkeitsgruppe impliziert nicht die Installation der Abhängigkeiten eines Pakets (oder des Pakets selbst)
Anwendungsfälle
Die folgenden Anwendungsfälle werden als wichtige Ziele für diese PEP betrachtet. Sie sind im Anhang Anwendungsfälle detaillierter definiert.
- Webanwendungen, die über einen Nicht-Python-Packaging-Build-Prozess bereitgestellt werden
- Bibliotheken mit nicht veröffentlichten Entwicklungsabhängigkeitsgruppen
- Data Science Projekte mit Abhängigkeitsgruppen, aber ohne Kernpaket
- Eingabedaten für die Lockfile-Generierung (Abhängigkeitsgruppen sollten im Allgemeinen nicht als Speicherort für gesperrte Abhängigkeitsdaten verwendet werden)
- Eingabedaten für einen Umgebungsmanager wie tox, Nox oder Hatch
- Konfigurierbare IDE-Erkennung von Test- und Linter-Anforderungen
Zu Poetry und PDM Abhängigkeitsgruppen
Die bestehenden Werkzeuge Poetry und PDM bieten bereits eine Funktion, die jeder „Abhängigkeitsgruppen“ nennt. In Ermangelung eines Standards für die Angabe von Abhängigkeits-Sammlungen definiert jedes Werkzeug diese auf eine werkzeugspezifische Weise in den entsprechenden Abschnitten der [tool]-Tabelle.
(PDM verwendet auch Extras für einige Abhängigkeitsgruppen und überschneidet die Vorstellung stark mit Extras.)
Diese PEP unterstützt nicht alle Funktionen von Poetry und PDM, die, wie requirements.txt-Dateien für pip, mehrere nicht standardmäßige Erweiterungen gängiger Abhängigkeits-Spezifizierer unterstützen.
Es sollte möglich sein, dass solche Werkzeuge standardisierte Abhängigkeitsgruppen als Erweiterungen ihrer eigenen Abhängigkeitsgruppen-Mechanismen nutzen. Die Definition eines neuen Datenformats, das die bestehenden Poetry- und PDM-Lösungen ersetzt, ist jedoch kein Ziel. Dies würde die Standardisierung mehrerer zusätzlicher Funktionen erfordern, wie z.B. Pfadabhängigkeiten, die von diesen Werkzeugen unterstützt werden.
Zukunftskompatibilität & Ungültige Daten
Abhängigkeitsgruppen sind dafür vorgesehen, in zukünftigen PEPs erweiterbar zu sein. Abhängigkeitsgruppen sollten jedoch auch von mehreren Werkzeugen in einem einzigen Python-Projekt nutzbar sein. Mit mehreren Werkzeugen, die dieselben Daten verwenden, ist es möglich, dass eines eine zukünftige PEP implementiert, die Abhängigkeitsgruppen erweitert, während ein anderes dies nicht tut.
Um Benutzer in diesem Fall zu unterstützen, definiert und empfiehlt diese PEP Validierungsverhalten, bei dem Werkzeuge nur die Abhängigkeitsgruppen prüfen, die sie verwenden. Dies ermöglicht es mehreren Werkzeugen, die unterschiedliche Versionen von Abhängigkeitsgruppen-Daten verwenden, eine einzige Tabelle in pyproject.toml zu teilen.
Spezifikation
Diese PEP definiert einen neuen Abschnitt (Tabelle) in pyproject.toml-Dateien namens dependency-groups. Die Tabelle dependency-groups enthält eine beliebige Anzahl von benutzerdefinierten Schlüsseln, von denen jeder als Wert eine Liste von Anforderungen (unten definiert) hat. Diese Schlüssel müssen gültige nicht normalisierte Namen sein und müssen vor Vergleichen normalisiert werden.
Werkzeuge SOLLTEN es bevorzugen, den ursprünglichen, nicht normalisierten Namen standardmäßig den Benutzern anzuzeigen. Wenn doppelte Namen nach der Normalisierung gefunden werden, SOLLTEN Werkzeuge einen Fehler ausgeben.
Anforderungslisten unter dependency-groups können Zeichenketten, Tabellen („Dictionaries“ in Python) oder eine Mischung aus Zeichenketten und Tabellen enthalten.
Zeichenketten in Anforderungslisten müssen gültige Abhängigkeits-Spezifizierer sein, wie in PEP 508 definiert.
Tabellen in Anforderungslisten müssen gültige Abhängigkeitsobjektspezifizierer sein.
Abhängigkeitsobjektspezifizierer
Abhängigkeitsobjektspezifizierer sind Tabellen, die null oder mehr Abhängigkeiten definieren.
Diese PEP standardisiert nur einen Typ von Abhängigkeitsobjektspezifizierer, eine „Abhängigkeitsgruppen-Einbindung“. Andere Typen können in zukünftigen Standards hinzugefügt werden.
Abhängigkeitsgruppen-Einbindung
Eine Abhängigkeitsgruppen-Einbindung schließt die Abhängigkeiten einer anderen Abhängigkeitsgruppe in die aktuelle Abhängigkeitsgruppe ein.
Eine Einbindung wird als eine Tabelle mit genau einem Schlüssel, "include-group", definiert, dessen Wert eine Zeichenkette ist, der Name einer anderen Abhängigkeitsgruppe.
Zum Beispiel ist {include-group = "test"} eine Einbindung, die sich zur Inhaltsmenge der test-Abhängigkeitsgruppe erweitert.
Einbindungen sind genau gleichwertig mit dem Inhalt der benannten Abhängigkeitsgruppe definiert und werden an der Stelle der Einbindung in die aktuelle Gruppe eingefügt. Wenn beispielsweise foo = ["a", "b"] eine Gruppe ist und bar = ["c", {include-group = "foo"}, "d"] eine andere, dann sollte bar zu ["c", "a", "b", "d"] ausgewertet werden, wenn Abhängigkeitsgruppen-Einbindungen expandiert werden.
Abhängigkeitsgruppen-Einbindungen können denselben Paket mehrmals angeben. Werkzeuge SOLLTEN die durch die Einbindung erzeugten Listeninhalte nicht deduplizieren oder anderweitig verändern. Zum Beispiel bei der folgenden Tabelle:
[dependency-groups]
group-a = ["foo"]
group-b = ["foo>1.0"]
group-c = ["foo<1.0"]
all = ["foo", {include-group = "group-a"}, {include-group = "group-b"}, {include-group = "group-c"}]
Der aufgelöste Wert von all SOLLTE ["foo", "foo", "foo>1.0", "foo<1.0"] sein. Werkzeuge sollten eine solche Liste genauso behandeln wie jeden anderen Fall, in dem sie gebeten werden, dieselbe Anforderung mehrmals mit unterschiedlichen Versionsbeschränkungen zu verarbeiten.
Abhängigkeitsgruppen-Einbindungen können Listen enthalten, die Abhängigkeitsgruppen-Einbindungen enthalten. In diesem Fall sollten diese Einbindungen ebenfalls expandiert werden. Abhängigkeitsgruppen-Einbindungen DÜRFEN keine Zyklen enthalten, und Werkzeuge SOLLTEN einen Fehler melden, wenn sie einen Zyklus erkennen.
Beispiel: Tabelle für Abhängigkeitsgruppen
Das Folgende ist ein Beispiel für eine teilweise pyproject.toml, die dies zur Definition von vier Abhängigkeitsgruppen verwendet: test, docs, typing und typing-test
[dependency-groups]
test = ["pytest", "coverage"]
docs = ["sphinx", "sphinx-rtd-theme"]
typing = ["mypy", "types-requests"]
typing-test = [{include-group = "typing"}, {include-group = "test"}, "useful-types"]
Beachten Sie, dass keine dieser Abhängigkeitsgruppen-Deklarationen implizit das aktuelle Paket, seine Abhängigkeiten oder optionale Abhängigkeiten installiert. Die Verwendung einer Abhängigkeitsgruppe wie test zum Testen eines Pakets erfordert, dass die Konfiguration oder Toolchain des Benutzers auch das aktuelle Paket (.) installiert. Zum Beispiel:
$TOOL install-dependency-group test
pip install -e .
könnte verwendet werden (vorausgesetzt, $TOOL ist ein Werkzeug, das die Installation von Abhängigkeitsgruppen unterstützt), um eine Testumgebung aufzubauen.
Dies ermöglicht es auch, dass die Abhängigkeitsgruppe docs verwendet werden kann, ohne das Projekt als Paket zu installieren.
$TOOL install-dependency-group docs
Paketbau
Build-Backends DÜRFEN keine Abhängigkeitsgruppen-Daten in erstellten Distributionen als Paketmetadaten einfügen. Das bedeutet, dass PKG-INFO in sdists und METADATA in wheels keine referenzierbaren Felder enthalten, die Abhängigkeitsgruppen enthalten.
Es ist gültig, Abhängigkeitsgruppen bei der Auswertung dynamischer Metadaten zu verwenden, und pyproject.toml-Dateien, die in sdists enthalten sind, enthalten natürlich weiterhin die Tabelle [dependency-groups]. Die Tabelleninhalte sind jedoch kein Teil der Schnittstellen eines veröffentlichten Pakets.
Installation von Abhängigkeitsgruppen
Von Werkzeugen, die Abhängigkeitsgruppen unterstützen, wird erwartet, dass sie neue Optionen und Schnittstellen bereitstellen, um Benutzern die Installation aus Abhängigkeitsgruppen zu ermöglichen.
Es wird keine Syntax für die Darstellung der Abhängigkeitsgruppen eines Pakets definiert, aus zwei Gründen:
- es wäre nicht gültig, auf die Abhängigkeitsgruppen eines Drittanbieterpakets von PyPI zu verweisen (da die Daten als unveröffentlicht definiert sind)
- es gibt keine Garantie, dass es ein aktuelles Paket für Abhängigkeitsgruppen gibt – ein Teil ihres Zwecks ist die Unterstützung von Nicht-Paket-Projekten
Zum Beispiel wäre eine mögliche Pip-Schnittstelle zur Installation von Abhängigkeitsgruppen:
pip install --dependency-groups=test,typing
Beachten Sie, dass dies nur ein Beispiel ist. Diese PEP definiert keine Anforderungen, wie Werkzeuge die Installation von Abhängigkeitsgruppen unterstützen.
Überlappende Installations-UX mit Extras
Werkzeuge KÖNNEN wählen, dieselben Schnittstellen für die Installation von Abhängigkeitsgruppen bereitzustellen, wie sie für die Installation von Extras bereitstellen.
Beachten Sie, dass diese Spezifikation nicht verbietet, ein Extra zu haben, dessen Name mit einer Abhängigkeitsgruppe übereinstimmt.
Benutzern wird geraten, das Erstellen von Abhängigkeitsgruppen, deren Namen mit Extras übereinstimmen, zu vermeiden. Werkzeuge KÖNNEN eine solche Übereinstimmung als Fehler behandeln.
Validierung und Kompatibilität
Werkzeuge, die Abhängigkeitsgruppen unterstützen, möchten möglicherweise Daten validieren, bevor sie sie verwenden. Werkzeuge, die ein solches Validierungsverhalten implementieren, sollten jedoch darauf achten, zukünftige Erweiterungen dieser Spezifikation zuzulassen, damit sie nicht unnötigerweise Fehler oder Warnungen bei Vorhandensein neuer Syntax ausgeben.
Werkzeuge SOLLTEN einen Fehler ausgeben, wenn sie nicht erkannte Daten in Abhängigkeitsgruppen auswerten oder verarbeiten.
Werkzeuge SOLLTEN die Listeninhalte von *allen* Abhängigkeitsgruppen nicht eifrig validieren.
Dies bedeutet, dass bei Vorhandensein der folgenden Daten die meisten Werkzeuge die Verwendung der Gruppe foo zulassen und nur einen Fehler ausgeben, wenn die Gruppe bar verwendet wird.
[dependency-groups]
foo = ["pyparsing"]
bar = [{set-phasers-to = "stun"}]
Linter und Validatoren können strenger sein
Eifrige Validierung wird für Werkzeuge, die hauptsächlich Abhängigkeitsgruppen installieren oder auflösen, nicht empfohlen. Linter und Validierungswerkzeuge können gute Gründe haben, diese Empfehlung zu ignorieren.
Referenzimplementierung
Die folgende Referenzimplementierung gibt den Inhalt einer Abhängigkeitsgruppe mit Zeilenumbruch getrennt auf stdout aus. Die Ausgabe ist daher eine gültige requirements.txt-Datenquelle.
import re
import sys
import tomllib
from collections import defaultdict
from packaging.requirements import Requirement
def _normalize_name(name: str) -> str:
return re.sub(r"[-_.]+", "-", name).lower()
def _normalize_group_names(dependency_groups: dict) -> dict:
original_names = defaultdict(list)
normalized_groups = {}
for group_name, value in dependency_groups.items():
normed_group_name = _normalize_name(group_name)
original_names[normed_group_name].append(group_name)
normalized_groups[normed_group_name] = value
errors = []
for normed_name, names in original_names.items():
if len(names) > 1:
errors.append(f"{normed_name} ({', '.join(names)})")
if errors:
raise ValueError(f"Duplicate dependency group names: {', '.join(errors)}")
return normalized_groups
def _resolve_dependency_group(
dependency_groups: dict, group: str, past_groups: tuple[str, ...] = ()
) -> list[str]:
if group in past_groups:
raise ValueError(f"Cyclic dependency group include: {group} -> {past_groups}")
if group not in dependency_groups:
raise LookupError(f"Dependency group '{group}' not found")
raw_group = dependency_groups[group]
if not isinstance(raw_group, list):
raise ValueError(f"Dependency group '{group}' is not a list")
realized_group = []
for item in raw_group:
if isinstance(item, str):
# packaging.requirements.Requirement parsing ensures that this is a valid
# PEP 508 Dependency Specifier
# raises InvalidRequirement on failure
Requirement(item)
realized_group.append(item)
elif isinstance(item, dict):
if tuple(item.keys()) != ("include-group",):
raise ValueError(f"Invalid dependency group item: {item}")
include_group = _normalize_name(next(iter(item.values())))
realized_group.extend(
_resolve_dependency_group(
dependency_groups, include_group, past_groups + (group,)
)
)
else:
raise ValueError(f"Invalid dependency group item: {item}")
return realized_group
def resolve(dependency_groups: dict, group: str) -> list[str]:
if not isinstance(dependency_groups, dict):
raise TypeError("Dependency Groups table is not a dict")
if not isinstance(group, str):
raise TypeError("Dependency group name is not a str")
return _resolve_dependency_group(dependency_groups, group)
if __name__ == "__main__":
with open("pyproject.toml", "rb") as fp:
pyproject = tomllib.load(fp)
dependency_groups_raw = pyproject["dependency-groups"]
dependency_groups = _normalize_group_names(dependency_groups_raw)
print("\n".join(resolve(pyproject["dependency-groups"], sys.argv[1])))
Abwärtskompatibilität
Zum Zeitpunkt der Erstellung ist der Namensraum dependency-groups innerhalb einer pyproject.toml-Datei ungenutzt. Da der Top-Level-Namensraum nur für Standards, die auf packaging.python.org spezifiziert sind, reserviert ist, gibt es keine direkten Rückwärtskompatibilitätsprobleme.
Die Einführung der Funktion hat jedoch Auswirkungen auf eine Reihe von Ökosystem-Werkzeugen, insbesondere auf solche, die versuchen, die Untersuchung von Daten in setup.py und requirements.txt zu unterstützen.
Audit- und Update-Tools
Eine breite Palette von Werkzeugen versteht Python-Abhängigkeitsdaten, wie sie in requirements.txt-Dateien ausgedrückt werden. (z.B. Dependabot, Tidelift usw.)
Solche Werkzeuge inspizieren Abhängigkeitsdaten und bieten in einigen Fällen werkzeuggestützte oder vollständig automatisierte Updates. Wir erwarten, dass keine solchen Werkzeuge die neuen Abhängigkeitsgruppen zunächst unterstützen werden, und eine breite Ökosystem-Unterstützung könnte viele Monate oder sogar einige Jahre dauern, bis sie eintrifft.
Daher würden Benutzer von Abhängigkeitsgruppen zum Zeitpunkt der Verwendung von Abhängigkeitsgruppen eine Verschlechterung ihrer Arbeitsabläufe und Werkzeugunterstützung erfahren. Dies gilt für jeden neuen Standard, wo und wie Abhängigkeitsdaten kodiert werden.
Sicherheitsimplikationen
Diese PEP führt neue Syntaxen und Datenformate zur Angabe von Abhängigkeitsinformationen in Projekten ein. Sie führt jedoch keine neu spezifizierten Mechanismen zur Handhabung oder Auflösung von Abhängigkeiten ein.
Sie birgt daher keine Sicherheitsbedenken außer denen, die in allen Werkzeugen enthalten sind, die bereits zur Installation von Abhängigkeiten verwendet werden könnten – d.h., bösartige Abhängigkeiten können hier spezifiziert werden, genauso wie sie in requirements.txt-Dateien spezifiziert werden können.
Wie man das lehrt
Diese Funktion sollte unter ihrem kanonischen Namen „Abhängigkeitsgruppen“ bezeichnet werden.
Die grundlegende Nutzungsform sollte als Variante der typischen requirements.txt-Daten gelehrt werden. Standard-Abhängigkeits-Spezifizierer (PEP 508) können einer benannten Liste hinzugefügt werden. Anstatt Pip aufzufordern, aus einer requirements.txt-Datei zu installieren, wird entweder Pip oder ein relevantes Workflow-Tool aus einer benannten Abhängigkeitsgruppe installieren.
Für neue Python-Benutzer können sie direkt angewiesen werden, einen Abschnitt in pyproject.toml mit ihren Abhängigkeitsgruppen zu erstellen, ähnlich wie sie derzeit angewiesen werden, requirements.txt-Dateien zu verwenden. Dies ermöglicht es neuen Python-Benutzern auch, etwas über pyproject.toml-Dateien zu lernen, ohne Paket-Erstellung lernen zu müssen. Eine pyproject.toml-Datei mit nur [dependency-groups] und keinen anderen Tabellen ist gültig.
Sowohl für neue als auch für erfahrene Benutzer müssen die Abhängigkeitsgruppen-Einbindungen erklärt werden. Für Benutzer mit Erfahrung in der Verwendung von requirements.txt kann dies als Analogie zu -r beschrieben werden. Für neue Benutzer sollten sie lernen, dass eine Einbindung es einer Abhängigkeitsgruppe erlaubt, eine andere zu erweitern. Ähnliche Konfigurationsschnittstellen und die Python-Methode list.extend können verwendet werden, um die Idee analog zu erklären.
Python-Benutzer, die setup.py-Packaging verwendet haben, sind möglicherweise mit gängigen Praktiken vertraut, die pyproject.toml vorausgehen, bei denen Paketmetadaten dynamisch definiert werden. Anforderungen, die aus requirements.txt-Dateien geladen werden, und Definitionen statischer Listen vor dem Aufruf von setup() sind leicht mit Abhängigkeitsgruppen analogisierbar.
Schnittstellen zur Nutzung von Abhängigkeitsgruppen
Diese Spezifikation bietet keine universelle Schnittstelle für die Interaktion mit Abhängigkeitsgruppen, außer der Einbeziehung in ein erstelltes Paket über die project-Tabelle. Dies hat Auswirkungen sowohl auf Werkzeugentwickler als auch auf Benutzer.
Werkzeugentwickler sollten bestimmen, wie oder ob Abhängigkeitsgruppen für ihre User Stories relevant sind und ihre eigenen Schnittstellen erstellen, die dazu passen. Für Umgebungsmanager, Resolver, Installer und verwandte Nicht-Build-Tools können sie dokumentieren, dass sie „PEP 735 Abhängigkeitsgruppen“ unterstützen, sind aber für die Dokumentation ihrer Nutzungsmodi verantwortlich. Für Build-Backends erfordert die Unterstützung von Abhängigkeitsgruppen die Unterstützung der Einbeziehung aus der project-Tabelle, schreibt aber keine anderen strengen Anforderungen vor.
Für Benutzer ist die primäre Konsequenz, dass sie die jeweilige Werkzeugdokumentation konsultieren müssen, wann immer sie Abhängigkeitsgruppen außerhalb von Paket-Builds verwenden möchten. Benutzer sollten von Werkzeugen, entweder durch Dokumentation oder Laufzeitwarnungen oder -fehler, über Verwendungen informiert werden, die nicht empfohlen oder nicht unterstützt werden. Wenn ein Werkzeug beispielsweise verlangt, dass alle Abhängigkeitsgruppen gegenseitig kompatibel sind und keine widersprüchlichen Paketspezifizierer enthalten, sollte es diese Einschränkung dokumentieren und Benutzer informieren, wie sie Abhängigkeitsgruppen für seine Zwecke richtig nutzen.
Abgelehnte Ideen
Warum nicht jede Abhängigkeitsgruppe als Tabelle definieren?
Wenn unser Ziel die Erweiterbarkeit in der Zukunft ist, dann ermöglicht die Definition jeder Abhängigkeitsgruppe als Untertabelle, wodurch wir zukünftige Schlüssel an jede Gruppe anhängen können, die größte zukünftige Flexibilität.
Dies macht die Struktur jedoch tiefer verschachtelt und daher schwieriger zu lehren und zu lernen. Eines der Ziele dieser PEP ist es, ein einfacher Ersatz für viele requirements.txt-Anwendungsfälle zu sein.
Warum keine spezielle String-Syntax zur Erweiterung von Abhängigkeitsspezifizierern definieren?
Frühere Entwürfe dieser Spezifikation definierten syntaktische Formen für Abhängigkeitsgruppen-Einbindungen und Pfadabhängigkeiten.
Es gab jedoch drei Hauptprobleme bei diesem Ansatz:
- es verkompliziert die Zeichenketten-Syntax, die gelehrt werden muss, über PEP 508 hinaus
- die resultierenden Zeichenketten müssten immer von PEP 508-Spezifizierern disambiguiert werden, was die Implementierungen verkompliziert
Warum nicht mehr Nicht-PEP 508 Abhängigkeitsspezifizierer zulassen?
Mehrere Anwendungsfälle, die während der Diskussion aufkamen, benötigen expressivere Spezifizierer als mit PEP 508 möglich sind.
„Pfadabhängigkeiten“, die sich auf lokale Pfade beziehen, und Verweise auf [project.dependencies] waren von besonderem Interesse.
Es gibt jedoch keine bestehenden Standards für diese Funktionen (abgesehen vom De-facto-Standard der Implementierungsdetails von pip).
Als Ergebnis führt der Versuch, diese Funktionen in diese PEP aufzunehmen, zu einer erheblichen Ausweitung des Umfangs, um zu versuchen, diese verschiedenen Funktionen und pip-Verhaltensweisen zu standardisieren.
Besondere Aufmerksamkeit wurde dem Versuch gewidmet, die Angabe von Editier-Installationen zu standardisieren, wie sie von pip install -e und PEP 660 ausgedrückt werden. Obwohl die Erstellung von Editier-Installationen für Build-Backends standardisiert ist, ist das Verhalten von Editier-Installationen für Installer nicht standardisiert. Die Aufnahme von Editier-Installationen in diese PEP erfordert, dass jedes unterstützende Werkzeug die Installation von Editier-Installationen ermöglicht.
Daher werden, obwohl Poetry und PDM Syntaxen für einige dieser Features bereitstellen, diese derzeit als nicht ausreichend standardisiert für die Aufnahme in Dependency Groups angesehen.
Warum heißt die Tabelle nicht [run], [project.dependency-groups], …?
Es gibt viele mögliche Namen für dieses Konzept. Es wird neben den bereits existierenden Tabellen [project.dependencies] und [project.optional-dependencies] bestehen müssen, und möglicherweise auch einer neuen Tabelle für externe Abhängigkeiten [external] (zum Zeitpunkt der Erstellung ist PEP 725, die die Tabelle [external] definiert, in Bearbeitung).
[run] war ein führender Vorschlag in früheren Diskussionen, aber seine vorgeschlagene Verwendung konzentrierte sich auf einen einzigen Satz von Laufzeitabhängigkeiten. Dieses PEP umreißt explizit mehrere Gruppen von Abhängigkeiten, was [run] zu einer weniger geeigneten Wahl macht – dies sind nicht nur Abhängigkeitsdaten für einen bestimmten Laufzeitkontext, sondern für mehrere Kontexte.
[project.dependency-groups] würde eine schöne Parallele zu [project.dependencies] und [project.optional-dependencies] bieten, hat aber erhebliche Nachteile für Nicht-Paket-Projekte. [project] erfordert die Definition mehrerer Schlüssel wie name und version. Die Verwendung dieses Namens würde entweder eine Neudefinition der Tabelle [project] erfordern, um das Fehlen dieser Schlüssel zu erlauben, oder Nicht-Paket-Projekte dazu zwingen, diese Schlüssel zu definieren und zu verwenden. Dadurch würde es effektiv erforderlich, dass jedes Nicht-Paket-Projekt sich selbst als Paket behandeln lässt.
Warum ist Pips geplante Implementierung von --only-deps nicht ausreichend?
pip hat derzeit eine Funktion auf der Roadmap, die ein Flag –only-deps hinzufügt. Dieses Flag soll es Benutzern ermöglichen, Paketabhängigkeiten und Extras zu installieren, ohne das aktuelle Paket zu installieren.
Es adressiert nicht die Bedürfnisse von Nicht-Paket-Projekten und erlaubt auch nicht die Installation eines Extras ohne die Paketabhängigkeiten.
Warum ist <Umgebungsmanager> keine Lösung?
Bestehende Umgebungsmanager wie tox, Nox und Hatch haben bereits die Fähigkeit, inline Abhängigkeiten als Teil ihrer Konfigurationsdaten aufzulisten. Dies erfüllt viele Bedürfnisse an Entwicklungabhängigkeiten und ordnet Abhängigkeitsgruppen klar den relevanten auszuführbaren Aufgaben zu. Diese Mechanismen sind *gut*, aber sie sind nicht *ausreichend*.
Erstens adressieren sie nicht die Bedürfnisse von Nicht-Paket-Projekten.
Zweitens gibt es keinen Standard, den andere Werkzeuge verwenden können, um auf diese Daten zuzugreifen. Dies hat Auswirkungen auf High-Level-Tools wie IDEs und Dependabot, die keine tiefe Integration mit diesen Dependency Groups unterstützen können. (Zum Beispiel wird Dependabot zum Zeitpunkt der Erstellung keine Abhängigkeiten markieren, die in tox.ini-Dateien angepinnt sind.)
Zurückgestellte Ideen
Warum Abhängigkeitsgruppen-Einbindungen nicht in [project.dependencies] oder [project.optional-dependencies] unterstützen?
Frühere Entwürfe dieser Spezifikation erlaubten die Verwendung von Dependency Group Includes in der Tabelle [project]. Während des Community-Feedbacks wurden jedoch mehrere Probleme angesprochen, die zu seiner Entfernung führten.
Nur eine kleine Anzahl zusätzlicher Anwendungsfälle würde durch die Aufnahme von Dependency Groups abgedeckt, und dies erhöhte den Umfang der Spezifikation erheblich. Insbesondere würde diese Aufnahme die Anzahl der beteiligten Parteien erhöhen. Viele Leser der Tabelle [project], einschließlich Build-Backends, SBOM-Generatoren und Abhängigkeitsanalysatoren, sind von einer Änderung an [project] betroffen, können aber bei einer neuen (aber nicht verbundenen) Tabelle [dependency-groups] weiterhin wie bisher funktionieren.
Getrennt von der obigen Bedenken ermutigt die Erlaubnis zur Aufnahme von Abhängigkeitsgruppen aus der Tabelle [project] Paketbetreuer, Abhängigkeitsmetadaten vom aktuellen Standardspeicherort zu verschieben. Dies erschwert statische pyproject.toml Metadaten und steht im Widerspruch zum Ziel von PEP 621, Abhängigkeitsmetadaten an einem einzigen Ort zu speichern.
Schließlich ist der Ausschluss der Unterstützung für [project] aus diesem PEP nicht endgültig. Die Verwendung von Includes aus dieser Tabelle oder eine Include-Syntax von [dependency-groups] in [project] könnte durch ein zukünftiges PEP eingeführt und eigenständig bewertet werden.
Anwendungsfälle für Abhängigkeitsgruppen-Einbindungen aus [project]
Obwohl in diesem PEP zurückgestellt, würde die Zulassung von Includes aus der Tabelle [project] mehrere Anwendungsfälle abdecken.
Insbesondere gibt es Fälle, in denen Paketentwickler nur die Abhängigkeiten eines Pakets installieren möchten, ohne das Paket selbst.
Zum Beispiel:
- Unterschiedliche Umgebungsvariablen oder Optionen beim Erstellen von Abhängigkeiten im Vergleich zum Erstellen des Pakets selbst angeben
- Erstellung von geschichteten Container-Images, bei denen die Abhängigkeiten vom zu installierenden Paket isoliert sind
- Bereitstellung der Abhängigkeiten für Analyseumgebungen (z.B. Typüberprüfung), ohne das Paket selbst erstellen und installieren zu müssen
Als Beispiel für den letzten Fall betrachten Sie die folgende Beispiel pyproject.toml
[project]
dependencies = [{include = "runtime"}]
[optional-dependencies]
foo = [{include = "foo"}]
[dependency-groups]
runtime = ["a", "b"]
foo = ["c", "d"]
typing = ["mypy", {include = "runtime"}, {include = "foo"}]
In diesem Fall kann eine Gruppe typing definiert werden, mit allen Laufzeitabhängigkeiten des Pakets, aber ohne das Paket selbst. Dies ermöglicht es Verwendungen der typing Dependency Group, die Installation des Pakets zu überspringen – dies ist nicht nur effizienter, sondern kann auch die Anforderungen an Testsysteme reduzieren.
Warum Abhängigkeitsgruppen-Einbindungen nicht in [build-system.requires] unterstützen?
Da wir die Verwendung von Dependency Groups in [project] nicht zulassen werden, kann [build-system.requires] im Vergleich zu [project.dependencies] betrachtet werden.
Es gibt weniger theoretische Anwendungsfälle für Build-Anforderungen, die in einer Gruppe spezifiziert sind, als für Paket-Anforderungen. Darüber hinaus impliziert die Auswirkung einer solchen Änderung den PEP 517 Frontend, das Dependency Groups unterstützen müsste, um eine Build-Umgebung vorzubereiten.
Im Vergleich zu Änderungen an [project.dependencies] und [project.optional-dependencies] hat die Änderung des Verhaltens von [build-system.requires] eine höhere Auswirkung und weniger potenzielle Nutzungen. Daher wird, da dieses PEP keine Änderungen an der Tabelle [project] vornimmt, auch die Änderung von [build-system] zurückgestellt.
Warum keine Abhängigkeitsgruppe unterstützen, die das aktuelle Projekt einschließt?
Mehrere Anwendungsfälle für Dependency Groups beinhalten die Installation einer Dependency Group zusammen mit einem Paket, das in der Tabelle [project] definiert ist. Zum Beispiel beinhaltet das Testen eines Pakets die Installation von Testabhängigkeiten und des Pakets selbst. Darüber hinaus ist die Kompatibilität einer Dependency Group mit dem Hauptpaket eine wertvolle Eingabe für Lockfile-Generatoren.
In solchen Fällen ist es wünschenswert, dass eine Dependency Group deklariert, dass sie vom Projekt selbst abhängt. Beispiel-Syntaxes aus Diskussionen waren {include-project = true} und {include-group = ":project:"}.
Wenn jedoch eine Spezifikation zur Erweiterung von PEP 508 um Pfadabhängigkeiten festgelegt wird, würde dies dazu führen, dass Dependency Groups zwei Möglichkeiten haben, das Hauptpaket zu spezifizieren. Wenn beispielsweise . formal unterstützt wird und {include-project = true} in diesem PEP enthalten ist, können Dependency Groups eine der folgenden Gruppen spezifizieren
[dependency-groups]
case1 = [{include-project = true}]
case2 = ["."]
case3 = [{include-project = true}, "."]
case4 = [{include-project = false}, "."]
Um eine verwirrende Zukunft zu vermeiden, in der mehrere verschiedene Optionen das in pyproject.toml definierte Paket spezifizieren, wird jede Syntax zur Deklaration dieser Beziehung aus diesem PEP weggelassen.
Anhang A: Vorherige Arbeiten in nicht-pythonischen Sprachen
Dieser Abschnitt ist primär informativ und dient dazu, zu dokumentieren, wie andere Sprach-Ökosysteme ähnliche Probleme lösen.
JavaScript und package.json
In der JavaScript-Community enthalten Pakete eine kanonische Konfigurations- und Datendatei, ähnlich im Umfang wie pyproject.toml, unter package.json.
Zwei Schlüssel in package.json steuern die Abhängigkeitsdaten: "dependencies" und "devDependencies". Die Rolle von "dependencies" ist effektiv dieselbe wie die von [project.dependencies] in pyproject.toml und deklariert die direkten Abhängigkeiten eines Pakets.
"dependencies"-Daten
Abhängigkeitsdaten werden in package.json als Mapping von Paketnamen zu Versionsspezifikatoren deklariert.
Versionsspezifikatoren unterstützen eine kleine Grammatik möglicher Versionen, Bereiche und anderer Werte, ähnlich den PEP 440 Versionsspezifikatoren von Python.
Zum Beispiel hier ist eine teilweise package.json Datei, die einige Abhängigkeiten deklariert
{
"dependencies": {
"@angular/compiler": "^17.0.2",
"camelcase": "8.0.0",
"diff": ">=5.1.0 <6.0.0"
}
}
Die Verwendung des @-Symbols ist ein Scope, der den Paketbesitzer für organisatorisch besessene Pakete deklariert. "@angular/compiler" deklariert daher ein Paket namens compiler, das unter der angular-Eigentümerschaft gruppiert ist.
Abhängigkeiten, die URLs und lokale Pfade referenzieren
Abhängigkeitsspezifikatoren unterstützen eine Syntax für URLs und Git-Repositorys, ähnlich den Bestimmungen in der Python-Paketierung.
URLs können anstelle von Versionsnummern verwendet werden. Wenn sie verwendet werden, beziehen sie sich implizit auf Tarballs von Paketquellcode.
Git-Repositorys können ähnlich verwendet werden, einschließlich der Unterstützung für Committish-Spezifikatoren.
Im Gegensatz zu PEP 440 erlaubt NPM die Verwendung lokaler Pfade zu Quellcodeverzeichnissen von Paketen für Abhängigkeiten. Wenn diese Daten über den Standardbefehl npm install --save zu package.json hinzugefügt werden, wird der Pfad zu einem relativen Pfad normalisiert, vom Verzeichnis, das package.json enthält, und mit file: präfixiert. Zum Beispiel enthält die folgende partielle package.json einen Verweis auf einen Geschwister des aktuellen Verzeichnisses
{
"dependencies": {
"my-package": "file:../foo"
}
}
Die offizielle NPM-Dokumentation besagt, dass lokale Pfadabhängigkeiten „nicht“ in öffentliche Paket-Repositories veröffentlicht werden „sollten“, trifft aber keine Aussage über die inhärente Gültigkeit oder Ungültigkeit solcher Abhängigkeitsdaten in veröffentlichten Paketen.
"devDependencies"-Daten
package.json darf einen zweiten Abschnitt namens "devDependencies" enthalten, im gleichen Format wie "dependencies". Die in "devDependencies" deklarierten Abhängigkeiten werden standardmäßig nicht installiert, wenn ein Paket aus dem Paket-Repository installiert wird (z.B. als Teil der Auflösung einer Abhängigkeit), aber sie werden installiert, wenn npm install im Quellbaum ausgeführt wird, der package.json enthält.
Genauso wie "dependencies" URLs und lokale Pfade unterstützt, tut dies auch "devDependencies".
"peerDependencies" und "optionalDependencies"
Es gibt zwei zusätzliche, verwandte Abschnitte in package.json, die relevant sind.
"peerDependencies" deklariert eine Liste von Abhängigkeiten im gleichen Format wie "dependencies", aber mit der Bedeutung, dass dies eine Kompatibilitätserklärung ist. Zum Beispiel deklariert die folgende Angabe Kompatibilität mit Paket foo Version 2
{
"peerDependencies": {
"foo": "2.x"
}
}
"optionalDependencies" deklariert eine Liste von Abhängigkeiten, die, falls möglich, installiert werden sollten, aber bei Nichtverfügbarkeit nicht als Fehler behandelt werden sollten. Sie verwendet ebenfalls das gleiche Mapping-Format wie "dependencies".
"peerDependenciesMeta"
"peerDependenciesMeta" ist ein Abschnitt, der eine zusätzliche Kontrolle darüber ermöglicht, wie "peerDependencies" behandelt werden.
Warnungen über fehlende Abhängigkeiten können deaktiviert werden, indem Pakete in diesem Abschnitt auf optional gesetzt werden, wie im folgenden Beispiel
{
"peerDependencies": {
"foo": "2.x"
},
"peerDependenciesMeta": {
"foo": {
"optional": true
}
}
}
--omit und --include
Der Befehl npm install unterstützt zwei Optionen, --omit und --include, die steuern können, ob „prod“, „dev“, „optional“ oder „peer“ Abhängigkeiten installiert werden.
Der Name „prod“ bezieht sich auf Abhängigkeiten, die unter "dependencies" aufgeführt sind.
Standardmäßig werden alle vier Gruppen installiert, wenn npm install gegen einen Quellbaum ausgeführt wird, aber diese Optionen können verwendet werden, um das Installationsverhalten genauer zu steuern. Darüber hinaus können diese Werte in .npmrc-Dateien deklariert werden, was benutzerspezifische und projektspezifische Konfigurationen zur Steuerung des Installationsverhaltens ermöglicht.
Ruby & Ruby Gems
Ruby-Projekte sind möglicherweise nicht dazu bestimmt, Pakete („Gems“) im Ruby-Ökosystem zu produzieren. Tatsächlich ist die Erwartung, dass die meisten Benutzer der Sprache keine Gems produzieren wollen und kein Interesse daran haben, ihre eigenen Pakete zu erstellen. Viele Tutorials berühren nicht, wie Pakete erstellt werden, und die Toolchain erfordert niemals Benutzercode, der für unterstützte Anwendungsfälle verpackt wird.
Ruby teilt die Spezifikation von Anforderungen in zwei separate Dateien auf.
Gemfile: eine dedizierte Datei, die nur Anforderungsdaten in Form von Abhängigkeitsgruppen unterstützt<package>.gemspec: eine dedizierte Datei zur Deklaration von Paket- (Gem-) Metadaten
Das Werkzeug bundler, das den Befehl bundle bereitstellt, ist die primäre Schnittstelle zur Verwendung von Gemfile-Daten.
Das Werkzeug gem ist für die Erstellung von Gems aus .gemspec-Daten über den Befehl gem build zuständig.
Gemfiles & bundle
Ein Gemfile ist eine Ruby-Datei, die gem-Direktiven enthält, die in beliebig vielen group-Deklarationen eingeschlossen sind. gem-Direktiven können auch außerhalb der group-Deklaration verwendet werden, in diesem Fall bilden sie eine implizit unbenannte Gruppe von Abhängigkeiten.
Zum Beispiel listet das folgende Gemfile rails als Projekt-Abhängigkeit auf. Alle anderen Abhängigkeiten sind unter Gruppen aufgeführt
source 'https://rubygems.org'
gem 'rails'
group :test do
gem 'rspec'
end
group :lint do
gem 'rubocop'
end
group :docs do
gem 'kramdown'
gem 'nokogiri'
end
Wenn ein Benutzer bundle install mit diesen Daten ausführt, werden alle Gruppen installiert. Benutzer können Gruppen deselektieren, indem sie eine Bundler-Konfiguration in .bundle/config erstellen oder ändern, entweder manuell oder über die CLI. Zum Beispiel: bundle config set --local without 'lint:docs'.
Mit den obigen Daten ist es nicht möglich, die Top-Level-Nutzung des Gems 'rails' auszuschließen oder diese implizite Gruppierung namentlich zu referenzieren.
gemspec und verpackte Abhängigkeitsdaten
Eine Gemspec-Datei ist eine Ruby-Datei, die eine Gem::Specification Instanzdeklaration enthält.
Nur zwei Felder in einer Gem::Specification beziehen sich auf Paket-Abhängigkeitsdaten. Dies sind add_development_dependency und add_runtime_dependency. Ein Gem::Specification-Objekt bietet auch Methoden zum Hinzufügen von Abhängigkeiten dynamisch, einschließlich add_dependency (was eine Laufzeitabhängigkeit hinzufügt).
Hier ist eine Variante der Datei rails.gemspec, mit vielen Feldern entfernt oder gekürzt zur Vereinfachung
version = '7.1.2'
Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = "rails"
s.version = version
s.summary = "Full-stack web application framework."
s.license = "MIT"
s.author = "David Heinemeier Hansson"
s.files = ["README.md", "MIT-LICENSE"]
# shortened from the real 'rails' project
s.add_dependency "activesupport", version
s.add_dependency "activerecord", version
s.add_dependency "actionmailer", version
s.add_dependency "activestorage", version
s.add_dependency "railties", version
end
Beachten Sie, dass keine Verwendung von add_development_dependency erfolgt. Einige andere Mainstream-, Major-Pakete (z.B. rubocop) verwenden keine Entwicklung-Abhängigkeiten in ihren Gems.
Andere Projekte nutzen diese Funktion *doch*. Zum Beispiel nutzt kramdown Entwicklung-Abhängigkeiten und enthält die folgende Spezifikation in seiner Rakefile
s.add_dependency "rexml"
s.add_development_dependency 'minitest', '~> 5.0'
s.add_development_dependency 'rouge', '~> 3.0', '>= 3.26.0'
s.add_development_dependency 'stringex', '~> 1.5.1'
Der Zweck von Entwicklung-Abhängigkeiten ist nur die Deklaration einer impliziten Gruppe, als Teil des .gemspec, die dann von bundler verwendet werden kann.
Für vollständige Details siehe die gemspec-Direktive in der Dokumentation zu Gemfiles von bundler. Die Integration zwischen .gemspec Entwicklung-Abhängigkeiten und der Verwendung von Gemfile/bundle lässt sich jedoch am besten anhand eines Beispiels verstehen.
Beispiel für Entwicklungsabhängigkeiten in gemspec
Betrachten Sie das folgende einfache Projekt in Form eines Gemfile und .gemspec. Die Datei cool-gem.gemspec
Gem::Specification.new do |s|
s.author = 'Stephen Rosen'
s.name = 'cool-gem'
s.version = '0.0.1'
s.summary = 'A very cool gem that does cool stuff'
s.license = 'MIT'
s.files = []
s.add_dependency 'rails'
s.add_development_dependency 'kramdown'
end
und das Gemfile
source 'https://rubygems.org'
gemspec
Die gemspec-Direktive in Gemfile deklariert eine Abhängigkeit vom lokalen Paket, cool-gem, definiert in der lokal verfügbaren Datei cool-gem.gemspec. Sie fügt *auch* implizit alle Entwicklung-Abhängigkeiten zu einer Abhängigkeitsgruppe namens development hinzu.
Daher ist in diesem Fall die gemspec-Direktive äquivalent zum folgenden Gemfile-Inhalt
gem 'cool-gem', :path => '.'
group :development do
gem 'kramdown'
end
Anhang B: Vorherige Arbeiten in Python
In Abwesenheit eines vorherigen Standards für Dependency Groups haben zwei bekannte Workflow-Tools, PDM und Poetry, eigene Lösungen definiert.
Dieser Abschnitt konzentriert sich hauptsächlich auf diese beiden Tools als Beispiele für bisherige Lösungen bezüglich der Definition und Verwendung von Dependency Groups in Python.
Projekte sind Pakete
Sowohl PDM als auch Poetry behandeln die von ihnen unterstützten Projekte als Pakete. Dies erlaubt ihnen, Standard-Metadaten von pyproject.toml für einige ihrer Bedürfnisse zu verwenden und zu interagieren, und erlaubt ihnen, die Installation des „aktuellen Projekts“ zu unterstützen, indem sie einen Build und eine Installation mit ihren Build-Backends durchführen.
Effektiv bedeutet dies, dass weder Poetry noch PDM Nicht-Paket-Projekte unterstützt.
Nicht standardisierte Abhängigkeitsspezifizierer
PDM und Poetry erweitern PEP 508 Abhängigkeitsspezifikatoren um zusätzliche Features, die nicht Teil eines gemeinsamen Standards sind. Die beiden Tools verwenden jedoch leicht unterschiedliche Ansätze für diese Probleme.
PDM unterstützt die Spezifikation lokaler Pfade und editierbarer Installationen über eine Syntax, die wie eine Reihe von Argumenten für pip install aussieht. Zum Beispiel beinhaltet die folgende Abhängigkeitsgruppe ein lokales Paket im Editier-Modus
[tool.pdm.dev-dependencies]
mygroup = ["-e file:///${PROJECT_ROOT}/foo"]
Dies deklariert eine Abhängigkeitsgruppe mygroup, die eine lokale, editierbare Installation aus dem Verzeichnis foo enthält.
Poetry beschreibt Abhängigkeitsgruppen als Tabellen, die Paketnamen auf Spezifikatoren abbilden. Zum Beispiel könnte die gleiche Konfiguration wie das obige mygroup-Beispiel unter Poetry wie folgt aussehen
[tool.poetry.group.mygroup]
foo = { path = "foo", editable = true }
PDM beschränkt sich auf eine String-Syntax, während Poetry Tabellen einführt, die Abhängigkeiten beschreiben.
Installation und Referenzierung von Abhängigkeitsgruppen
Sowohl PDM als auch Poetry haben Tool-spezifische Unterstützung für die Installation von Abhängigkeitsgruppen. Da beide Projekte ihre eigenen Lockfile-Formate unterstützen, haben sie auch beide die Fähigkeit, einen Abhängigkeitsgruppennamen transparent zu verwenden, um auf die *gesperrten* Abhängigkeitsdaten für diese Gruppe zu verweisen.
Keines der Tools Abhängigkeitsgruppen kann jedoch nativ von anderen Tools wie tox, nox oder pip referenziert werden. Der Versuch, eine Abhängigkeitsgruppe unter tox zu installieren, erfordert beispielsweise einen expliziten Aufruf von PDM oder Poetry, um deren Abhängigkeitsdaten zu parsen und den entsprechenden Installationsschritt durchzuführen.
Anhang C: Anwendungsfälle
Webanwendungen
Eine Webanwendung (z.B. eine Django- oder Flask-App) muss oft keine Distribution erstellen, sondern bündelt und liefert ihren Quellcode an eine Deployment-Toolchain.
Zum Beispiel kann ein Quellcode-Repository Python-Paketierungsmetadaten sowie Containerisierungs- oder andere Build-Pipeline-Metadaten (Dockerfile, etc.) definieren. Die Python-Anwendung wird erstellt, indem das gesamte Repository in einen Build-Kontext kopiert, Abhängigkeiten installiert und das Ergebnis als Maschinen-Image oder Container gebündelt wird.
Solche Anwendungen haben Abhängigkeitsgruppen für den Build, aber auch für Linting, Testing usw. In der Praxis definieren diese Anwendungen heute oft selbst als Pakete, um Paketierungs-Tools und -Mechanismen wie extras zur Verwaltung ihrer Abhängigkeitsgruppen nutzen zu können. Sie sind jedoch konzeptionell keine Pakete, die in sdist- oder Wheel-Format zur Verteilung bestimmt sind.
Dependency Groups ermöglichen es diesen Anwendungen, ihre verschiedenen Abhängigkeiten zu definieren, ohne auf Paketierungsmetadaten angewiesen zu sein und ohne zu versuchen, ihre Bedürfnisse in Paketierungstermen auszudrücken.
Bibliotheken
Bibliotheken sind Python-Pakete, die Distributionen (sdist und wheel) erstellen und sie auf PyPI veröffentlichen.
Für Bibliotheken stellen Dependency Groups eine Alternative zu extras zur Definition von Gruppen von Entwicklung-Abhängigkeiten dar, mit den wichtigen oben genannten Vorteilen.
Eine Bibliothek kann Gruppen für test und typing definieren, die Tests und Typüberprüfung ermöglichen, und somit auf die eigenen Abhängigkeiten der Bibliothek angewiesen sein (wie in [project.dependencies] spezifiziert).
Andere Entwicklungsbedürfnisse erfordern möglicherweise gar keine Installation des Pakets. Zum Beispiel kann eine lint Dependency Group gültig und schneller zu installieren sein, ohne die Bibliothek, da sie nur Tools wie black, ruff oder flake8 installiert.
lint und test Umgebungen können auch wertvolle Orte sein, um IDE- oder Editor-Unterstützung einzubinden. Siehe den unten stehenden Fall für eine ausführlichere Beschreibung einer solchen Verwendung.
Hier ist eine Beispiel-Dependency-Groups-Tabelle, die für eine Bibliothek geeignet sein könnte
[dependency-groups]
test = ["pytest<8", "coverage"]
typing = ["mypy==1.7.1", "types-requests"]
lint = ["black", "flake8"]
typing-test = [{include-group = "typing"}, "pytest<8"]
Beachten Sie, dass keine dieser Gruppen implizit die Bibliothek selbst installiert. Es liegt daher in der Verantwortung jeder Umgebungsmanagement-Toolchain, die entsprechenden Dependency Groups zusammen mit der Bibliothek zu installieren, wenn nötig, wie im Fall von test.
Data Science Projekte
Data Science Projekte nehmen typischerweise die Form einer logischen Sammlung von Skripten und Dienstprogrammen zur Verarbeitung und Analyse von Daten unter Verwendung einer gemeinsamen Toolchain an. Komponenten können im Jupyter Notebook-Format (ipynb) definiert werden, basieren aber auf demselben gemeinsamen Kernsatz von Dienstprogrammen.
In einem solchen Projekt gibt es kein Paket zu erstellen oder zu installieren. Daher bietet pyproject.toml derzeit keine Lösung für das Abhängigkeitsmanagement oder die Deklaration.
Es ist für ein solches Projekt wertvoll, mindestens eine Hauptgruppierung von Abhängigkeiten definieren zu können. Zum Beispiel
[dependency-groups]
main = ["numpy", "pandas", "matplotlib"]
Es kann jedoch auch notwendig sein, dass verschiedene Skripte zusätzliche unterstützende Werkzeuge benötigen. Projekte können sogar widersprüchliche oder inkompatible Werkzeuge oder Werkzeugversionen für verschiedene Komponenten haben, wenn diese sich im Laufe der Zeit weiterentwickeln.
Betrachten Sie die folgende aufwendigere Konfiguration
[dependency-groups]
main = ["numpy", "pandas", "matplotlib"]
scikit = [{include-group = "main"}, "scikit-learn==1.3.2"]
scikit-old = [{include-group = "main"}, "scikit-learn==0.24.2"]
Dies definiert scikit und scikit-old als zwei ähnliche Varianten der üblichen Abhängigkeitssuite, die unterschiedliche Versionen von scikit-learn für verschiedene Skripte einbinden.
Diese PEP definiert nur diese Daten. Sie formalisiert keinen Mechanismus für ein Data Science Projekt (oder irgendeinen anderen Projekttyp), um die Abhängigkeiten in bekannten Umgebungen zu installieren oder diese Umgebungen mit den verschiedenen Skripten zu verknüpfen. Solche Datenkombinationen bleiben ein Problem für Toolautoren, das gelöst und vielleicht irgendwann standardisiert werden muss.
Lockfile-Generierung
Es gibt heute eine Reihe von Tools, die Lockfiles im Python-Ökosystem generieren. PDM und Poetry verwenden jeweils ihre eigenen Lockfile-Formate, und pip-tools generiert requirements.txt-Dateien mit Versions-Pins und Hashes.
Abhängigkeitsgruppen sind kein geeigneter Ort zur Speicherung von Lockfiles, da ihnen viele der notwendigen Funktionen fehlen. Am bemerkenswertesten ist, dass sie keine Hashes speichern können, was die meisten Lockfile-Benutzer für unerlässlich halten.
Abhängigkeitsgruppen sind jedoch eine gültige Eingabe für Tools, die Lockfiles generieren. Darüber hinaus erlauben sowohl PDM als auch Poetry, einen Namen für eine Abhängigkeitsgruppe (innerhalb ihrer Vorstellung von Abhängigkeitsgruppen) zu verwenden, um sich auf seine gesperrte Variante zu beziehen.
Betrachten Sie daher ein Tool, das Lockfiles erzeugt, hier als $TOOL bezeichnet. Es könnte wie folgt verwendet werden
$TOOL lock --dependency-group=test
$TOOL install --dependency-group=test --use-locked
Alles, was ein solches Tool tun muss, ist sicherzustellen, dass seine Lockfile-Daten den Namen test aufzeichnen, um eine solche Verwendung zu unterstützen.
Die gegenseitige Kompatibilität von Abhängigkeitsgruppen ist nicht garantiert. Das Beispiel für Data Science oben zeigt beispielsweise widersprüchliche Versionen von scikit-learn. Daher kann die parallele Installation mehrerer gesperrter Abhängigkeitsgruppen erfordern, dass Tools zusätzliche Einschränkungen anwenden oder zusätzliche Lockfile-Daten generieren. Diese Probleme werden für diese PEP als außerhalb des Geltungsbereichs betrachtet.
Als zwei Beispiele dafür, wie Kombinationen gesperrt werden könnten
- Ein Tool könnte verlangen, dass Lockfile-Daten explizit für jede zu berücksichtigende gültige Kombination generiert werden.
- Poetry implementiert die Anforderung, dass alle Abhängigkeitsgruppen gegenseitig kompatibel sind, und generiert nur eine gesperrte Version. (Das bedeutet, es findet eine einzelne Lösung und nicht eine Menge oder Matrix von Lösungen.)
Eingaben für Umgebungsmanager
Eine gängige Verwendung in tox, Nox und Hatch ist die Installation einer Reihe von Abhängigkeiten in einer Testumgebung.
Zum Beispiel können unter tox.ini Typüberprüfungsabhängigkeiten direkt definiert werden.
[testenv:typing]
deps =
pyright
useful-types
commands = pyright src/
Diese Kombination bietet eine wünschenswerte Entwicklererfahrung in einem begrenzten Kontext. Unter dem entsprechenden Umgebungsmanager werden die für die Testumgebung benötigten Abhängigkeiten zusammen mit den Befehlen deklariert, die diese Abhängigkeiten benötigen. Sie werden nicht in Paketmetadaten veröffentlicht, wie es extras wären, und sie sind für das Tool, das sie zum Erstellen der relevanten Umgebung benötigt, auffindbar.
Abhängigkeitsgruppen gelten für solche Verwendungen, indem sie diese Anforderungsdaten effektiv von einem toolspezifischen Speicherort an einen breiter verfügbaren "anheben". Im obigen Beispiel hat nur tox Zugriff auf die deklarierte Liste von Abhängigkeiten. Unter einer Implementierung, die Abhängigkeitsgruppen unterstützt, könnten dieselben Daten in einer Abhängigkeitsgruppe verfügbar sein.
[dependency-groups]
typing = ["pyright", "useful-types"]
Die Daten können dann unter mehreren Tools verwendet werden. Beispielsweise könnte tox die Unterstützung als dependency_groups = typing implementieren und die obige deps-Verwendung ersetzen.
Damit Abhängigkeitsgruppen eine praktikable Alternative für Benutzer von Umgebungsmanagern darstellen, müssen die Umgebungsmanager die Verarbeitung von Abhängigkeitsgruppen ähnlich unterstützen, wie sie die direkte Deklaration von Abhängigkeiten unterstützen.
IDE und Editor-Nutzung von Anforderungsdaten
IDE- und Editor-Integrationen können von konventionellen oder konfigurierbaren Namensdefinitionen für Abhängigkeitsgruppen profitieren, die für Integrationen verwendet werden.
Es gibt mindestens zwei bekannte Szenarien, in denen es für einen Editor oder eine IDE von Vorteil ist, die nicht veröffentlichten Abhängigkeiten eines Projekts erkennen zu können.
- Testen: IDEs wie VS Code unterstützen grafische Benutzeroberflächen zum Ausführen bestimmter Tests.
- Linting: Editoren und IDEs unterstützen oft Linting- und Autoformatierungs-Integrationen, die Fehler hervorheben oder korrigieren.
Diese Fälle könnten durch die Definition konventioneller Gruppennamen wie test, lint und fix oder durch die Definition von Konfigurationsmechanismen, die die Auswahl von Abhängigkeitsgruppen ermöglichen, behandelt werden.
Zum Beispiel deklariert die folgende pyproject.toml die drei oben genannten Gruppen.
[dependency-groups]
test = ["pytest", "pytest-timeout"]
lint = ["flake8", "mypy"]
fix = ["black", "isort", "pyupgrade"]
Diese PEP unternimmt keinen Versuch, solche Namen zu standardisieren oder sie für solche Zwecke zu reservieren. IDEs können standardisieren oder Benutzern erlauben, die für verschiedene Zwecke verwendeten Gruppennamen zu konfigurieren.
Diese Deklaration ermöglicht es, dass das Wissen des Projektverfassers über die geeigneten Tools für das Projekt mit allen Bearbeitern dieses Projekts geteilt wird.
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-0735.rst
Zuletzt geändert: 2025-01-18 20:45:37 GMT