- Selbst kleine API-Testskripte erfordern wegen ihrer Abhängigkeiten eine passende Laufzeitumgebung; mit einem uv-Ausführungs-Shebang lassen sie sich jedoch so gestalten, dass sie ohne Installationsschritte direkt ausführbar sind
- Das Beispiel
jam_users.py nutzt httpx, IPython und loguru, um den /users-Endpunkt einer Go-API zu testen: Benutzer löschen, neu anlegen und anschließend in eine REPL wechseln
- Beim bisherigen Ansatz müssen Pakete global in der System-Python-Installation installiert oder eine virtuelle Umgebung manuell vorbereitet werden, was das Teilen und erneute Ausführen von Skripten umständlich macht
- Wenn im Header
# /// script dependencies = ["ipython", "httpx", "loguru"] deklariert ist und uv run jam_users.py ausgeführt wird, übernimmt uv die isolierte Umgebung und die Installation der Abhängigkeiten
- Ergänzt man den Shebang
#!/usr/bin/env -S uv run --script sowie Ausführungsrechte, kann das Skript auf Unix-Systemen nur mit installiertem uv wie ./jam_users.py ausgeführt werden
Abhängigkeiten im Skript deklarieren
- Das Beispiel
jam_users.py bereitet Testbenutzerdaten für die lokale API http://localhost:4000/v1/users vor
- Sendet API-Anfragen mit httpx
- Wechselt in eine IPython-REPL, um Antworten zu prüfen und Tests fortzusetzen
- Protokolliert Lösch- und Erstellungsvorgänge mit
loguru
- Das grundlegende Skript folgt dem Ablauf, zunächst die Benutzerliste zurückzusetzen und anschließend Testdaten wieder einzufügen
- Ruft mit
GET /v1/users die bestehende Benutzerliste ab
- Ruft für jeden Benutzer
DELETE /v1/users/{id} auf
- Sendet die vorbereitete Benutzerliste als JSON an
POST /v1/users
- Startet danach mit
IPython.embed() eine REPL
- Für die Ausführung mit
python jam_users.py müssen httpx, IPython und loguru bereits in der Laufzeitumgebung installiert sein
- Man kann sie zwar global in der System-Python-Installation installieren oder separat eine virtuelle Umgebung erstellen, beides erfordert jedoch Vorbereitungen vor der Ausführung
- Eine globale Installation kann die System-Python-Umgebung mit Paketen überfrachten
- Beim Ansatz mit virtueller Umgebung müssen Erstellung, Aktivierung, Installation und Ausführung manuell verwaltet werden
- Beide Ansätze setzen eine System-Python-Installation voraus, die mit den benötigten Paketen kompatibel ist
Ein direkt ausführbares Skript mit uv erstellen
- Wenn das Tag
# /// script von uv am Anfang des Skripts eingefügt wird, können Abhängigkeiten innerhalb der Datei deklariert werden
# /// script
# dependencies = ["ipython", "httpx", "loguru"]
# ///
- Mit diesem Header lässt sich das Skript per
uv run jam_users.py ausführen
- uv erstellt eine isolierte virtuelle Umgebung für das Skript
- Lädt die benötigten Abhängigkeiten herunter und installiert sie
- Führt das Skript im Kontext dieser virtuellen Umgebung aus
- Ein normaler Python-Shebang wie
#!/usr/bin/env python kann den uv-Skriptheader nicht nutzen, da Python die Kommentare # /// script ignoriert
- Wird der uv-Aufruf direkt in den Shebang geschrieben, kann das Skript wie eine ausführbare Datei behandelt werden
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["ipython", "httpx", "loguru"]
# ///
- Das Flag
-S von env sorgt dafür, dass die nachfolgende Zeichenkette in separate Argumente aufgeteilt und an env übergeben wird
- Nachdem mit
chmod +x jam_users.py Ausführungsrechte vergeben wurden, kann das Skript wie folgt direkt gestartet werden
./jam_users.py
- Auf diese Weise lässt sich das Skript auf Unix-Systemen ausführen, sofern uv installiert ist, ohne separate Installation von Abhängigkeiten oder Verwaltung einer virtuellen Umgebung
- Beim Weitergeben komplexer Python-Skripte an andere Nutzer reduziert dies den Aufwand, lange Vorbereitungsanleitungen für das Zielsystem mitzuliefern
1 Kommentare
Meinungen auf Hacker News
Nicht speziell UV, sondern ganz allgemein stört mich der Ansatz, die Codeausführung über Kommentare zu steuern.
Kommentare für Linter-Anweisungen oder Entwicklernotizen sind in Ordnung, aber wenn es um Konfiguration oder ausführungsbezogene Daten geht, halte ich eine Form wie
UV_ENV = { "dependencies": { "requests": "2.32.3", "pandas": "2.2.3" } }für deutlich besser.Das ist gültige Python-Syntax, nutzt statt willkürlichem Kommentar-Parsing Standard-Datenstrukturen und lässt sich dadurch leicht erzeugen und validieren. Vor allem wahrt es das Prinzip, dass der Code auch dann identisch laufen sollte, wenn alle Kommentare entfernt werden.
Auch der vorgeschlagene Ansatz ist zur Laufzeit nur eine magische Konstante, die nichts tut und nur per statischer Analyse geparst wird; uv könnte sie lesen, aber ein anderes Tool könnte sie als unbenutzten Code ansehen und entfernen.
Stattdessen könnte man nach
import uvdirekt aufrufen, was uv tun soll, etwauv.exec(dependencies=["clown"], python=">=3.10").Der erste Lauf erfolgt in irgendeiner Python-Runtime, die dieses hypothetische
uv-Paket finden kann; dasuv-Paket richtet dann die virtuelle Umgebung und die Python-Runtime ein und führt anschließend mit einem Flag, etwa über eine Umgebungsvariable, einre-exec(3)aus.In der zweiten Runtime erkennt
uv.execdas Flag und macht einfach nichts.https://peps.python.org/pep-0723/
Außerdem verhält sich auch die Shebang-Zeile im Grunde wie ein Kommentar.
Sie ist seit 45 Jahren so tief verankert, dass den Leuten kaum noch bewusst ist, dass sie ein Shell-Kommentar ist.
Wenn dieselben Abhängigkeiten installiert sind, läuft der Code identisch.
Es ändert nicht die Bedeutung des Codes selbst, sondern die Umgebung, in der der Code ausgeführt wird; in dieser Hinsicht unterscheidet es sich nicht von einem
#!/bin/bash-Kommentar am Anfang eines Shell-Skripts.Allerdings will uv vermutlich keinen Code ausführen, nur um die Abhängigkeiten zu ermitteln; daher müsste es ein sehr eingeschränktes Subset der Python-Syntax sein.
Schon die Tatsache, dass so etwas überhaupt nötig ist, zeigt eine Schwäche der Sprache.
Die
import-Anweisung selbst sollte alle Informationen über Abhängigkeiten vermitteln können.In den letzten Monaten kam dieses Thema auf HN sehr häufig auf; aktuelle Beispiele sind:
https://news.ycombinator.com/item?id=43500124
https://news.ycombinator.com/item?id=42463975
Ich mag uv, aber mit der Bezeichnung self-contained habe ich aus zwei Gründen Schwierigkeiten.
Erstens muss uv bereits installiert sein, damit man das Skript ausführen kann.
Man könnte ein Shell-Skript bauen, das prüft, ob uv installiert ist, und es andernfalls per curlpipe installiert, aber dadurch wächst der Boilerplate erheblich, und der curlpipe-Ansatz selbst ist auch nicht gut.
Zweitens ist es nicht wirklich self-contained, irgendwo im Home-Verzeichnis automatisch eine virtuelle Umgebung anzulegen.
Selbst wenn man es nur einmal ausführt und das Skript danach löscht, bleibt diese virtuelle Umgebung zurück und belegt Platz; in der uv-Dokumentation habe ich keine Zusicherung gefunden, dass solche temporären virtuellen Umgebungen automatisch aufgeräumt werden.
curl | shverwenden muss, ist berechtigt.Allerdings wird das mit der Zeit weniger problematisch, sobald Paketmanager uv in ihre Repositories aufnehmen.
Zum Beispiel ist uv bereits in Alpine Linux und Homebrew verfügbar: https://repology.org/project/uv/versions
Außerdem sind Inline-Skript-Metadaten ein Python-Standard.
Wenn auf dem System kein uv vorhanden und es auch nicht paketiert ist, aber eine zum Skript passende Python-Version existiert, kann man es mit pipx ausführen: https://pipx.pypa.io/stable/examples/#pipx-run-examples
pipx ist wesentlich breiter paketiert: https://repology.org/project/pipx/versions
Auf beliebigen Entwicklerrechnern ist Docker möglicherweise häufiger vorhanden als uv, und da es im Originaltext um ein Firmenprojekt ging, erschien mir das plausibel.
Allerdings wird derzeit überhaupt nicht gecacht, sodass bei jeder Ausführung Downloads anfallen; das dürfte sich wohl mit Volumes beheben lassen.
So sieht es aus: https://hugojosefson.github.io/docker-shebang/#python
Wenn beim Ausführen aber irgendwelche unbekannten Dinge aus dem Internet heruntergeladen werden, ist es schwer, das self-contained zu nennen; wirklich vollständig self-contained ist eher etwas wie AppImage.
Mit etwas wie py2exe kann man ein self-contained „Python-Skript“ erstellen.
Für Entwickler verursacht das viele Probleme, für Nutzer werden die Probleme aber minimiert.
Mit Nix verwendet man denselben Ansatz; die Shebang-Zeile sieht dann so aus:
#! nix-shell -i python3 -p "python312.withPackages (pkgs: [ pkgs.boto3 pkgs.click ])"Damit braucht das System nur Nix; Python muss nicht einmal installiert sein.
Das naheliegendste Beispiel ist Bash mit allen angegebenen Abhängigkeiten; ich habe mit einem Nix-Shebang auch schon schnelle Single-File-Rust-Skripte gebaut.
https://nixos.wiki/wiki/Nix-shell_shebang
nix shellstattnix-shellmacht.Wie in anderen Kommentaren hängt die Behauptung „self-contained“ davon ab, dass
uvinstalliert ist.Wenn man wirklich ein self-contained Python-Skript will, lohnt sich ein Blick auf den Nuitka-Compiler.
Wir verwenden ihn problemlos in der Produktion für einen gRPC-Service, und
nuitka --onefile run.pyauszuführen reicht aus.Da es ein Compiler ist, ist das erzeugte Binary oft auch schneller als das ursprüngliche, mit PyInstaller gebündelte Python-Programm.
Auf der GitHub-Seite des Autors steht: „Meine Lebensaufgabe ist es, bis ins hohe Alter zu versuchen, den bestmöglichen Python Compiler zu bauen.“
https://nuitka.net/
https://github.com/kayhayen
Dieses Pattern gefällt mir wirklich, aber leider habe ich es nicht sauber mit LSP zum Laufen bekommen.
Ich nutze pyright in Helix, und selbst wenn ich den Editor mit
uv run hx script.pystarte, funktioniert es nicht.Man kann zwar so etwas wie
uv run --with whatever-it-is-i-need hx script.pymachen, aber das führt zunehmend zu Dopplungen.uve-Skript:$ cat ~/.local/bin/uve#!/bin/bashtemp=$(mktemp)uv export --script $1 --no-hashes > $tempuv run --with-requirements $temp vim $1unlink $tempIch hoffe, dass Editoren bald
uv python find --scriptunterstützen.Sieht ziemlich nützlich aus.
Ich frage mich, ob uv für die langfristige Bereitstellung Python-basierter Projekte die sicherere Wahl ist.
Ich denke dabei an den Rug Pull mit Anaconda vor etwa fünf Jahren: Wir nutzten es für Dependency-Management, später änderten sich die Regeln, und Kundenorganisationen mit mehr als 200 Mitarbeitenden durften Anaconda nicht mehr kostenlos verwenden, sondern mussten eine kommerzielle Lizenz bezahlen.
Sie können die Entwicklung einstellen oder künftige Arbeit in einen Fork mit anderer Lizenz verschieben, aber sie können die frühere Lizenz nicht rückwirkend ändern; was heute existiert, ist also als Open Source garantiert.
Wenn man wirklich besorgt ist, kann man einen Fork erstellen und weiter synchron halten.
Im Grunde gilt das für andere OSS-Projekte fast genauso, daher würde ich mir keine großen Sorgen machen.
Soweit ich weiß, war conda nie Open Source und hat Binaries verteilt.
https://github.com/astral-sh/uv?tab=readme-ov-file#license
Die Pakete von conda-forge kann man weiterhin kostenlos nutzen.
uv verwendet einfach PyPI; wenn es Probleme gibt, kann man von uv zu pip, Poetry oder Ähnlichem wechseln, und die Pakete kommen weiterhin aus derselben Quelle.
Ein solcher Eigentümer wird am Ende von der schlimmsten reichen Person übernommen, die man sich vorstellen kann.
Ich habe die Contribution-Guides und Issues von uv überflogen, aber kein CLA gesehen; bei PyTorch stand ein CLA ganz oben im Contribution-Guide.
Trotzdem hätte es einen Community-Fork der letzten FOSS-Version von Anaconda geben müssen.
Bei Redis ist das passiert, und Redis verwendet ebenfalls ein CLA: https://github.com/redis/redis/blob/unstable/CONTRIBUTING.md#redis-software-grant-and-contributor-license-agreement
Man sollte niemals ein CLA unterschreiben.
Ich finde, man sollte lieber nur zu Copyleft-Projekten beitragen.
Wir werden zu gut bezahlt, um kostenlos zu arbeiten.
Für kleine Utilities wirkt das wie eine gute Packaging-Alternative zur Containerisierung.
Jetzt muss ich nur noch alle Kolleginnen und Kollegen überzeugen, uv zu installieren.
Sieht ähnlich aus wie bundler/inline in Ruby.
Schön, dass es etwas Ähnliches nun auch für Python gibt; in der Praxis ist es wirklich sehr bequem.
https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html
Ich frage mich, ob das jemand unter Windows zum Laufen gebracht hat
Ich wollte diesen Trick für ein Tool für einen Spiel-Mod verwenden, an dem ich arbeite, konnte den Shebang-Trick aber nicht zum Laufen bringen
$> uv init --script .py$> uv add --script .py ...$> uv add --script .py --dev ...$> uv run .pyHoffe, das hilft
Quelle: https://docs.astral.sh/uv/guides/scripts/
.py-DateienDer py launcher unterstützt Shebang-Zeilen
Das wurde auch in einem Blogpost zum selben Thema behandelt, der vor ein paar Tagen erschien; demnach muss man
-Sweglassen: https://thisdavej.com/share-python-scripts-like-a-pro-uv-and-pep-723-for-easy-deployment/https://news.ycombinator.com/item?id=43500124
Ich habe es nicht selbst ausprobiert, sondern stattdessen die Dateizuordnung geändert, sodass alle
.py-Dateien standardmäßig mituv rungeöffnet werdenhttps://docs.python.org/3/using/windows.html#python-launcher-for-windows
https://peps.python.org/pep-0397/
https://pyinstaller.org
https://onor.io/2025/01/more-scripting-with-racket.html
Durch diesen Anwendungsfall habe ich uv schätzen gelernt, aber ich finde, dass es dem Zen of Python widerspricht, dass ein offizielles und sehr nützliches PEP nicht von den offiziellen Python-Tools unterstützt wird
Aus meiner Sicht war das der erste Moment, in dem Python nicht mehr „batteries included“ war
Jetzt habe ich auch zwei Python-Dependency-Manager auf dem System
Ich weiß, dass es zur Python-Abhängigkeitsverwaltung viel zu sagen gibt, aber in den letzten Jahren bin ich bei Projekten mit nur einer
requirements.txtmit dem Standard-pip+venvzurechtgekommenIch meine mich zu erinnern, dass
pyproject.tomlvor der BibliothektomllibkamDaher musste man einige Versionen lang Module in einer Sprache spezifizieren, die Python standardmäßig nicht lesen konnte
Das ist eine schlechtere Situation, als wenn es ein Standardverfahren gäbe, das ungenutzte Metadaten enthält
Genau deshalb sind es schließlich Metadaten; andernfalls wäre es einfach Python-Syntax gewesen