PEP 427 – Das Wheel-Binärpaketformat 1.0
- Autor:
- Daniel Holth <dholth at gmail.com>
- BDFL-Delegate:
- Alyssa Coghlan <ncoghlan at gmail.com>
- Discussions-To:
- Distutils-SIG Liste
- Status:
- Final
- Typ:
- Standards Track
- Thema:
- Packaging
- Erstellt:
- 20-Sep-2012
- Post-History:
- 18-Oct-2012, 15-Feb-2013
- Resolution:
- Python-Dev Nachricht
Inhaltsverzeichnis
- Zusammenfassung
- PEP-Akzeptanz
- Begründung
- Details
- FAQ
- Wheel definiert ein .data-Verzeichnis. Soll ich alle meine Daten dort ablegen?
- Warum enthält Wheel angehängte Signaturen?
- Warum erlaubt Wheel JWS-Signaturen?
- Warum erlaubt Wheel auch S/MIME-Signaturen?
- Was hat es mit „purelib“ im Gegensatz zu „platlib“ auf sich?
- Ist es möglich, Python-Code direkt aus einer Wheel-Datei zu importieren?
- Referenzen
- Anhang
- Urheberrecht
Zusammenfassung
Dieses PEP beschreibt ein integriertes Paketformat für Python namens „Wheel“.
Ein Wheel ist ein ZIP-formatiertes Archiv mit einem speziell formatierten Dateinamen und der Erweiterung .whl. Es enthält eine einzelne Distribution nahezu so, wie sie gemäß PEP 376 mit einem bestimmten Installationsschema installiert würde. Obwohl ein spezialisierter Installer empfohlen wird, kann eine Wheel-Datei durch einfaches Entpacken in site-packages mit dem Standardwerkzeug „unzip“ installiert werden, wobei genügend Informationen erhalten bleiben, um ihre Inhalte zu einem späteren Zeitpunkt auf ihre endgültigen Pfade zu verteilen.
PEP-Akzeptanz
Dieses PEP wurde von Alyssa Coghlan am 16. Februar 2013 angenommen und die definierte Wheel-Version auf 1.0 aktualisiert [1]
Begründung
Python benötigt ein Paketformat, das einfacher zu installieren ist als sdist. Pythons sdist-Pakete werden durch die Build-Systeme distutils und setuptools definiert und erfordern diese, führen beliebigen Code aus, um zu bauen und zu installieren, und kompilieren Code neu, nur damit er in einem neuen virtuellen Environment installiert werden kann. Dieses System, das Build- und Installationsvorgänge vermischt, ist langsam, schwer zu warten und behindert Innovationen sowohl bei Build-Systemen als auch bei Installern.
Wheel versucht, diese Probleme zu beheben, indem es eine einfachere Schnittstelle zwischen dem Build-System und dem Installer bereitstellt. Das Wheel-Binärpaketformat befreit Installer von der Notwendigkeit, das Build-System zu kennen, spart Zeit, indem es die Kompilierungszeit auf viele Installationen amortisiert, und beseitigt die Notwendigkeit, ein Build-System in der Zielumgebung zu installieren.
Details
Installation eines Wheels „distribution-1.0-py32-none-any.whl“
Die Wheel-Installation besteht notionell aus zwei Phasen
- Entpacken.
- Parsen von
distribution-1.0.dist-info/WHEEL. - Überprüfen, ob der Installer mit Wheel-Version kompatibel ist. Warnen, wenn die Nebenversion größer ist, abbrechen, wenn die Hauptversion größer ist.
- Wenn Root-Is-Purelib == ‚true‘, Archiv in purelib (site-packages) entpacken.
- Andernfalls Archiv in platlib (site-packages) entpacken.
- Parsen von
- Verteilen.
- Das entpackte Archiv enthält
distribution-1.0.dist-info/und (falls vorhanden)distribution-1.0.data/. - Jeder Unterverzeichnisbaum von
distribution-1.0.data/auf seinen Zielpfad verschieben. Jedes Unterverzeichnis vondistribution-1.0.data/ist ein Schlüssel in einem Dictionary von Zielverzeichnissen, wie z. B.distribution-1.0.data/(purelib|platlib|headers|scripts|data). Die anfänglich unterstützten Pfade stammen ausdistutils.command.install. - Falls zutreffend, Skripte, die mit
#!pythonbeginnen, aktualisieren, um auf den korrekten Interpreter zu verweisen. - Aktualisieren von
distribution-1.0.dist-info/RECORDmit den installierten Pfaden. - Leeres Verzeichnis
distribution-1.0.dataentfernen. - Alle installierten .py-Dateien nach .pyc kompilieren. (Deinstallationsprogramme sollten schlau genug sein, .pyc zu entfernen, auch wenn sie nicht in RECORD aufgeführt sind.)
- Das entpackte Archiv enthält
Empfohlene Installer-Funktionen
- Aktualisieren von
#!python. - In Wheel werden Skripte in
{distribution}-{version}.data/scripts/gepackt. Wenn die erste Zeile einer Datei inscripts/exaktb'#!python'lautet, wird sie so umgeschrieben, dass sie auf den korrekten Interpreter verweist. Unix-Installer müssen möglicherweise die +x-Berechtigung für diese Dateien hinzufügen, wenn das Archiv unter Windows erstellt wurde.Die Konvention
b'#!pythonw'ist zulässig.b'#!pythonw'kennzeichnet ein GUI-Skript anstelle eines Konsolenskripts. - Generieren von Skript-Wrappern.
- In Wheel haben Skripte, die unter Unix-Systemen gepackt wurden, sicherlich keine begleitenden .exe-Wrapper. Windows-Installer möchten diese möglicherweise während der Installation hinzufügen.
Empfohlene Archiver-Funktionen
- Platzieren von
.dist-infoam Ende des Archivs. - Archivierer werden ermutigt, die
.dist-info-Dateien physisch am Ende des Archivs zu platzieren. Dies ermöglicht einige potenziell interessante ZIP-Tricks, einschließlich der Möglichkeit, Metadaten zu ändern, ohne das gesamte Archiv neu zu schreiben.
Dateiformat
Dateinamenskonvention
Der Wheel-Dateiname lautet {distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl.
- distribution
- Distributionsname, z. B. ‚django‘, ‚pyramid‘.
- version
- Distributionsversion, z. B. 1.0.
- build tag
- Optionaler Build-Nummer. Muss mit einer Ziffer beginnen. Dient als Unterscheidungsmerkmal, wenn zwei Wheel-Dateinamen in allen anderen Aspekten (d. h. Name, Version und andere Tags) gleich sind. Wird als leeres Tupel sortiert, wenn nicht angegeben, andernfalls als zweielementiges Tupel sortiert, wobei das erste Element die anfänglichen Ziffern als
intund das zweite Element der Rest des Tags alsstrist. - Sprachimplementierungs- und Versionstag
- z. B. ‚py27‘, ‚py2‘, ‚py3‘.
- abi tag
- z. B. ‚cp33m‘, ‚abi3‘, ‚none‘.
- platform tag
- z. B. ‚linux_x86_64‘, ‚any‘.
Zum Beispiel ist distribution-1.0-1-py27-none-any.whl der erste Build eines Pakets namens ‚distribution‘ und ist kompatibel mit Python 2.7 (jede Python 2.7-Implementierung), ohne ABI (reines Python), auf jeder CPU-Architektur.
Die letzten drei Komponenten des Dateinamens vor der Erweiterung werden als „Kompatibilitätstags“ bezeichnet. Die Kompatibilitätstags geben die grundlegenden Interpreter-Anforderungen des Pakets an und sind in PEP 425 detailliert.
Escaping und Unicode
Jede Komponente des Dateinamens wird durch Ersetzen von aufeinanderfolgenden nicht-alphanumerischen Zeichen durch einen Unterstrich _ escaped
re.sub("[^\w\d.]+", "_", distribution, re.UNICODE)
Der Archivdateiname ist Unicode. Es wird einige Zeit dauern, bis die Werkzeuge aktualisiert sind, um Nicht-ASCII-Dateinamen zu unterstützen, aber sie werden in dieser Spezifikation unterstützt.
Die Dateinamen *innerhalb* des Archivs sind als UTF-8 kodiert. Obwohl einige gängige ZIP-Clients UTF-8-Dateinamen nicht korrekt anzeigen, wird die Kodierung sowohl von der ZIP-Spezifikation als auch von Pythons zipfile unterstützt.
Dateiinhalte
Der Inhalt einer Wheel-Datei, wobei {distribution} durch den Namen des Pakets ersetzt wird, z. B. beaglevote, und {version} durch seine Version, z. B. 1.0.0, besteht aus
/, der Wurzel des Archivs, enthält alle Dateien, die inpureliboderplatlibgemäßWHEELzu installieren sind.purelibundplatlibsind normalerweise beidesite-packages.{distribution}-{version}.dist-info/enthält Metadaten.{distribution}-{version}.data/enthält ein Unterverzeichnis für jeden nicht-leeren Installationsschema-Schlüssel, der noch nicht abgedeckt ist, wobei der Unterverzeichnisname ein Index in einem Dictionary von Installationspfaden ist (z. B.data,scripts,headers,purelib,platlib).- Python-Skripte müssen in
scriptserscheinen und exakt mitb'#!python'beginnen, um bei der Installation Skript-Wrapper-Generierung und#!python-Umschreibung zu genießen. Sie dürfen jede beliebige oder keine Erweiterung haben. {distribution}-{version}.dist-info/METADATAsind Metadaten im Format Version 1.1 oder höher.{distribution}-{version}.dist-info/WHEELsind Metadaten über das Archiv selbst im gleichen grundlegenden Schlüssel: Wert-FormatWheel-Version: 1.0 Generator: bdist_wheel 1.0 Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any Build: 1
Wheel-Versionist die Versionsnummer der Wheel-Spezifikation.Generatorist der Name und optional die Version der Software, die das Archiv erzeugt hat.Root-Is-Purelibist wahr, wenn das Top-Level-Verzeichnis des Archivs in purelib installiert werden soll; andernfalls soll das Root in platlib installiert werden.Tagsind die erweiterten Kompatibilitätstags des Wheels; im Beispiel würde der Dateinamepy2.py3-none-anyenthalten.Buildist die Build-Nummer und wird weggelassen, wenn keine Build-Nummer vorhanden ist.- Ein Wheel-Installer sollte warnen, wenn Wheel-Version höher ist als die unterstützte Version, und muss fehlschlagen, wenn Wheel-Version eine höhere Hauptversion als die unterstützte Version hat.
- Wheel, als Installationsformat, das über verschiedene Python-Versionen hinweg funktionieren soll, enthält im Allgemeinen keine .pyc-Dateien.
- Wheel enthält kein setup.py oder setup.cfg.
Diese Version der Wheel-Spezifikation basiert auf den distutils-Installationsschemata und definiert nicht, wie Dateien an andere Speicherorte zu installieren sind. Das Layout bietet eine Obermenge der Funktionalität, die von den bestehenden wininst- und egg-Binärformaten bereitgestellt wird.
Das Verzeichnis .dist-info
- Wheel .dist-info-Verzeichnisse enthalten mindestens METADATA, WHEEL und RECORD.
- METADATA sind die Paketmetadaten, im gleichen Format wie PKG-INFO, das sich im Stammverzeichnis von sdists befindet.
- WHEEL sind die Wheel-Metadaten, die spezifisch für einen Build des Pakets sind.
- RECORD ist eine Liste von (fast) allen Dateien im Wheel und deren sicheren Hashs. Im Gegensatz zu PEP 376 muss jede Datei außer RECORD, die keinen eigenen Hash enthalten kann, ihren Hash enthalten. Der Hash-Algorithmus muss SHA-256 oder besser sein; insbesondere sind md5 und sha1 nicht zulässig, da signierte Wheel-Dateien auf die starken Hashs in RECORD zur Validierung der Integrität des Archivs angewiesen sind.
- Die aus PEP 376 stammenden Felder INSTALLER und REQUESTED sind nicht im Archiv enthalten.
- RECORD.jws wird für digitale Signaturen verwendet. Es wird in RECORD nicht erwähnt.
- RECORD.p7s ist als Gefälligkeit für alle zulässig, die S/MIME-Signaturen zum Sichern ihrer Wheel-Dateien bevorzugen. Es wird in RECORD nicht erwähnt.
- Während der Extraktion verifizieren Wheel-Installer alle Hashs in RECORD gegen die Dateiinhalte. Abgesehen von RECORD und seinen Signaturen schlägt die Installation fehl, wenn eine Datei im Archiv nicht sowohl in RECORD aufgeführt als auch korrekt gehasht ist.
Das Verzeichnis .data
Jede Datei, die nicht normalerweise innerhalb von site-packages installiert wird, kommt in das .data-Verzeichnis, benannt wie das .dist-info-Verzeichnis, aber mit der Erweiterung .data/
distribution-1.0.dist-info/
distribution-1.0.data/
Das .data-Verzeichnis enthält Unterverzeichnisse mit den Skripten, Headern, Dokumentationen und so weiter der Distribution. Während der Installation werden die Inhalte dieser Unterverzeichnisse an ihre Zielpfade verschoben.
Signierte Wheel-Dateien
Wheel-Dateien enthalten ein erweitertes RECORD, das digitale Signaturen ermöglicht. Das RECORD aus PEP 376 wird modifiziert, um einen sicheren Hash digestname=urlsafe_b64encode_nopad(digest) (urlsafe Base64-Kodierung ohne abschließende = Zeichen) als zweite Spalte anstelle eines md5sum zu enthalten. Alle möglichen Einträge werden gehasht, einschließlich aller generierten Dateien wie .pyc-Dateien, aber nicht RECORD, das keinen eigenen Hash enthalten kann. Zum Beispiel
file.py,sha256=AVTFPZpEKzuHr7OvQZmhaU3LvwKz06AJw8mT\_pNh2yI,3144
distribution-1.0.dist-info/RECORD,,
Die Signaturdatei(en) RECORD.jws und RECORD.p7s werden in RECORD überhaupt nicht erwähnt, da sie erst hinzugefügt werden können, nachdem RECORD generiert wurde. Jede andere Datei im Archiv muss einen korrekten Hash in RECORD haben, sonst schlägt die Installation fehl.
Wenn JSON Web Signatures verwendet werden, werden eine oder mehrere JSON Web Signature JSON Serialization (JWS-JS) Signaturen in einer Datei RECORD.jws neben RECORD gespeichert. JWS wird verwendet, um RECORD zu signieren, indem der SHA-256-Hash von RECORD als JSON-Payload der Signatur aufgenommen wird.
{ "hash": "sha256=ADD-r2urObZHcxBW3Cr-vDCu5RJwT4CaRTHiFmbcIYY" }
(Der Hash-Wert ist im gleichen Format wie in RECORD.)
Wenn RECORD.p7s verwendet wird, muss es eine losgelöste S/MIME-Format-Signatur von RECORD enthalten.
Ein Wheel-Installer muss keine digitalen Signaturen verstehen, aber er *muss* die Hashs in RECORD gegen die extrahierten Dateiinhalte verifizieren. Wenn der Installer Datei-Hashs gegen RECORD prüft, muss ein separater Signaturprüfer nur feststellen, dass RECORD mit der Signatur übereinstimmt.
Siehe
Vergleich mit .egg
- Wheel ist ein Installationsformat; egg ist importierbar. Wheel-Archive müssen keine .pyc-Dateien enthalten und sind weniger an eine bestimmte Python-Version oder Implementierung gebunden. Wheel kann (reine Python-)Pakete installieren, die mit früheren Python-Versionen erstellt wurden, sodass Sie nicht immer darauf warten müssen, dass der Paketierer aufholt.
- Wheel verwendet .dist-info-Verzeichnisse; egg verwendet .egg-info. Wheel ist mit der neuen Welt der Python-Paketierung und den neuen Konzepten, die sie mit sich bringt, kompatibel.
- Wheel hat eine reichhaltigere Dateinamenskonvention für die heutige Multi-Implementierungs-Welt. Ein einzelnes Wheel-Archiv kann seine Kompatibilität mit einer Reihe von Python-Sprachversionen und -Implementierungen, ABIs und Systemarchitekturen angeben. Historisch war die ABI spezifisch für eine CPython-Version, Wheel ist bereit für die stabile ABI.
- Wheel ist verlustfrei. Die erste Wheel-Implementierung bdist_wheel generiert immer egg-info und konvertiert es dann in ein .whl. Es ist auch möglich, vorhandene Eggs und bdist_wininst-Distributionen zu konvertieren.
- Wheel ist versioniert. Jede Wheel-Datei enthält die Version der Wheel-Spezifikation und die Implementierung, die sie gepackt hat. Hoffentlich kann die nächste Migration einfach auf Wheel 2.0 erfolgen.
- Wheel ist eine Referenz auf das andere Python.
FAQ
Wheel definiert ein .data-Verzeichnis. Soll ich alle meine Daten dort ablegen?
Diese Spezifikation hat keine Meinung darüber, wie Sie Ihren Code organisieren sollen. Das .data-Verzeichnis ist lediglich ein Ort für alle Dateien, die nicht normalerweise innerhalb vonsite-packagesoder auf der PYTHONPATH installiert werden. Mit anderen Worten, Sie könnenpkgutil.get_data(package, resource)weiterhin verwenden, auch wenn *diese* Dateien normalerweise nicht in Wheels.data-Verzeichnis verteilt werden.
Warum enthält Wheel angehängte Signaturen?
Angehängte Signaturen sind bequemer als getrennte Signaturen, da sie mit dem Archiv reisen. Da nur die einzelnen Dateien signiert sind, kann das Archiv neu komprimiert werden, ohne die Signatur ungültig zu machen, oder einzelne Dateien können verifiziert werden, ohne das gesamte Archiv herunterladen zu müssen.
Warum erlaubt Wheel JWS-Signaturen?
Die JOSE-Spezifikationen, zu denen JWS gehört, sind so konzipiert, dass sie leicht zu implementieren sind, eine Funktion, die auch eines der Hauptentwicklungsziele von Wheel ist. JWS liefert eine nützliche, prägnante reine Python-Implementierung.
Warum erlaubt Wheel auch S/MIME-Signaturen?
S/MIME-Signaturen sind für Benutzer zulässig, die bestehende Public-Key-Infrastrukturen mit Wheel verwenden müssen oder wollen.Signierte Pakete sind nur ein grundlegender Baustein in einem sicheren Paketaktualisierungssystem. Wheel stellt nur den Baustein bereit.
Was hat es mit „purelib“ im Gegensatz zu „platlib“ auf sich?
Wheel behält die Unterscheidung „purelib“ vs. „platlib“ bei, die auf einigen Plattformen wichtig ist. Zum Beispiel installiert Fedora reine Python-Pakete nach ‚/usr/lib/pythonX.Y/site-packages‘ und plattformabhängige Pakete nach ‚/usr/lib64/pythonX.Y/site-packages‘.Ein Wheel mit „Root-Is-Purelib: false“ mit allen seinen Dateien in
{name}-{version}.data/purelibist äquivalent zu einem Wheel mit „Root-Is-Purelib: true“ mit denselben Dateien im Stammverzeichnis, und es ist zulässig, Dateien sowohl in der Kategorie „purelib“ als auch „platlib“ zu haben.In der Praxis sollte ein Wheel nur eines von „purelib“ oder „platlib“ haben, je nachdem, ob es sich um reines Python handelt oder nicht, und diese Dateien sollten sich im Stammverzeichnis mit der entsprechenden Einstellung für „Root-is-purelib“ befinden.
Ist es möglich, Python-Code direkt aus einer Wheel-Datei zu importieren?
Technisch gesehen, aufgrund der Kombination aus Unterstützung der Installation durch einfache Extraktion und der Verwendung eines Archivformats, das mitzipimportkompatibel ist, unterstützen eine Teilmenge von Wheel-Dateien *die* Platzierung direkt aufsys.path. Obwohl dieses Verhalten eine natürliche Folge des Formates-Designs ist, wird die tatsächliche Nutzung im Allgemeinen nicht empfohlen.Erstens ist Wheel *hauptsächlich* als Distributionsformat konzipiert, sodass das Überspringen des Installationsschritts auch bedeutet, bewusst auf die Nutzung von Funktionen zu verzichten, die eine vollständige Installation voraussetzen (wie z. B. die Verwendung von Standardwerkzeugen wie
pipundvirtualenv, um Abhängigkeiten zu erfassen und zu verwalten, die für Auditing- und Sicherheitsupdate-Zwecke ordnungsgemäß erfasst werden können, oder die vollständige Integration mit der Standard-Build-Maschinerie für C-Erweiterungen durch die Veröffentlichung von Header-Dateien an der entsprechenden Stelle).Zweitens, obwohl einige Python-Software so geschrieben ist, dass sie direkt aus einem Zip-Archiv ausgeführt werden kann, ist es immer noch üblich, dass Code so geschrieben wird, dass er davon ausgeht, dass er vollständig installiert wurde. Wenn diese Annahme durch den Versuch, die Software aus einem Zip-Archiv auszuführen, gebrochen wird, können die Fehler oft obskur und schwer zu diagnostizieren sein (insbesondere wenn sie in Drittanbieter-Bibliotheken auftreten). Die beiden häufigsten Fehlerquellen hierbei sind die Tatsache, dass das Importieren von C-Erweiterungen aus einem Zip-Archiv von CPython *nicht* unterstützt wird (da dies auf keiner Plattform direkt von der dynamischen Lade-Maschinerie unterstützt wird) und dass beim Ausführen aus einem Zip-Archiv das `__file__`-Attribut nicht mehr auf einen gewöhnlichen Dateisystempfad verweist, sondern auf eine Pfadkombination, die sowohl den Speicherort des Zip-Archivs im Dateisystem als auch den relativen Pfad zum Modul innerhalb des Archivs umfasst. Selbst wenn Software intern die abstrakten Ressourcen-APIs korrekt verwendet, kann die Interaktion mit externen Komponenten immer noch die Verfügbarkeit einer tatsächlichen Datei auf der Festplatte erfordern.
Wie Metaklassen, Monkey-Patching und Metapath-Importer gilt: Wenn Sie sich nicht bereits sicher sind, dass Sie diese Funktion nutzen müssen, dann brauchen Sie sie mit ziemlicher Sicherheit nicht. Wenn Sie sich *dennoch* entscheiden, sie zu verwenden, beachten Sie, dass viele Projekte einen Fehler, der mit einem vollständig installierten Paket reproduziert werden muss, bevor er als echter Bug akzeptiert wird.
Referenzen
Anhang
Beispiel für eine urlsafe-base64-nopad-Implementierung
# urlsafe-base64-nopad for Python 3
import base64
def urlsafe_b64encode_nopad(data):
return base64.urlsafe_b64encode(data).rstrip(b'=')
def urlsafe_b64decode_nopad(data):
pad = b'=' * (4 - (len(data) & 3))
return base64.urlsafe_b64decode(data + pad)
Urheberrecht
Dieses Dokument wurde in den öffentlichen Bereich gestellt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0427.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT