PEP 633 – Abhängigkeitsspezifikation in pyproject.toml mit aufgeklappter TOML-Tabelle
- Autor:
- Laurie Opperman <laurie_opperman at hotmail.com>, Arun Babu Neelicattu <arun.neelicattu at gmail.com>
- Sponsor:
- Brett Cannon <brett at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Thema:
- Packaging
- Erstellt:
- 02-Sep-2020
- Post-History:
- 02-Sep-2020
- Resolution:
- Discourse-Nachricht
Ablehnungsbescheid
Dieser PEP wurde zugunsten von PEP 631 abgelehnt, da dieser aufgrund seiner Beliebtheit, seiner Konsistenz mit der bestehenden Verwendung von PEP 508-Strings und seiner Kompatibilität mit bestehenden Packaging-Tool-Suiten bevorzugt wurde.
Zusammenfassung
Dieser PEP legt fest, wie die Abhängigkeiten eines Projekts in einer pyproject.toml-Datei für die Nutzung durch Packaging-bezogene Tools unter Verwendung der in PEP 621 definierten Felder geschrieben werden, als Alternative zu dem in PEP 631 definierten, auf PEP 508 basierenden Ansatz.
Motivation
Es gibt mehrere Vorteile bei der Verwendung von TOML-Tabellen und anderen Datentypen zur Darstellung von Anforderungen im Vergleich zu PEP 508-Strings.
- Einfache anfängliche Validierung durch die TOML-Syntax.
- Einfache sekundäre Validierung mit einem Schema, z. B. einem JSON-Schema.
- Potenzial für Benutzer, die Schlüssel von gegebenen Funktionen zu erraten, anstatt eine Syntax auswendig zu lernen.
- Benutzer mehrerer anderer populärer Sprachen sind möglicherweise bereits mit der TOML-Syntax vertraut.
- TOML repräsentiert direkt die gleichen Datenstrukturen wie in JSON und damit eine Teilmenge von Python-Literalen, sodass Benutzer die Hierarchie und den Werttyp verstehen können.
Begründung
Ein Großteil davon stammt aus Diskussionen im PEP 621 Dependencies Topic. Dies enthält Elemente aus Pipfile, Poetry, Dart’s Dependencies und Rust’s Cargo. Ein Vergleichsdokument zeigt Vorteile und Nachteile zwischen diesem Format und PEP 508-Style-Spezifizierern.
Bei der Spezifikation mehrerer Anforderungen mit demselben Verteilungsnamen (bei denen Umgebungsmarker die passende Abhängigkeit wählen) ist die gewählte Lösung ähnlich der von Poetry, bei der ein Array von Anforderungen zulässig ist.
Die Schlüssel für direkte Referenzen stimmen eng mit PEP 610 und PEP 440 überein und nutzen diese, um Unterschiede im Packaging-Ökosystem zu reduzieren und auf früheren Spezifikationsarbeiten aufzubauen.
Spezifikation
Wie in PEP 621 gilt: Wenn Metadaten falsch angegeben werden, MÜSSEN Tools einen Fehler ausgeben. Die Metadaten MÜSSEN der TOML-Spezifikation entsprechen.
Um Verwirrung zu vermeiden, da dieses Dokument eine Spezifikation für die Angabe von Abhängigkeiten ist, wird das Wort „Anforderung“ verwendet, um eine PEP 508-Abhängigkeitsspezifikation zu bedeuten.
Die folgenden Tabellen werden zur project-Tabelle hinzugefügt, die in PEP 621 spezifiziert ist.
dependencies
Format: Tabelle
Die Schlüssel in dieser Tabelle sind die Namen der benötigten Distribution. Die Werte können einen der folgenden Typen haben:
- String: Die Anforderung wird nur durch eine Versionsanforderung definiert, mit der gleichen Spezifikation wie
versionin der Anforderungstabelle, mit der Ausnahme, dass ein leerer String""keine Einschränkung der Version bedeutet. - Tabelle: eine Anforderungstabelle.
- Array: ein Array von Anforderungstabellen. Es ist ein Fehler, ein leeres Array
[]als Wert anzugeben.
Anforderungstabelle
Die Schlüssel der Anforderungstabelle sind wie folgt (alle sind optional):
version(string): Ein PEP 440-Versionsspezifizierer, der eine durch Kommas getrennte Liste von Versionsspezifiziererklauseln ist. Der String MUSS nicht leer sein.extras(Array von Strings): Eine Liste von PEP 508-Extra-Deklarationen für die Distribution. Die Liste MUSS nicht leer sein.markers(string): Ein PEP 508-Umgebungsmarker-Ausdruck. Der String MUSS nicht leer sein.url(string): Die URL des Artefakts zur Installation und Erfüllung der Anforderung. Beachten Sie, dassfile://das Präfix ist, das für Pakete verwendet wird, die aus dem lokalen Dateisystem abgerufen werden.git,hg,bzrodersvn(string): Die URL eines VCS-Repositorys (wie in PEP 440 spezifiziert), das geklont wird und dessen Baum zur Erfüllung der Anforderung installiert wird. Weitere VCS-Schlüssel werden durch Ergänzungen zu PEP 610 hinzugefügt. Tools KÖNNEN sich jedoch dafür entscheiden, andere VCSs mit ihrem Befehlszeilenbefehl vor der Annahme der Ergänzung zu unterstützen.revision(string): Die Kennung einer bestimmten Revision des angegebenen VCS-Repositorys, die vor der Installation ausgecheckt werden muss. Benutzer dürfen dies nur angeben, wenn einer der Schlüsselgit,hg,bzr,svnoder ein anderer VCS-Schlüssel zur Identifizierung der zu installierenden Distribution verwendet wird. Revisionskennungen werden in PEP 610 vorgeschlagen.
Höchstens einer der folgenden Schlüssel kann gleichzeitig angegeben werden, da sie sich logisch widersprechen: version, url, git, hg, bzr, svn und jeder andere VCS-Schlüssel.
Eine leere Anforderungstabelle {} stellt zusätzlich zum leeren String "" keine Einschränkung der Anforderung dar.
Alle angegebenen Schlüssel, die in diesem Dokument nicht spezifiziert sind, MÜSSEN zu einem Fehler beim Parsen führen.
optionale-abhängigkeiten
Format: Tabelle
Die Schlüssel in dieser Tabelle sind die Namen der benötigten Distribution eines Extras. Die Werte können einen der folgenden Typen haben:
- Tabelle: eine Anforderungstabelle.
- Array: Ein Array von Anforderungstabellen.
Diese Anforderungstabellen haben die gleiche Spezifikation wie oben, mit der zusätzlichen folgenden erforderlichen Schlüssel:
for-extra(string): Der Name des PEP 508-Extras, für das diese Anforderung benötigt wird.
Referenzimplementierung
Tools müssen dieses Format in PEP 508-Anforderungsstrings konvertieren. Nachfolgend finden Sie ein Beispiel für die Implementierung dieser Konvertierung (unter der Annahme, dass die Validierung bereits durchgeführt wurde).
def convert_requirement_to_pep508(name, requirement):
if isinstance(requirement, str):
requirement = {"version": requirement}
pep508 = name
if "extras" in requirement:
pep508 += " [" + ", ".join(requirement["extras"]) + "]"
if "version" in requirement:
pep508 += " " + requirement["version"]
if "url" in requirement:
pep508 += " @ " + requirement["url"]
for vcs in ("git", "hg", "bzr", "svn"):
if vcs in requirement:
pep508 += " @ " + vcs + "+" + requirement[vcs]
if "revision" in requirement:
pep508 += "@" + requirement["revision"]
extra = None
if "for-extra" in requirement:
extra = requirement["for-extra"]
if "markers" in requirement:
markers = requirement["markers"]
if extra:
markers = "extra = '" + extra + "' and (" + markers + ")"
pep508 += "; " + markers
return pep508, extra
def convert_requirements_to_pep508(dependencies):
pep508s = []
extras = set()
for name, req in dependencies.items():
if isinstance(req, list):
for sub_req in req:
pep508, extra = convert_requirement_to_pep508(name, sub_req)
pep508s.append(pep508)
if extra:
extras.add(extra)
else:
pep508, extra = convert_requirement_to_pep508(name, req)
pep508s.append(pep508)
if extra:
extras.add(extra)
return pep508s, extras
def convert_project_requirements_to_pep508(project):
reqs, _ = convert_requirements_to_pep508(project.get("dependencies", {}))
optional_reqs, extras = convert_requirements_to_pep508(
project.get("optional-dependencies", {})
)
reqs += optional_reqs
return reqs, extras
JSON-Schema
Für die anfängliche Validierung kann ein JSON-Schema verwendet werden. Dies hilft nicht nur Tools bei einer konsistenten Validierung, sondern ermöglicht es Code-Editoren, Validierungsfehler hervorzuheben, während Benutzer die Abhängigkeitsliste erstellen.
{
"$id": "spam",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Project metadata",
"type": "object",
"definitions": {
"requirementTable": {
"title": "Full project dependency specification",
"type": "object",
"properties": {
"extras": {
"title": "Dependency extras",
"type": "array",
"items": {
"title": "Dependency extra",
"type": "string"
}
},
"markers": {
"title": "Dependency environment markers",
"type": "string"
}
},
"propertyNames": {
"enum": [
"extras",
"markers",
"version",
"url",
"git",
"hg",
"bzr",
"svn",
"for-extra"
]
},
"oneOf": [
{
"title": "Version requirement",
"properties": {
"version": {
"title": "Version",
"type": "string"
}
}
},
{
"title": "URL requirement",
"properties": {
"url": {
"title": "URL",
"type": "string",
"format": "uri"
}
},
"required": [
"url"
]
},
{
"title": "VCS requirement",
"properties": {
"revision": {
"title": "VCS repository revision",
"type": "string"
}
},
"oneOf": [
{
"title": "Git repository",
"properties": {
"git": {
"title": "Git URL",
"type": "string",
"format": "uri"
}
},
"required": [
"git"
]
},
{
"title": "Mercurial repository",
"properties": {
"hg": {
"title": "Mercurial URL",
"type": "string",
"format": "uri"
}
},
"required": [
"hg"
]
},
{
"title": "Bazaar repository",
"properties": {
"bzr": {
"title": "Bazaar URL",
"type": "string",
"format": "uri"
}
},
"required": [
"bzr"
]
},
{
"title": "Subversion repository",
"properties": {
"svn": {
"title": "Subversion URL",
"type": "string",
"format": "uri"
}
},
"required": [
"svn"
]
}
]
}
]
},
"requirementVersion": {
"title": "Version project dependency specification",
"type": "string"
},
"requirement": {
"title": "Project dependency specification",
"oneOf": [
{
"$ref": "#/definitions/requirementVersion"
},
{
"$ref": "#/definitions/requirementTable"
},
{
"title": "Multiple specifications",
"type": "array",
"items": {
"$ref": "#/definitions/requirementTable"
},
"minLength": 1
}
]
},
"optionalRequirementTable": {
"title": "Project optional dependency specification table",
"allOf": [
{
"$ref": "#/definitions/requirementTable"
},
{
"properties": {
"for-extra": {
"title": "Dependency's extra",
"type": "string"
}
},
"required": [
"for-extra"
]
}
]
},
"optionalRequirement": {
"title": "Project optional dependency specification",
"oneOf": [
{
"$ref": "#/definitions/optionalRequirementTable"
},
{
"title": "Multiple specifications",
"type": "array",
"items": {
"$ref": "#/definitions/optionalRequirementTable"
},
"minLength": 1
}
]
}
},
"properties": {
"dependencies": {
"title": "Project dependencies",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/requirement"
}
},
"optional-dependencies": {
"title": "Project dependencies",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/optionalRequirement"
}
}
}
}
Beispiele
Vollständiges künstliches Beispiel
[project.dependencies]
flask = { }
django = { }
requests = { version = ">= 2.8.1, == 2.8.*", extras = ["security", "tests"], markers = "python_version < '2.7'" }
pip = { url = "https://github.com/pypa/pip/archive/1.3.1.zip" }
sphinx = { git = "ssh://git@github.com/sphinx-doc/sphinx.git" }
numpy = "~=1.18"
pytest = [
{ version = "<6", markers = "python_version < '3.5'" },
{ version = ">=6", markers = "python_version >= '3.5'" },
]
[project.optional-dependencies]
pytest-timout = { for-extra = "dev" }
pytest-mock = [
{ version = "<6", markers = "python_version < '3.5'", for-extra = "dev" },
{ version = ">=6", markers = "python_version >= '3.5'", for-extra = "dev" },
]
In Anlehnung an PEP 631 ist das Folgende eine äquivalente Abhängigkeitsspezifikation für docker-compose.
[project.dependencies]
cached-property = ">= 1.2.0, < 2"
distro = ">= 1.2.0, < 2"
docker = { extras = ["ssh"], version = ">= 4.2.2, < 5" }
docopt = ">= 0.6.1, < 1"
jsonschema = ">= 2.5.1, < 4"
PyYAML = ">= 3.10, < 6"
python-dotenv = ">= 0.13.0, < 1"
requests = ">= 2.20.0, < 3"
texttable = ">= 0.9.0, < 2"
websocket-client = ">= 0.32.0, < 1"
# Conditional
"backports.shutil_get_terminal_size" = { version = "== 1.0.0", markers = "python_version < '3.3'" }
"backports.ssl_match_hostname" = { version = ">= 3.5, < 4", markers = "python_version < '3.5'" }
colorama = { version = ">= 0.4, < 1", markers = "sys_platform == 'win32'" }
enum34 = { version = ">= 1.0.4, < 2", markers = "python_version < '3.4'" }
ipaddress = { version = ">= 1.0.16, < 2", markers = "python_version < '3.3'" }
subprocess32 = { version = ">= 3.5.4, < 4", markers = "python_version < '3.2'" }
[project.optional-dependencies]
PySocks = { version = ">= 1.5.6, != 1.5.7, < 2", for-extra = "socks" }
ddt = { version = ">= 1.2.2, < 2", for-extra = "tests" }
pytest = { version = "< 6", for-extra = "tests" }
mock = { version = ">= 1.0.1, < 4", markers = "python_version < '3.4'", for-extra = "tests" }
Kompatibilitätsbeispiele
Die Autoren dieses PEP erkennen an, dass verschiedene Tools sowohl aus diesem Format lesen als auch in dieses schreiben müssen, um Abhängigkeiten zu spezifizieren. Dieser Abschnitt zielt darauf ab, einen direkten Vergleich mit dem derzeit verwendeten Standard, PEP 508, sowie Beispiele für die Übersetzung zu und von diesem Standard bereitzustellen.
Hinweis
Zur Vereinfachung und Klarheit werden verschiedene Möglichkeiten, wie TOML die Spezifikation ermöglicht, nicht dargestellt. Diese Beispiele verwenden die standardmäßige Inline-Darstellung.
Zum Beispiel, während die folgenden denkbaren gleichwertigen Darstellungen in TOML sind, wählen wir die zweite Form für die Beispiele in diesem Abschnitt.
aiohttp.version = "== 3.6.2"
aiohttp = { version = "== 3.6.2" }
Versionsbeschränkte Abhängigkeiten
Keine Versionsbeschränkung
aiohttp
aiohttp = {}
Einfache Versionsbeschränkung
aiohttp >= 3.6.2, < 4.0.0
aiohttp = { version = ">= 3.6.2, < 4.0.0" }
Hinweis
Dies kann aus Gründen der Kürze auch als String dargestellt werden.
aiohttp = ">= 3.6.2, < 4.0.0"
Direkte Referenzabhängigkeiten
URL-Abhängigkeit
aiohttp @ https://files.pythonhosted.org/packages/97/d1/1cc7a1f84097d7abdc6c09ee8d2260366f081f8e82da36ebb22a25cdda9f/aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl
aiohttp = { url = "https://files.pythonhosted.org/packages/97/d1/1cc7a1f84097d7abdc6c09ee8d2260366f081f8e82da36ebb22a25cdda9f/aiohttp-3.6.2-cp35-cp35m-macosx_10_13_x86_64.whl" }
VCS-Abhängigkeit
aiohttp @ git+ssh://git@github.com/aio-libs/aiohttp.git@master
aiohttp = { git = "ssh://git@github.com/aio-libs/aiohttp.git", revision = "master" }
Umgebungsmarker
aiohttp >= 3.6.1; python_version >= '3.8'
aiohttp = { version = ">= 3.6.1", markers = "python_version >= '3.8'" }
Ein leicht erweitertes Beispiel für das Obige, bei dem eine bestimmte Version von aiohttp basierend auf der Interpreterversion benötigt wird.
aiohttp >= 3.6.1; python_version >= '3.8'
aiohttp >= 3.0.0, < 3.6.1; python_version < '3.8'
aiohttp = [
{ version = ">= 3.6.1", markers = "python_version >= '3.8'" },
{ version = ">= 3.0.0, < 3.6.1", markers = "python_version < '3.8'" }
]
Paket-Extras
Spezifikation der Abhängigkeit für ein Paket-Extra
aiohttp >= 3.6.2; extra == 'http'
aiohttp = { version = ">= 3.6.2", for-extra = "http" }
Verwendung von Extras aus einer Abhängigkeit
aiohttp [speedups] >= 3.6.2
aiohttp = { version = ">= 3.6.2", extras = ["speedups"] }
Komplexe Beispiele
Versionsbeschränkung
aiohttp [speedups] >= 3.6.2; python_version >= '3.8' and extra == 'http'
aiohttp = { version = ">= 3.6.2", extras = ["speedups"], markers = "python_version >= '3.8'", for-extra = "http" }
Direkte Referenz (VCS)
aiohttp [speedups] @ git+ssh://git@github.com/aio-libs/aiohttp.git@master ; python_version >= '3.8' and extra == 'http'
aiohttp = { git = "ssh://git@github.com/aio-libs/aiohttp.git", revision = "master", extras = ["speedups"], markers = "python_version >= '3.8'", for-extra = "http" }
Abgelehnte Ideen
Wechsel zu einem Array für dependencies
Verwenden Sie ein Array anstelle einer Tabelle, damit jedes Element nur eine Tabelle (mit einem name-Schlüssel) und keine Arrays von Anforderungstabellen ist. Dies war im TOML-Format sehr ausführlich und einschränkend, und das Vorhandensein mehrerer Anforderungen für eine gegebene Distribution ist nicht sehr verbreitet.
Ersetzen von optional-dependencies durch extras
Entfernen Sie die Tabelle optional-dependencies zugunsten der Aufnahme eines optional-Schlüssels in die Anforderung und einer extras-Tabelle, die angibt, welche (optionalen) Anforderungen für ein Projekt-Extra benötigt werden. Dies reduziert die Anzahl der Tabellen mit der gleichen Spezifikation (auf 1) und ermöglicht es, Anforderungen einmal zu spezifizieren, aber in mehreren Extras zu verwenden. Dies entfernt jedoch einige Eigenschaften der Anforderung (zu welchem Extra sie gehört), gruppiert erforderliche und optionale Abhängigkeiten zusammen (möglicherweise gemischt), und es gibt möglicherweise keine einfache Möglichkeit, eine Anforderung auszuwählen, wenn eine Distribution mehrere Anforderungen hat. Dies wurde abgelehnt, da optional-dependencies bereits im Entwurf von PEP 621 verwendet wurde.
direct-Tabelle in der Anforderung
Fügen Sie die Schlüssel für direkte Referenzen in eine direct-Tabelle ein, wobei der VCS als Wert eines vcs-Schlüssels angegeben wird. Dies war expliziter und leichter in eine JSON-Schema-Validierung einzubeziehen, wurde aber als zu umständlich und nicht so lesbar entschieden.
Hash einschließen
Fügen Sie Hashes in Direktverweis-Anforderungen ein. Dies war nur für Paket-Lockfiles gedacht und hatte keinen wirklichen Platz in den Projektmetadaten.
Abhängigkeitstabellen für jedes Extra
Machen Sie optional-dependencies zu einer Tabelle von Abhängigkeitstabellen für jedes Extra, wobei der Tabellenname der Name des Extras ist. Dies macht optional-dependencies zu einem anderen Typ (Tabelle von Tabellen von Anforderungen) als dependencies (Tabelle von Anforderungen), was für Benutzer verwirrend und schwieriger zu parsen sein könnte.
Umgebungsmarker-Schlüssel
Machen Sie jeden PEP 508-Umgebungsmarker als Schlüssel (oder Schlüssel einer Untertabelle) in der Anforderung. Dies erhöht argumentativ die Lesbarkeit und einfache Verarbeitung. Der Schlüssel markers wäre weiterhin für fortgeschrittenere Spezifikationen zulässig, mit denen die schlüsselbasierten Umgebungsmarker mit dem Ergebnis verknüpft werden. Dies wurde zurückgestellt, da mehr Designarbeit erforderlich ist.
Mehrere Extras, die eine Anforderung erfüllen kann
Ersetzen Sie den Schlüssel for-extra durch for-extras, wobei der Wert ein Array von Extras ist, die die Anforderung erfüllt. Dies reduziert eine gewisse Duplizierung, aber in diesem Fall macht diese Duplizierung explizit, welche Extras welche Abhängigkeiten haben.
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-0633.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT