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

Python Enhancement Proposals

PEP 725 – Spezifikation externer Abhängigkeiten in pyproject.toml

Autor:
Pradyun Gedam <pradyunsg at gmail.com>, Jaime Rodríguez-Guerra <jaime.rogue at gmail.com>, Ralf Gommers <ralf.gommers at gmail.com>
Discussions-To:
Discourse thread
Status:
Entwurf
Typ:
Standards Track
Thema:
Packaging
Erstellt:
17-Aug-2023
Post-History:
18-Aug-2023, 22-Sep-2025

Inhaltsverzeichnis

Zusammenfassung

Dieses PEP spezifiziert, wie die externen oder Nicht-PyPI-Build- und Laufzeitabhängigkeiten eines Projekts in einer pyproject.toml-Datei für Verpackungs-bezogene Werkzeuge angegeben werden.

Dieses PEP schlägt vor, eine [external]-Tabelle zu pyproject.toml mit sieben Schlüsseln hinzuzufügen. „build-requires“, „host-requires“ und „dependencies“ dienen der Angabe von drei Arten von *erforderlichen* Abhängigkeiten

  1. build-requires, Build-Werkzeuge, die auf dem Build-Computer ausgeführt werden sollen
  2. host-requires, Build-Abhängigkeiten, die für den Host-Computer benötigt werden, aber auch zur Build-Zeit benötigt werden.
  3. dependencies, die zur Laufzeit auf dem Host-Computer benötigt werden, aber nicht zur Build-Zeit benötigt werden.

Diese drei Schlüssel haben auch ihre *optionalen* external-Gegenstücke (optional-build-requires, optional-host-requires, optional-dependencies), die dieselbe Rolle spielen wie project.optional-dependencies für project.dependencies. Schließlich bietet dependency-groups dieselbe Funktionalität wie PEP 735, jedoch für externe Abhängigkeiten.

Kreuzkompilierung wird berücksichtigt, indem zwischen Build- und Host-Abhängigkeiten unterschieden wird. Optionale Build-Zeit- und Laufzeitabhängigkeiten werden ebenfalls unterstützt, analog zu ihrer Unterstützung in der [project]-Tabelle.

Motivation

Python-Pakete können Abhängigkeiten von Build-Werkzeugen, Bibliotheken, Kommandozeilen-Tools oder anderer Software haben, die nicht auf PyPI vorhanden ist. Derzeit gibt es keine Möglichkeit, diese Abhängigkeiten in standardisierter Metadaten auszudrücken [1], [2]. Hauptmotivationen für dieses PEP sind:

  • Werkzeugen die automatische Zuordnung externer Abhängigkeiten zu Paketen in anderen Paket-Repositories zu ermöglichen,
  • es möglich zu machen, benötigte Abhängigkeiten in Fehlermeldungen von Python-Paketinstallern und Build-Frontends aufzunehmen,
  • einen kanonischen Ort für Paketautoren bereitzustellen, um diese Abhängigkeitsinformationen zu erfassen.

Verpackungs-Ökosysteme wie Linux-Distributionen, conda, Homebrew, Spack und Nix benötigen vollständige Abhängigkeitssätze für Python-Pakete und verfügen über Werkzeuge wie pyp2spec (Fedora), Grayskull (conda) und dh_python (Debian), die versuchen, Abhängigkeitsmetadaten für ihre eigenen Paketmanager automatisch aus den Metadaten von vorgeschalteten Python-Paketen zu generieren. Externe Abhängigkeiten werden derzeit manuell gehandhabt, da es keine Metadaten dafür in pyproject.toml oder an einem anderen standardmäßigen Ort gibt. Andere Werkzeuge greifen auf die Extraktion von Abhängigkeiten aus Erweiterungsmodulen und Shared Libraries innerhalb von Python-Paketen zurück, wie z.B. elfdeps (Fedora). Die Ermöglichung der Automatisierung dieser Art von Konvertierung ausschließlich durch explizit annotierte Metadaten ist ein wesentlicher Vorteil dieses PEP, der die Verpackung von Python-Paketen für Distributionen einfacher und zuverlässiger macht. Darüber hinaus stellen sich die Autoren andere Arten von Werkzeugen vor, die diese Informationen nutzen könnten, z.B. Abhängigkeitsanalyse-Werkzeuge wie Repology, Dependabot und libraries.io.

Werkzeuge zur Generierung von Software Bill of Materials (SBOMs) könnten diese Informationen ebenfalls nutzen, z.B. um zu kennzeichnen, dass externe Abhängigkeiten, die in pyproject.toml aufgeführt, aber nicht in den Wheel-Metadaten enthalten sind, wahrscheinlich innerhalb des Wheels gevendort sind. PEP 770, das standardisiert, wie SBOMs in Wheels enthalten sind, enthält einen lehrreichen Abschnitt darüber, wie sich dieses PEP von diesem unterscheidet.

Pakete mit externen Abhängigkeiten sind typischerweise schwer aus dem Quellcode zu bauen, und Fehlermeldungen bei Build-Fehlern sind für Endbenutzer oft schwer zu entschlüsseln. Fehlende externe Abhängigkeiten auf dem System des Endbenutzers sind die wahrscheinlichste Ursache für Build-Fehler. Wenn Installer die benötigten externen Abhängigkeiten als Teil ihrer Fehlermeldung anzeigen können, kann dies Benutzern viel Zeit sparen.

Derzeit werden Informationen zu externen Abhängigkeiten nur in der Installationsdokumentation einzelner Pakete erfasst. Dies ist für Paketautoren schwer zu pflegen und neigt dazu, veraltet zu sein. Es ist auch für Benutzer und Distro-Paketer schwer, sie zu finden. Ein kanonischer Ort zur Erfassung dieser Abhängigkeitsinformationen wird diese Situation verbessern.

Dieses PEP versucht nicht zu spezifizieren, wie externe Abhängigkeiten verwendet werden sollen, noch einen Mechanismus zur Implementierung einer Namenszuordnung von Namen einzelner Pakete, die für auf PyPI veröffentlichte Python-Projekte kanonisch sind, zu anderen Verpackungs-Ökosystemen. Kanonische Namen und ein Namenszuordnungsmechanismus werden in PEP 804 behandelt.

Begründung

Arten externer Abhängigkeiten

Es können mehrere Arten von externen Abhängigkeiten unterschieden werden

  • Konkrete Pakete, die durch ihren Namen identifiziert werden können und einen kanonischen Speicherort in einem anderen sprachspezifischen Paket-Repository haben. Z.B. Rust-Pakete auf crates.io, R-Pakete auf CRAN, JavaScript-Pakete auf der npm-Registry.
  • Konkrete Pakete, die durch ihren Namen identifiziert werden können, aber keinen klaren kanonischen Speicherort haben. Dies ist typischerweise der Fall für Bibliotheken und Werkzeuge, die in C, C++, Fortran, CUDA und anderen Low-Level-Sprachen geschrieben sind. Z.B. Boost, OpenSSL, Protobuf, Intel MKL, GCC.
  • „Virtuelle“ Pakete, die Namen für Konzepte, Arten von Werkzeugen oder Schnittstellen sind. Diese haben typischerweise mehrere Implementierungen, die *konkrete* Pakete sind. Z.B. ein C++-Compiler, BLAS, LAPACK, OpenMP, MPI.

Konkrete Pakete sind einfach zu verstehen und ein Konzept, das in jedem Paketverwaltungssystem vorhanden ist. Virtuelle Pakete sind ebenfalls ein Konzept, das in einer Reihe von Paketverwaltungssystemen vorhanden ist – aber nicht immer, und die Details ihrer Implementierung variieren.

Kreuzkompilierung

Kreuzkompilierung wird (Stand September 2025) von stdlib-Modulen und pyproject.toml-Metadaten noch nicht gut unterstützt. Sie ist jedoch wichtig, wenn externe Abhängigkeiten in die von anderen Paketverwaltungssystemen übersetzt werden (mit Werkzeugen wie pyp2spec). Die sofortige Einführung der Unterstützung für Kreuzkompilierung in diesem PEP ist einfacher als die zukünftige Erweiterung von [external], daher haben sich die Autoren entschieden, dies jetzt aufzunehmen.

Terminologie

Dieses PEP verwendet die folgende Terminologie

  • Build-Maschine: die Maschine, auf der der Paket-Build-Prozess ausgeführt wird.
  • Host-Maschine: die Maschine, auf der das erzeugte Artefakt installiert und ausgeführt wird.
  • Build-Abhängigkeit: ein Paket, das nur während des Build-Prozesses benötigt wird. Es muss zur Build-Zeit verfügbar sein und wird für das Betriebssystem und die Architektur der *Build-Maschine* erstellt. Typische Beispiele sind Compiler, Codegeneratoren und Build-Werkzeuge.
  • Host-Abhängigkeit: ein Paket, das während des Builds und oft auch zur Laufzeit benötigt wird. Es muss während des Builds verfügbar sein und wird für das Betriebssystem und die Architektur der *Host-Maschine* erstellt. Dies sind normalerweise Bibliotheken, gegen die das Projekt verlinkt.
  • Laufzeitabhängigkeit: ein Paket, das nur benötigt wird, wenn das Paket nach der Installation verwendet wird. Es wird nicht zur Build-Zeit benötigt, muss aber zur Laufzeit auf der *Host-Maschine* verfügbar sein.

Beachten Sie, dass diese Terminologie nicht über Build- und Verpackungswerkzeuge hinweg konsistent ist. Daher ist Vorsicht geboten, wenn Build-/Host-Abhängigkeiten in pyproject.toml mit Abhängigkeiten aus anderen Paketmanagern verglichen werden.

Beachten Sie, dass „Zielmaschine“ oder „Zielabhängigkeit“ in diesem PEP nicht verwendet werden. Dies ist typischerweise nur für die Kreuzkompilierung eines Compilers oder ähnlicher fortgeschrittener Szenarien relevant [3], [4] – dies liegt außerhalb des Geltungsbereichs dieses PEP.

Beachten Sie schließlich, dass obwohl „Abhängigkeit“ der am weitesten verbreitete Begriff für Pakete ist, die zur Build-Zeit benötigt werden, der vorhandene Schlüssel in pyproject.toml für Build-Zeit-Abhängigkeiten von PyPI build-requires lautet. Daher verwendet dieses PEP die Schlüssel build-requires und host-requires unter [external] zur Konsistenz.

Build- und Host-Abhängigkeiten

Eine klare Trennung von Metadaten, die mit der Definition von Build- und Host-Plattformen verbunden sind, anstatt davon auszugehen, dass Build- und Host-Plattform immer gleich sind, ist wichtig [5].

Build-Abhängigkeiten werden typischerweise während des Build-Prozesses ausgeführt – es können Compiler, Codegeneratoren oder andere solche Werkzeuge sein. Wenn die Verwendung einer Build-Abhängigkeit eine Laufzeitabhängigkeit impliziert, muss diese Laufzeitabhängigkeit nicht explizit deklariert werden. Wenn beispielsweise Fortran-Code mit gfortran in ein Python-Erweiterungsmodul kompiliert wird, fallen für das Paket wahrscheinlich Abhängigkeiten von der libgfortran-Laufzeitbibliothek an. Die Begründung für die Nicht-Explizite-Auflistung solcher Laufzeitabhängigkeiten ist zweifach: (1) es kann von Compiler-/Linker-Flags oder Details der Build-Umgebung abhängen, ob die Abhängigkeit vorhanden ist, und (2) diese Laufzeitabhängigkeiten können von Werkzeugen wie auditwheel automatisch erkannt und behandelt werden.

Host-Abhängigkeiten werden typischerweise nicht während des Build-Prozesses ausgeführt, sondern nur zum Linken verwendet. Dies ist jedoch keine Regel – es kann möglich oder notwendig sein, eine Host-Abhängigkeit unter einem Emulator oder über ein benutzerdefiniertes Werkzeug wie crossenv auszuführen. Wenn Host-Abhängigkeiten eine Laufzeitabhängigkeit implizieren, muss diese Laufzeitabhängigkeit ebenfalls nicht deklariert werden, genau wie bei Build-Abhängigkeiten.

Wenn Host-Abhängigkeiten deklariert werden und ein Werkzeug eine Aktion ausführt, die nichts mit Kreuzkompilierung zu tun hat, kann es entscheiden, die host-requires-Liste in build-requires zu integrieren – ob dies nützlich ist, hängt vom Kontext ab.

Spezifikation externer Abhängigkeiten

Spezifikation konkreter Pakete

Eine „Package URL“ oder PURL ist eine weit verbreitete URL-Zeichenkette zur Identifizierung von Paketen, die für verschiedene Verpackungs-Ökosysteme portabel sein soll. Ihr Design ist

scheme:type/namespace/name@version?qualifiers#subpath

Die scheme-Komponente ist eine feste Zeichenkette, pkg, und von den anderen Komponenten sind nur type und name erforderlich.

Da externe Abhängigkeiten wahrscheinlich von Hand eingegeben werden, schlagen wir ein PURL-Derivat vor, das im Sinne von Ergonomie und Benutzerfreundlichkeit eine Reihe von Änderungen einführt (weiter unten besprochen)

  • Unterstützung für virtuelle Pakete über einen neuen virtual-Typ.
  • Erlaubt Versionsbereiche (und nicht nur Literale) im version-Feld.

In diesem Derivat ersetzen wir das pkg-Schema durch dep. Daher werden wir uns auf sie als DepURLs beziehen.

Als Beispiel wäre eine DepURL für das requests-Paket auf PyPI

dep:pypi/requests
# equivalent to pkg:pypi/requests

Die Übernahme von PURL-kompatiblen Zeichenketten zur Spezifikation externer Abhängigkeiten in pyproject.toml löst eine Reihe von Problemen auf einmal, und es gibt bereits Implementierungen der Spezifikation in Python und mehreren anderen Sprachen. PURL wird auch bereits von Abhängigkeitsbezogenen Werkzeugen wie SPDX (siehe External Repository Identifiers in der SPDX 2.3 spec), dem Open Source Vulnerability-Format und dem Sonatype OSS Index unterstützt; nicht jahrelang auf Unterstützung in solchen Werkzeugen warten zu müssen, ist wertvoll. DepURLs lassen sich sehr leicht in PURLs transformieren, mit Ausnahme von dep:virtual, das kein Äquivalent in PURL hat.

Für konkrete Pakete ohne einen kanonischen Paketmanager kann entweder dep:generic/dep-name verwendet werden, oder ein direkter Verweis auf das VCS, in dem das Paket verwaltet wird (z.B. dep:github/user-or-org-name/dep-name). Welcher davon geeigneter ist, hängt von der Situation ab. Dieses PEP empfiehlt die Verwendung von dep:generic, wenn der Paketname eindeutig und bekannt ist (z.B. dep:generic/git oder dep:generic/openblas), und andernfalls die Verwendung des VCS als Typ. Welcher Name für ein bestimmtes Paket als kanonisch gewählt wird und welcher Prozess zur Erstellung und Aufzeichnung solcher Entscheidungen angewendet wird, ist Thema von PEP 804.

Spezifikation virtueller Pakete

PURL unterstützt noch keine virtuellen Pakete oder virtuelle Abhängigkeitsspezifikationen. Ein Vorschlag zur Hinzufügung eines virtuellen Typs wird für Version 1.1 diskutiert.

In der Zwischenzeit schlagen wir die Hinzufügung eines neuen *Typs* zu unserem dep:-Derivat vor, des virtual-Typs, der zwei *Namespaces* annehmen kann (erweiterbar durch den in PEP 804 gegebenen Prozess)

  • interface: für Komponenten wie BLAS oder MPI.
  • compiler: für kompilierte Sprachen wie C oder Rust.

Der *Name* sollte der gebräuchlichste Name für die Schnittstelle oder Sprache sein, in Kleinbuchstaben. Einige Beispiele sind

dep:virtual/compiler/c
dep:virtual/compiler/cxx
dep:virtual/compiler/rust
dep:virtual/interface/blas
dep:virtual/interface/lapack

Da es eine begrenzte Anzahl solcher Abhängigkeiten gibt, scheint es, dass sie gut verständlich ist und sich gut auf Linux-Distributionen mit virtuellen Paketen und auf conda und Spack abbilden lässt.

Versionierung

PURLs unterstützen feste Versionen über die @-Komponente der URL. Zum Beispiel kann numpy===2.0 als pkg:pypi/numpy@2.0 ausgedrückt werden.

Unterstützung in PURL für Versionsausdrücke und -bereiche über eine feste Version hinaus ist über vers-URIs verfügbar (siehe Spezifikation)

vers:type/version-constraint|version-constraint|...

Benutzer sollen eine pkg:-URL mit einer vers:-URL koppeln. Um beispielsweise numpy>=2.0 auszudrücken, wäre das PURL-Äquivalent pkg:pypi/numpy plus vers:pypi/>=2.0. Dies kann mit

  • einer Liste aus zwei Elementen erfolgen: ["pkg:pypi/numpy", "vers:pypi/>=2.0"].
  • ein prozentuell kodierter URL-Qualifizierer: pkg:pypi/numpy?vers=vers:pypi%2F%3E%3D2.0.

Da keine dieser Optionen besonders ergonomisch ist, haben wir uns stattdessen entschieden, dass DepURLs auch Versionsbereichsspezifizierer akzeptieren, deren Semantik eine Teilmenge der PEP 440-Semantik ist. Die erlaubten Operatoren sind diejenigen, die in den meisten Paketmanagern verfügbar sind (z.B. ==, > und >= sind üblich, während ~= nicht).

Einige Beispiele

  • dep:pypi/numpy@2.0: numpy auf exakt Version 2.0 fixiert.
  • dep:pypi/numpy@>=2.0: numpy mit Version größer oder gleich 2.0.
  • dep:virtual/interface/lapack@>=3.7.1: jedes Paket, das die LAPACK-Schnittstelle für Versionen größer oder gleich 3.7.1 implementiert.

Das Versionierungsschema für bestimmte virtuelle Pakete wird, falls dies nicht eindeutig von einem vorgeschalteten Projekt oder Standard definiert ist, im Zentralen Register definiert (siehe PEP 804).

Umgebungsmarkierer

Reguläre Umgebungsmarkierer (wie ursprünglich in PEP 508 definiert) können hinter DepURLs verwendet werden. PURL-Qualifizierer, die ? gefolgt von einer pakettypspezifischen Abhängigkeitsspezifikationskomponente verwenden, sollten nicht für Zwecke verwendet werden, für die Umgebungsmarkierer ausreichen. Der Grund dafür ist pragmatisch: Umgebungsmarkierer werden bereits für andere Metadaten in pyproject.toml verwendet, daher hat jedes Werkzeug, das mit pyproject.toml verwendet wird, wahrscheinlich bereits eine robuste Implementierung, um es zu parsen. Und wir erwarten nicht, die zusätzlichen Möglichkeiten zu benötigen, die PURL-Qualifizierer bieten (z.B. zur Angabe eines Conan- oder conda-Kanals oder einer RubyGems-Plattform).

Die Kombination aus einer DepURL und Umgebungsmarkierern nennen wir „externe Abhängigkeitsspezifizierer“, analog zu den vorhandenen Abhängigkeitsspezifizierern.

Kanonische Namen von Abhängigkeiten und -dev(el)-geteilte Pakete

Es ist recht üblich, aber keineswegs universell, dass Distributionen ein Paket in zwei oder mehr Pakete aufteilen. Insbesondere Laufzeitkomponenten sind oft separat von Entwicklungs komponen ten (Header, pkg-config und CMake-Dateien usw.) installierbar. Letztere haben dann typischerweise einen Namen mit -dev oder -devel am Ende des Projekt-/Bibliotheksnamens. Auch größere Pakete werden manchmal in mehrere separate Pakete aufgeteilt, um die Installationsgröße überschaubar zu halten. Meistens sind solche Paketaufteilungen nicht von den Maintainern eines Pakets definiert oder erkannt, und es ist daher unklar, was eine solche Aufteilung bedeuten würde. Daher sollten solche Aufteilungen nicht in der [external]-Tabelle reflektiert werden. Es ist nicht möglich, dies in einer sinnvollen Weise zu spezifizieren, die über Distributionen hinweg funktioniert, daher sollte nur der kanonische Name in [external] verwendet werden.

Die beabsichtigte Bedeutung der Verwendung einer DepURL ist „das vollständige Paket mit dem angegebenen Namen“. D.h., einschließlich aller installierbaren Artefakte, die Teil des Pakets sind. Es hängt vom Kontext ab, in dem die Metadaten verwendet werden, ob eine Paketaufteilung relevant ist. Wenn beispielsweise libffi eine Host-Abhängigkeit ist und ein Werkzeug eine Umgebung zur Erstellung eines Wheels vorbereiten möchte, dann muss das Werkzeug beide, libffi und libffi-devel, installieren, wenn eine Distribution die Header für libffi in ein libffi-devel-Paket aufgeteilt hat.

Für die Definition, was kanonische Paketnamen sind und wie Paketaufteilungen in der Praxis gehandhabt werden, wenn Werkzeuge versuchen, [external] für Installationszwecke zu verwenden, verweisen wir auf PEP 804.

Python-Entwicklungsheader

Python-Header und andere Build-Support-Dateien können ebenfalls aufgeteilt werden. Dies ist dieselbe Situation wie im obigen Abschnitt (da Python in Distributionen einfach ein reguläres Paket ist). *Jedoch* ist eine python-dev|devel-Abhängigkeit besonders, da in pyproject.toml Python selbst eine implizite statt eine explizite Abhängigkeit ist. Daher muss hier eine Wahl getroffen werden – python-dev implizit hinzufügen oder jeden Paketautor dazu bringen, es explizit unter [external] hinzuzufügen. Zur Konsistenz zwischen Python-Abhängigkeiten und externen Abhängigkeiten entscheiden wir uns, es implizit hinzuzufügen. Python-Entwicklungsheader müssen als notwendig erachtet werden, wenn eine [external]-Tabelle einen oder mehrere Compiler-Pakete enthält.

Neue Core-Metadaten-Felder

Zwei neue Core-Metadaten-Felder werden vorgeschlagen

  • Requires-External-Dep. Eine externe Anforderung. Spiegelt den Übergang von Requires zu Requires-Dist wider. Wir haben das -Dep-Suffix gewählt, um zu betonen, dass der Wert keine reguläre Python-Spezifikation (Distribution) ist, sondern eine externe Abhängigkeitsspezifikation, die eine DepURL enthält.
  • Provides-External-Extra. Eine *extra*-Gruppe, die nur externe Abhängigkeiten (wie in Requires-External-Dep gefunden) enthält.

Da die Core-Metadaten-Spezifikation keine Felder für Metadaten in der pyproject.toml [build-system]-Tabelle enthält, müssen der Inhalt von build-requires und host-requires nicht in bestehenden Core-Metadaten-Feldern reflektiert werden.

Zusätzlich schlägt dieses PEP auch die Deprezierung des Feldes Requires-External vor. Die Gründe dafür sind:

  • Vermeidung von Verwechslungen mit den neu vorgeschlagenen Feldern.
  • Vermeidung potenzieller Inkompatibilitäten mit bestehender Nutzung (auch wenn begrenzt).
  • Geringe Verbreitung im Ökosystem
    • Es gibt keine direkte Entsprechung zu einem Feld in den pyproject.toml-Metadaten.
    • Mainstream-Build-Backends wie setuptools (siehe pypa/setuptools#4220), hatch (siehe pypa/hatch#1712), flit (siehe pypa/flit#353) oder poetry bieten keine Möglichkeiten, es anzugeben, oder erfordern ein Plugin (z.B. poetry-external-dependencies). maturin scheint es seit 0.7.0 zu unterstützen (siehe PyO3/maturin@5b0e4808), aber es ist nicht direkt dokumentiert. Andere Backends wie scikit-build-core oder meson-python lieferten keine Ergebnisse für External-Requires.
    • Das Feld ist nicht in den PyPI JSON API-Antworten enthalten.

Auswirkung von gevendorten Shared Libraries auf Wheel-Metadaten

Ein Wheel kann seine externen Abhängigkeiten "vendoren". Dies geschieht insbesondere bei der Verteilung von Wheels auf PyPI oder anderen Python-Paketindizes – und Werkzeuge wie auditwheel, delvewheel und delocate automatisieren diesen Prozess. Infolgedessen kann ein Requires-External-Dep-Eintrag in einem sdist aus einem Wheel, das aus diesem sdist mit einem Werkzeug wie cibuildwheel erstellt wurde, verschwinden. Es ist auch möglich, dass ein Requires-External-Dep-Eintrag in einem Wheel verbleibt, entweder unverändert oder mit engeren Einschränkungen. auditwheel vendort bestimmte erlaubte Abhängigkeiten wie OpenGL standardmäßig nicht. Darüber hinaus erlauben auditwheel und delvewheel einem Benutzer, Abhängigkeiten manuell über ein Befehlszeilenflag --exclude oder --no-dll auszuschließen. Dies wird beispielsweise verwendet, um das Vendoring großer gemeinsam genutzter Bibliotheken wie CUDA zu vermeiden.

Requires-External-Dep-Einträge, die aus externen Abhängigkeiten in pyproject.toml generiert werden, können daher je nach Build-/Distributionsprozess zwischen einem sdist und seinen entsprechenden Wheels variieren.

Beachten Sie, dass dies nicht impliziert, dass das Feld als dynamisch markiert werden muss, da diese Unterscheidung nur für Wheels gilt, die von einem Build-Backend aus einem sdist erstellt wurden. Insbesondere müssen aus anderen Wheels erstellte Wheels diese Einschränkung nicht erfüllen.

Abhängigkeitsgruppen

Diese PEP hat sich entschieden, den PEP 735 Schlüssel dependency-groups auch unter der Tabelle [external] aufzunehmen. Diese Entscheidung wird durch die Notwendigkeit motiviert, ähnliche Funktionalität für externe Metadaten zu haben. Die oberste Tabelle kann nicht für externe Abhängigkeiten verwendet werden, da sie PEP 508-Strings (und Tabellen für Gruppen-Includes) erwartet, während wir uns für dep: URLs für die externen Abhängigkeiten entschieden haben. Das Vermischen beider würde erhebliche Kompatibilitätsprobleme mit der bestehenden Nutzung verursachen.

Streng genommen erlaubt das dependency-groups-Schema die Definition externer Abhängigkeiten in Untertabellen pro Gruppe.

[dependency-groups]
dev = [
  "pytest",
  { external = ["dep:cargo/ripgrep"] },
]

Dies hat jedoch dasselbe Problem: Wir mischen unterschiedliche Arten von Abhängigkeitsspezifizierern in derselben Datenstruktur. Wir sind der Meinung, dass es sauberer ist, Belange in verschiedenen obersten Tabellen zu trennen, weshalb wir external.dependency-groups weiterhin bevorzugen.

Optionale Abhängigkeiten im Vergleich zu Abhängigkeitsgruppen

Die Begründung für external.dependency-groups ist identisch mit der Begründung in PEP 735 für die Einführung von [dependency-groups]. Die beabsichtigte Nutzung und Semantik der Einbeziehung/des Ausschlusses in Core Metadata ist somit identisch mit [dependency-groups].

external.optional-dependencies werden in Core Metadata angezeigt. external.dependency-groups nicht.

Spezifikation

Wenn Metadaten fehlerhaft spezifiziert sind, MÜSSEN Werkzeuge einen Fehler ausgeben, um den Benutzer über seinen Fehler zu informieren.

DepURL

Ein DepURL implementiert ein Schema zur Identifizierung von Paketen, das über Paketierungs-Ökosysteme hinweg portabel sein soll. Sein Design ist

dep:type/namespace/name@version?qualifiers#subpath

dep: ist eine feste Zeichenkette und immer vorhanden. type und name sind erforderlich, andere Komponenten sind optional. Alle Komponenten gelten sowohl für PURL als auch für virtuelle type und haben diese Anforderungen:

  • type (erforderlich): Muss entweder ein PURL type oder virtual sein.
  • namespace (optional): Muss ein PURL namespace oder ein Namespace in der DepURL-Zentralregistrierung sein (siehe PEP 804).
  • name (erforderlich): Muss ein Name sein, der als gültiger PURL name geparst werden kann. Werkzeuge KÖNNEN warnen oder einen Fehler ausgeben, wenn ein Name nicht in der DepURL-Zentralregistrierung vorhanden ist (siehe PEP 804).
  • version (optional): Muss ein regulärer Versionsspezifizierer (PEP 440-Semantik) als einzelne Version oder Versionsbereich sein, mit der Einschränkung, dass nur die folgenden Operatoren verwendet werden dürfen: >=, >, <, <=, ==, ,.
  • qualifiers (optional): Muss als gültiger PURL qualifier geparst werden können.
  • subpath (optional): Muss als gültiger PURL subpath geparst werden können.

Externe Abhängigkeitsspezifizierer

Externe Abhängigkeitsspezifizierer MÜSSEN einen DepURL enthalten und KÖNNEN Umgebungsmarker mit derselben Syntax wie in regulären Abhängigkeitsspezifizierern (wie ursprünglich in PEP 508 spezifiziert) enthalten.

Änderungen an den Core-Metadaten

Deprecations

Das Core-Metadatenfeld Requires-External wird als *veraltet* markiert und seine Verwendung wird abgeraten.

Ergänzungen

Zwei neue Felder werden zu Core Metadata hinzugefügt:

  • Requires-External-Dep. Eine externe Anforderung, die als externe Abhängigkeitsspezifizierer-Zeichenkette ausgedrückt wird.
  • Provides-External-Extra. Eine *extra*-Gruppe, die nur externe Abhängigkeiten (wie in Requires-External-Dep gefunden) enthält.

Versionssprung

Da die vorgeschlagenen Änderungen rein additiv sind, wird die Core-Metadaten-Version auf 2.6 hochgestuft.

Dies betrifft nur PyPI und Tools, die externe Laufzeitabhängigkeiten unterstützen möchten, und erfordert andernfalls keine Änderungen.

Änderungen in pyproject.toml

Beachten Sie, dass der Inhalt von pyproject.toml im selben Format wie in PEP 621 ist.

Tabellenname

Werkzeuge MÜSSEN die von dieser PEP definierten Felder in einer Tabelle namens [external] angeben. Keine Werkzeuge dürfen Felder zu dieser Tabelle hinzufügen, die nicht von dieser PEP oder nachfolgenden PEPs definiert sind. Das Fehlen einer [external]-Tabelle bedeutet, dass das Paket entweder keine externen Abhängigkeiten hat oder die vorhandenen bereits auf dem System vorhanden sind.

build-requires/optional-build-requires

  • Format: Array von externen Abhängigkeitsspezifizierern (build-requires) und eine Tabelle mit Werten von Arrays von externen Abhängigkeitsspezifizierern (optional-build-requires).
  • Core-Metadaten: N/A

Die (optionalen) externen Build-Anforderungen, die zum Erstellen des Projekts benötigt werden.

Für build-requires handelt es sich um einen Schlüssel, dessen Wert ein Array von Zeichenketten ist. Jede Zeichenkette stellt eine Build-Anforderung des Projekts dar und MUSS als gültiger externer Abhängigkeitsspezifizierer formatiert sein.

Für optional-build-requires handelt es sich um eine Tabelle, bei der jeder Schlüssel einen zusätzlichen Satz von Build-Anforderungen angibt und deren Wert ein Array von Zeichenketten ist. Die Zeichenketten der Arrays MÜSSEN gültige externe Abhängigkeitsspezifizierer sein.

host-requires/optional-host-requires

  • Format: Array von externen Abhängigkeitsspezifizierern (host-requires) und eine Tabelle mit Werten von Arrays von externen Abhängigkeitsspezifizierern (optional-host-requires) - Core-Metadaten: N/A

Die (optionalen) externen Host-Anforderungen, die zum Erstellen des Projekts benötigt werden.

Für host-requires handelt es sich um einen Schlüssel, dessen Wert ein Array von Zeichenketten ist. Jede Zeichenkette stellt eine Host-Anforderung des Projekts dar und MUSS als gültiger externer Abhängigkeitsspezifizierer formatiert sein.

Für optional-host-requires handelt es sich um eine Tabelle, bei der jeder Schlüssel einen zusätzlichen Satz von Host-Anforderungen angibt und deren Wert ein Array von Zeichenketten ist. Die Zeichenketten der Arrays MÜSSEN gültige externe Abhängigkeitsspezifizierer sein.

dependencies/optional-dependencies

  • Format: Array von externen Abhängigkeitsspezifizierern (dependencies) und eine Tabelle mit Werten von Arrays von externen Abhängigkeitsspezifizierern (optional-dependencies).
  • Core-Metadaten: Requires-External-Dep, Provides-External-Extra

Die (optionalen) Laufzeitabhängigkeiten des Projekts.

Für dependencies handelt es sich um einen Schlüssel, dessen Wert ein Array von Zeichenketten ist. Jede Zeichenkette stellt eine Abhängigkeit des Projekts dar und MUSS als gültiger externer Abhängigkeitsspezifizierer formatiert sein. Jede Zeichenkette muss dem Core Metadata als Feld Requires-External-Dep hinzugefügt werden.

Für optional-dependencies handelt es sich um eine Tabelle, bei der jeder Schlüssel ein *Extra* angibt und deren Wert ein Array von Zeichenketten ist. Die Zeichenketten der Arrays MÜSSEN gültige externe Abhängigkeitsspezifizierer sein. Für jede Gruppe von optional-dependencies

  • Der Name der Gruppe MUSS dem Core Metadata als Feld Provides-External-Extra hinzugefügt werden.
  • Die externen Abhängigkeitsspezifizierer in dieser Gruppe MÜSSEN dem Core Metadata als Feld Requires-External-Dep hinzugefügt werden, mit dem entsprechenden Umgebungsmarker ; extra == 'name'.

dependency-groups

  • Format: Eine Tabelle, bei der jeder Schlüssel der Name der Gruppe ist und die Werte Arrays von externen Abhängigkeitsspezifizierern, Tabellen oder eine Mischung aus beidem sind.
  • Core-Metadaten: N/A

PEP 735-Style Abhängigkeitsgruppen, aber unter Verwendung externer Abhängigkeitsspezifizierer anstelle von PEP 508-Strings. Alle anderen Details (z. B. Gruppeneinbeziehung, Namensnormalisierung) folgen der offiziellen Spezifikation für Abhängigkeitsgruppen.

Beispiele

Diese Beispiele zeigen, wie der Inhalt der Tabelle [external] für eine Reihe von Paketen und der entsprechende Inhalt von PKG-INFO/METADATA (falls vorhanden) aussehen sollen.

cryptography 39.0

Inhalt von pyproject.toml

[external]
build-requires = [
  "dep:virtual/compiler/c",
  "dep:virtual/compiler/rust",
  "dep:generic/pkg-config",
]
host-requires = [
  "dep:generic/openssl",
  "dep:generic/libffi",
]

Inhalt von PKG-INFO / METADATA: N/A.

SciPy 1.10

Inhalt von pyproject.toml

[external]
build-requires = [
  "dep:virtual/compiler/c",
  "dep:virtual/compiler/cpp",
  "dep:virtual/compiler/fortran",
  "dep:generic/ninja",
  "dep:generic/pkg-config",
]
host-requires = [
  "dep:virtual/interface/blas",
  "dep:virtual/interface/lapack@>=3.7.1",
]

Inhalt von PKG-INFO / METADATA: N/A.

Pillow 10.1.0

Inhalt von pyproject.toml

[external]
build-requires = [
  "dep:virtual/compiler/c",
]
host-requires = [
  "dep:generic/libjpeg",
  "dep:generic/zlib",
]

[external.optional-host-requires]
extra = [
  "dep:generic/lcms2",
  "dep:generic/freetype",
  "dep:generic/libimagequant",
  "dep:generic/libraqm",
  "dep:generic/libtiff",
  "dep:generic/libxcb",
  "dep:generic/libwebp",
  "dep:generic/openjpeg@>=2.0",
  "dep:generic/tk",
]

Inhalt von PKG-INFO / METADATA: N/A.

Spyder 6.0

Inhalt von pyproject.toml

[external]
dependencies = [
  "dep:cargo/ripgrep",
  "dep:cargo/tree-sitter-cli",
  "dep:golang/github.com/junegunn/fzf",
]

Inhalt von PKG-INFO / METADATA

Requires-External-Dep: dep:cargo/ripgrep
Requires-External-Dep: dep:cargo/tree-sitter-cli
Requires-External-Dep: dep:golang/github.com/junegunn/fzf

jupyterlab-git 0.41.0

Inhalt von pyproject.toml

[external]
dependencies = [
  "dep:generic/git",
]

[external.optional-build-requires]
dev = [
  "dep:generic/nodejs",
]

Inhalt von PKG-INFO / METADATA

Requires-External-Dep: dep:generic/git

PyEnchant 3.2.2

Inhalt von pyproject.toml

[external]
dependencies = [
  # libenchant is needed on all platforms but vendored into wheels
  # distributed on PyPI for Windows. Hence choose to encode that in
  # the metadata. Note: there is no completely unambiguous way to do
  # this; another choice is to leave out the environment marker in the
  # source distribution and either live with the unnecessary ``METADATA``
  # entry in the distributed Windows wheels, or to apply a patch to this
  # metadata when building those wheels.
  "dep:github/AbiWord/enchant; platform_system!='Windows'",
]

Inhalt von PKG-INFO / METADATA

Requires-External-Dep: dep:github/AbiWord/enchant; platform_system!="Windows"

Mit Abhängigkeitsgruppen

Inhalt von pyproject.toml

[external.dependency-groups]
dev = [
  "dep:generic/catch2",
  "dep:generic/valgrind",
]

Inhalt von PKG-INFO / METADATA: N/A.

Abwärtskompatibilität

Es gibt keine Auswirkungen auf die Abwärtskompatibilität, da diese PEP nur neue, optionale Metadaten hinzufügt. In Abwesenheit solcher Metadaten ändert sich nichts für Paketautoren oder Paketierungs-Tools.

Die einzige in dieser PEP eingeführte Änderung, die Auswirkungen auf bestehende Projekte hat, ist die Veralterung des External-Requires Core-Metadatenfelds. Wir schätzen die Auswirkungen dieser Veralterung als vernachlässigbar ein, angesichts ihrer geringen Verbreitung im Ökosystem (siehe Rationale).

Das Feld wird weiterhin von bestehenden Tools wie setuptools-ext erkannt, aber seine Verwendung wird im Python Packaging User Guide abgeraten, ähnlich wie bei veralteten Feldern wie Requires (veraltet zugunsten von Requires-Dist).

Sicherheitsimplikationen

Es gibt keine direkten Sicherheitsbedenken, da diese PEP die statische Definition von Metadaten für externe Abhängigkeiten abdeckt. Etwaige Sicherheitsprobleme würden daraus resultieren, wie Tools die Metadaten konsumieren und darauf reagieren.

Wie man das lehrt

Externe Abhängigkeiten und ob und wie diese externen Abhängigkeiten vendort werden, sind Themen, die von Python-Paketautoren normalerweise nicht im Detail verstanden werden. Wir beabsichtigen, vom Verständnis auszugehen, wie eine externe Abhängigkeit definiert wird, die verschiedenen Arten, wie von ihr abgängig sein kann – von Laufzeit-only mit ctypes oder einem subprocess-Aufruf bis hin zu einer Build-Abhängigkeit, die dagegen gelinkt ist – bevor wir uns mit der Deklaration externer Abhängigkeiten in Metadaten befassen. Die Dokumentation sollte deutlich machen, was für Paketautoren relevant ist und was für Distributoren.

Material zu diesem Thema wird zu den relevantesten Paketierungs-Tutorials hinzugefügt, hauptsächlich zum Python Packaging User Guide. Darüber hinaus erwarten wir, dass jeder Build-Backend, der Unterstützung für externe Abhängigkeitsmetadaten hinzufügt, dies in seiner Dokumentation erwähnt, ebenso wie Tools wie auditwheel.

Referenzimplementierung

Diese PEP enthält eine Metadatenspezifikation, anstatt eine Codefunktion – daher wird es keinen Code geben, der die Metadatenspezifikation als Ganzes implementiert. Es gibt jedoch Teile, die eine Referenzimplementierung haben.

  1. Die Tabelle [external] muss gültiges TOML sein und kann daher mit tomllib geladen werden. Diese Tabelle kann mit dem Paket pyproject-external weiterverarbeitet werden, wie unten gezeigt.
  2. Die PURL-Spezifikation ist als wichtiger Teil dieser Spezifikation ein Python-Paket mit einer Referenzimplementierung für die Erstellung und das Parsen von PURLs: packageurl-python. Dieses Paket ist in pyproject-external eingebunden, um DepURL-spezifische Validierung und Handhabung bereitzustellen.

Es gibt mehrere mögliche Konsumenten und Anwendungsfälle für diese Metadaten, sobald diese Metadaten zu Python-Paketen hinzugefügt werden. Getestete Metadaten für die 150 meist heruntergeladenen Pakete von PyPI mit veröffentlichten plattformspezifischen Wheels finden Sie unter rgommers/external-deps-build. Diese Metadaten wurden durch die Verwendung zum Erstellen von Wheels aus mit diesen Metadaten gepatchten sdists in sauberen Docker-Containern validiert.

Beispiel

Gegeben sei eine pyproject.toml mit dieser Tabelle [external].

[external]
build-requires = [
  "dep:virtual/compiler/c",
  "dep:virtual/compiler/rust",
  "dep:generic/pkg-config",
]
host-requires = [
  "dep:generic/openssl",
  "dep:generic/libffi",
]

Sie können pyproject_external.External verwenden, um sie zu parsen und zu manipulieren.

>>> from pyproject_external import External
>>> external = External.from_pyproject_path("./pyproject.toml")
>>> external.validate()
>>> external.to_dict()
{'external': {'build_requires': ['dep:virtual/compiler/c', 'dep:virtual/compiler/rust', 'dep:generic/pkg-config'], 'host_requires': ['dep:generic/openssl', 'dep:generic/libffi']}}
>>> external.build_requires
[DepURL(type='virtual', namespace='compiler', name='c', version=None, qualifiers={}, subpath=None), DepURL(type='virtual', namespace='compiler', name='rust', version=None, qualifiers={}, subpath=None), DepURL(type='generic', namespace=None, name='pkg-config', version=None, qualifiers={}, subpath=None)]
>>> external.build_requires[0]
DepURL(type='virtual', namespace='compiler', name='c', version=None, qualifiers={}, subpath=None)

Beachten Sie, dass die vorgeschlagene Tabelle [external] wohlgeformt war. Mit ungültigen Inhalten wie

[external]
build-requires = [
  "dep:this-is-missing-the-type",
  "pkg:not-a-dep-url"
]

Würden Sie die Validierung nicht bestehen.

>>> external = External.from_pyproject_data(
  {
    "external": {
      "build_requires": [
        "dep:this-is-missing-the-type",
        "pkg:not-a-dep-url"
      ]
    }
  }
)
ValueError: purl is missing the required type component: 'dep:this-is-missing-the-type'.

Abgelehnte Ideen

Spezielle Syntax für externe Abhängigkeiten, die auch auf PyPI verpackt sind

Es gibt Nicht-Python-Pakete, die auf PyPI verpackt sind, wie z.B. Ninja, patchelf und CMake. Was typischerweise gewünscht ist, ist die Verwendung der Systemversion dieser Pakete, und wenn diese auf dem System nicht vorhanden ist, dann die Installation des PyPI-Pakets dafür. Die Autoren glauben, dass eine spezifische Unterstützung für dieses Szenario nicht notwendig ist (oder zumindest zu komplex, um eine solche Unterstützung zu rechtfertigen); ein Abhängigkeitsanbieter für externe Abhängigkeiten kann PyPI als eine mögliche Quelle für den Erhalt des Pakets behandeln. Eine beispielhafte Zuordnung für diesen Anwendungsfall wird in PEP 804 vorgeschlagen.

Verwendung von Bibliotheks- und Header-Namen als externe Abhängigkeiten

Ein früherer Entwurf der PEP („External dependencies“ (2015)) schlug die Verwendung spezifischer Bibliotheks- und Header-Namen als externe Abhängigkeiten vor. Dies ist sowohl zu granular als auch unzureichend (z. B. sind Header oft unverändert; mehrere Pakete können denselben Header oder dieselbe Bibliothek bereitstellen). Die Verwendung von Paketnamen ist ein etabliertes Muster in allen Paketierungs-Ökosystemen und sollte bevorzugt werden.

Aufteilen von Host-Abhängigkeiten mit expliziten -dev- oder -devel-Suffixen

Diese Konvention ist weder in den Paketierungs-Ökosystemen konsistent noch wird sie von Upstream-Paketautoren allgemein akzeptiert. Da die Notwendigkeit einer expliziten Kontrolle (z. B. das Installieren von Headern, wenn ein Paket als Laufzeit- und nicht als Build-Zeit-Abhängigkeit verwendet wird) eher Nischeninteresse hat und wir keine Designkomplexität ohne ausreichend klare Anwendungsfälle hinzufügen wollen, haben wir uns entschieden, uns ausschließlich auf die Aufteilung in die Kategorien build, host und run zu verlassen, wobei Tools dafür verantwortlich sind, welche Kategorie in einem kontextabhängigen Fall gilt.

Wenn sich dies als unzureichend erweist, könnte eine zukünftige PEP die URL-Qualifizierer-Funktionen des PURL-Schemas (?key=value) verwenden, um die notwendigen Anpassungen zu implementieren. Dies kann rückwärtskompatibel erfolgen.

Indirektionen von Identifikatoren

Einige Ökosysteme weisen Methoden auf, um Pakete basierend auf parametrisierten Funktionen wie cmake("dependency") oder compiler("language") auszuwählen, die Paketnamen basierend auf zusätzlichem Kontext oder Konfiguration zurückgeben. Diese Funktion ist wohl nicht sehr verbreitet und wird selbst dann selten verwendet. Darüber hinaus macht ihre dynamische Natur sie anfällig für sich ändernde Bedeutungen im Laufe der Zeit, und die Abhängigkeit von spezifischen Build-Systemen für die Namensauflösung ist im Allgemeinen keine gute Idee.

Die Autoren bevorzugen statische Bezeichner, die explizit über bekannte Metadaten zugeordnet werden können (z. B. wie in PEP 804 vorgeschlagen).

Ökosysteme, die diese Indirektionen implementieren, können sie verwenden, um die Infrastruktur zu unterstützen, die zum Generieren der in PEP 804 vorgeschlagenen Zuordnungen dient.

Hinzufügen eines host-requires-Schlüssels unter [build-system]

Das Hinzufügen von host-requires für Host-Abhängigkeiten auf PyPI, um die Namenszuordnung zu anderen Paketierungssystemen mit Cross-Compilation-Unterstützung besser zu unterstützen, erscheint prinzipiell nützlich, aus denselben Gründen, wie diese PEP host-requires unter der Tabelle [external] hinzufügt. Es ist jedoch nicht notwendig, dies in diese PEP aufzunehmen, und daher ziehen es die Autoren vor, den Geltungsbereich dieser PEP begrenzt zu halten – eine zukünftige PEP zur Cross-Compilation könnte sich damit befassen. Dieses Issue enthält weitere Argumente für und gegen die Aufnahme von host-requires unter [build-system] als Teil dieser PEP.

Wiederverwendung des Requires-External-Feldes in den Core-Metadaten

Die Spezifikation der Core Metadata enthält ein relevantes Feld, nämlich Requires-External. Obwohl es auf den ersten Blick ein guter Kandidat wäre, die Metadaten der Tabelle external.dependencies zu speichern, haben sich die Autoren entschieden, dieses Feld nicht zur Weitergabe der externen Laufzeitabhängigkeitsmetadaten wiederzuverwenden.

Das Feld Requires-External hat in Version 2.4 eine sehr vage definierte Semantik. Im Wesentlichen: name [(version)][; environment marker] (wobei eckige Klammern optionale Felder bedeuten). Es ist nicht definiert, welche Zeichenketten für name gültig sind; das Beispiel in der Spezifikation verwendet sowohl „C“ als Sprachnamen als auch „libpng“ als Paketnamen. Eine Verschärfung der Semantik wäre rückwärtsinkompatibel, und die Beibehaltung des aktuellen Zustands scheint unbefriedigend. DepURLs müssten zerlegt werden, um in diese Syntax zu passen.

Zulassen der Verwendung von Ökosystem-spezifischer Versionsvergleichssemantik

Es gibt Fälle, insbesondere bei der Behandlung von Vorabversionen, in denen die PEP 440-Semantik für Versionsvergleiche nicht ganz funktioniert. Zum Beispiel kann 1.2.3a eine Veröffentlichung nach 1.2.3 anzeigen und nicht eine Alpha-Version. Um solche Fälle korrekt zu behandeln, wäre es notwendig, beliebige Versionierungsschemata zuzulassen. Die Autoren dieser PEP sind der Meinung, dass der Mehrwert, dies zuzulassen, durch die zusätzliche Komplexität nicht gerechtfertigt ist. Wenn gewünscht, kann ein Paketautor entweder einen Code-Kommentar oder das Feld qualifier einer DepURL (siehe Abschnitt Versionierung unter Rationale) verwenden, um dieses Detaillierungsniveau zu erfassen.

Offene Fragen

Derzeit keine.

Referenzen


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

Zuletzt geändert: 2025-09-29 13:22:31 GMT