7 Punkte von GN⁺ 2025-06-25 | 2 Kommentare | Auf WhatsApp teilen
  • Beim Umstieg auf uv ist die Installation von Python-Abhängigkeiten im Vergleich zu pip etwa 10-mal schneller, und die Ausführung ist auch ohne separates venv als Nicht-Root-Benutzer möglich
  • Auf Basis von pyproject.toml müssen nur die Top-Level-Abhängigkeiten angegeben werden; uv verwaltet die Lock-Datei automatisch und bietet einen präzisen Abhängigkeitsbaum sowie exakte Versionsverwaltung, die pip freeze überlegen ist
  • Im Dockerfile sind schrittweise Änderungen nötig, etwa das Kopieren der Binärdateien uv und uvx, die Nutzung von pyproject.toml-/uv.lock-Dateien sowie das Setzen von Umgebungsvariablen
  • Mit Befehlen wie uv sync/add/remove, uv:outdated lassen sich Abhängigkeiten einfach hinzufügen, entfernen, aktualisieren und auf neue Paketversionen prüfen
  • Durch regelmäßige Pflege der Lock-Datei und Updates von Abhängigkeiten werden Konsistenzvorteile für Zusammenarbeit und Deployment-Umgebungen erreicht

10-mal schnellere Abhängigkeitsinstallation, kein venv, Nicht-Root-Umgebung

  • uv ist ein Tool, das die Installationsgeschwindigkeit von Abhängigkeiten in Python-Projekten gegenüber pip deutlich verbessert
  • Mit der Einführung von uv lässt sich in verschiedenen Projekten wie Flask oder Django gegenüber pip eine etwa 10-mal schnellere Installationsgeschwindigkeit erreichen
  • Auch ohne separate virtuelle Umgebung (venv) kann der Container sicher als Nicht-Root-Benutzer ausgeführt werden

pyproject.toml vs requirements.txt

  • Statt der bisherigen requirements.txt-Datei werden in pyproject.toml nur die Top-Level-Abhängigkeiten angegeben; uv erzeugt dann automatisch die Datei uv.lock
    • In pyproject.toml den Eintrag [project] dependencies ergänzen
    • Die bisherige requirements.txt löschen
  • Die Lock-Datei von uv ähnelt dem Ergebnis von pip freeze, enthält jedoch einen präzisen Abhängigkeitsbaum und genaue Versionsinformationen

Änderungen am Dockerfile

  • Die Binärdateien uv und uvx werden zur Nutzung in den Container kopiert (statisch kompilierte Rust-Binärdateien)
  • Statt der bisherigen requirements*.txt werden pyproject.tomlunduv.lock*` kopiert
  • Zusätzliche Umgebungsvariablen:
    • UV_COMPILE_BYTECODE=1: Kompiliert im Build-Schritt vorab zu Bytecode
    • UV_PROJECT_ENVIRONMENT="/home/python/.local": Installiert Pakete in einem bestimmten Pfad, ohne ein separates venv zu erzeugen
  • Auch der Befehl zur Installation von Abhängigkeiten wird von pip3-install auf uv-install umgestellt
    • Beispiel: RUN chmod 0755 bin/* && bin/uv-install

Abhängigkeiten hinzufügen, entfernen, aktualisieren und verwalten

  • Über ein separates Run-Skript können uv-Befehle im Container ausgeführt werden
    • ./run deps:install: Installiert nach dem Image-Build und exportiert dabei die Lock-Datei auf den Host
    • ./run deps:install --no-build: Aktualisiert nur die Lock-Datei ohne neuen Build
    • ./run uv add mypackage --no-sync: Aktualisiert nur pyproject.toml und die Lock-Datei, die eigentliche Installation erfolgt separat
    • ./run uv remove mypackage --no-sync: Entfernt ein Paket
    • ./run uv:outdated: Prüft die aktuellsten Versionen der derzeitigen Abhängigkeiten

Video- und Praxisleitfaden verfügbar

  • Es werden reale Demos und git-diff-Beispiele zu uv-Einführung, Erstellung von pyproject.toml, Änderungen am Dockerfile, Lock-/Sync-Befehlen, Hinzufügen/Entfernen von Abhängigkeiten und Versionsprüfung bereitgestellt
  • Zusätzlich sind die Migrations-Diffs für Flask- und Django-Projekte als Referenz verfügbar

2 Kommentare

 
yangeok 2025-06-26

Ich wollte ohnehin von einer mit Poetry bereitgestellten Umgebung migrieren, und das wirkt stabil und einfach^^

 
GN⁺ 2025-06-25
Hacker-News-Kommentare
  • Es ist wichtig zu beachten, dass uv einen Workflow unterstützt, der pyenv, virtualenv und pip direkt ersetzt. Es ist nicht auf eine durch Lockfile oder pyproject.toml erzwungene Arbeitsweise beschränkt. Mit dem Befehl uv python pin <version> wird im aktuellen Verzeichnis eine .python-version-Datei erstellt, mit uv virtualenv wird wie bei pyenv die entsprechende Python-Version heruntergeladen und eine .venv-Umgebung erzeugt, mit uv pip install -r requirements.txt werden die Pakete aus requirements.txt installiert, und mit uv run <command> kann ein Befehl einschließlich der Umgebungsvariablen aus der .env-Datei ausgeführt werden. Man sollte jedoch auf Probleme bei der Priorität von Umgebungsvariablen achten (zugehöriges Issue)

    • Die Flexibilität von uv ist wirklich beeindruckend. Etwas, das mit pip 10 Minuten dauert, erledigt uv in meiner Erfahrung in 20 bis 30 Sekunden
    • Genau das war für mich der Anlass, uv zu verwenden. Extrem praktisch. Allerdings gibt es Fälle, in denen uv pip langsam ist, und ich weiß nicht warum; vielleicht liegt es an der Netzwerkumgebung in unserem Unternehmen
    • Soweit ich weiß, wird die Python-Version auch in pyproject.toml gespeichert, daher frage ich mich, ob eine .python-version-Datei wirklich nötig ist
  • # Skript, das immer ein aktuelles Lockfile sicherstellt
    if ! test -f uv.lock || ! uv lock --check 2>/dev/null; then
      uv lock
    fi
    

    So ein Ansatz entwertet den Sinn eines Lockfiles. Wenn die Datei fehlt oder ungültig ist, liegt ein ernsthaftes Problem mit dem Lockfile vor, und idealerweise sollte sich jemand darum kümmern, der mit dem betreffenden Projekt vertraut ist. Andernfalls gibt es keinen Grund, überhaupt ein Lockfile zu haben. In CI kann es zu Verwirrung führen, wenn das Lockfile automatisch ersetzt wird

    • (Antwort des Autors) Wenn das Lockfile ungültig ist, wird das nicht stillschweigend übergangen, um eine neue Datei zu erstellen. uv lock schlägt mit einer hilfreichen Meldung fehl, und durch errexit im Shell-Skript wird sofort abgebrochen. Die Fehlerumleitung bei uv lock --check dient nur dazu, zu verhindern, dass derselbe Fehler zweimal ausgegeben wird. Wenn man das Lockfile absichtlich kaputt macht und dann das Skript ausführt, stoppt der Build mit einer konkreten Fehlermeldung. Das Skript wurde zur besseren Verständlichkeit auf if-else umgestellt. Wenn kein Lockfile vorhanden ist, ist es der richtige Ablauf, ein neues zu erzeugen. Dann erstellt man es und committet es
    • Das wird durch die Option uv sync --locked abgedeckt. Wenn das Lockfile fehlt oder veraltet ist, schlägt es eindeutig fehl. Ich würde vorschlagen, Builds immer mit der Option --locked auszuführen
    • In der Python-Welt werden Lockfiles oft nicht in die Versionsverwaltung aufgenommen, sondern eher als ein „seltsamer Schritt“ im Installationsprozess behandelt
    • Dieser Ansatz hat einen schwerwiegenden Bug. Mit dem Flag --frozen sollte das Lockfile eigentlich nicht aktualisiert werden, in der Praxis verhält es sich aber umgekehrt. Ich stimme zu, dass bei fehlendem oder nicht passendem Lockfile ein Mensch eingreifen sollte
    • Trotzdem gilt: Wenn das Lockfile fehlt, ist es entweder der erste Lauf oder es wird sowieso per Git-Upstream überschrieben. Wenn es kaputt ist, hat jemand bei der Installation einen Fehler gemacht, und es neu zu erzeugen ist praktisch die einzig sinnvolle Lösung. Das ist ein seltener Sonderfall, aber für eine einfache Behandlung reicht es aus
  • Ich bin grundsätzlich dagegen, dass Python-Tools in einer anderen Sprache als Python entwickelt werden. Es gibt bereits C, und CPython ist standardisiert, daher braucht es nicht noch eine neue Sprache wie Rust. Das Paket Pendulum hat die Unterstützung für 3.13 mehr als sieben Monate verspätet ausgeliefert, und ich vermute, dass das daran lag, dass wegen des nativen Rust-Anteils zu wenige Leute wussten, wie man das Problem behebt. Wäre es C gewesen, hätte ich es selbst repariert. (zugehöriges Issue) Im Idealfall sollte man, wenn man ein schnelles datetime in einer externen Sprache wie Rust bauen will, es per FFI in einer Form bereitstellen, die von mehreren Sprachen genutzt werden kann. Auf Rust basierende Lösungen sagen mir noch nicht wirklich zu, und ich verstehe inzwischen auch, warum die Linux-Community dem skeptisch gegenübersteht

    • Ich respektiere diese Sichtweise, aber ich halte es für eine gute Idee, Tools wie uv in Rust zu schreiben. Wenn man ein Python-Verwaltungstool in Python baut, entsteht eine Art Henne-Ei-Problem. Um ein Python-Tool zu nutzen, muss Python selbst zuerst installiert sein, und dann wird alles kompliziert: welche Python-Version verwendet wird, mögliche Konflikte zwischen den Bibliotheken des Tools und der eigentlichen App, Verwaltung von Umgebungsvariablen, Debugging. Ein als Rust oder in einer ähnlichen Sprache gebautes Binärtool lädt man dagegen einfach herunter und nutzt es sofort, ohne sich um all das kümmern zu müssen. Den meisten Nutzern ist es ziemlich egal, in welcher Sprache das Tool geschrieben wurde
    • Ich mag Python, aber die Einfachheit und Geschwindigkeit von uv sind nicht vergleichbar. Wenn man auf einem EOL-Server eine aktuelle Python-Version braucht oder für ein kleines Skript nur schnell Abhängigkeiten installieren will, ist uv einfach das Beste. Einem Teil stimme ich zu: Früher habe ich alles nur in purem Python geschrieben, dann nach und nach C-Erweiterungen genutzt, und wenn man an Grenzen stößt, möchte man am Ende fast alles in C schreiben. Weil C schwierig ist, refaktoriere ich in letzter Zeit nach Rust. Wenn der externe Code mehr wird als der interne, ist es besser, gleich alles in eine andere Sprache zu verlagern
    • Wenn du unbedingt willst, dass Tools nur in Python geschrieben werden, dann gehe ich eben spazieren, während du auf das langsame Pylint wartest
    • Unterstützung für verschiedene Sprachen ist für Nutzer kaum eine Belastung. Das Tool muss nur schnell sein und das Problem gut lösen. Tatsächlich ist es deutlich schneller. Verwaltungstools sind Werkzeuge für Entwickler
    • Mir ist egal, in welcher Sprache es geschrieben ist, solange die Funktion stimmt. Natürlich können Python-Nutzer eher zu einem Python-Tool beitragen, aber wenn das Tool seinen Zweck gut erfüllt, ist die Sprache unwichtig. Im Gegenteil: Wenn man in Umweltprobleme läuft, kann ein in Python geschriebenes Tool davon selbst mit betroffen sein
  • Wenn man statt pip uv verwendet, sollte man vorsichtig sein. Standardmäßig werden keine .pyc-Dateien erzeugt, weshalb der Start eines Dienstes langsamer sein kann (Hinweis)

    • Wenn man uv in Containern nutzt, ist die Guide-Dokumentation hilfreicher (Docker-Anleitung)
  • Wenn man uv in einem Flask-Container verwendet, ist der Unterschied bei der Build-Zeit nicht nur groß genug, um langweilig zu wirken, sondern der Installationsprozess wird auch sehr vorhersehbar. Mit pip gibt es nicht mehr diese frustrierenden Versionsänderungen bei Abhängigkeiten. Man nutzt pyproject.toml, führt uv lock aus, und das war’s. In Docker kopiert man nur pyproject.toml und uv.lock (HOT COPY) und führt uv sync --frozen --no-install-project aus; dadurch wird der App-Code übersprungen und die Installationsschicht kann gecacht werden. Wer den Schmerz kennt, wenn schon bei der Änderung nur eines Pakets die gesamte Schicht neu gebaut werden muss, versteht, warum das wichtig ist. Wenn man mit der Umgebungsvariablen UV_PROJECT_ENVIRONMENT=/home/python/.local das Basis-Image ohne venv vorwärmt, lassen sich Builds gemeinsam nutzen und Infrastrukturkosten sparen. Mit der Option UV_COMPILE_BYTECODE=1 werden beim Build .pyc-Dateien erzeugt. Veränderliche Umgebungen verschwinden und Reproduzierbarkeit wird erzwungen; wenn der Build jetzt fehlschlägt, ist dank des Lockfiles die Ursache eindeutig

  • Auch im Jahr 2025 bleiben Python-Paketierung und Abhängigkeitsverwaltung weiterhin chaotisch

    • Ich denke, es bleibt chaotisch, weil nicht alle uv verwenden
    • Das ist eine Lehre daraus, wie wichtig es ist, solche Dinge schon beim Entwurf einer Sprache sauber aufzusetzen. Nicht auf v2.0 verschieben, gründlich nachdenken, bevor man Metadaten in ausführbare Skripte steckt, und bedenken, dass etwas, das für manche Sprachen passt, für Python ungeeignet sein kann
    • Ich hatte noch nie Probleme mit Abhängigkeiten. requirements.txt und venv reichen völlig aus
    • Die Abhängigkeitsverwaltung ist immer noch ein Chaos, und jetzt kommt auch noch Rust dazu
  • Mich würde ein Vergleich der Sicherheit von Python-Paketmanagern wie uv, pip und conda interessieren. Geschwindigkeit ist gut, aber die Sicherheit eines Paketmanagers ist viel wichtiger

    • uv ist tendenziell sicherer als pip. Es analysiert Abhängigkeiten ohne beliebige Codeausführung, prüft standardmäßig Paket-Hashes und vermeidet verschiedene Risiken wie Typosquatting. Auch bei Geschwindigkeit und Reproduzierbarkeit ist es stark (technische Einführung, Kompatibilitätsdokumentation)
    • Aber letztlich laden und führen alle Paketmanager ungeprüften Drittcode aus. Wichtiger als Implementierungsunterschiede bei der Sicherheit zwischen uv und pip ist das Risiko, überhaupt keine Richtlinie für externen Code zu haben
  • Da ich selbst Pakete auf PyPI veröffentliche, würde ich uv wegen seiner Geschwindigkeit zwar gern verwenden, aber wenn es keine Garantie gibt, dass es sich exakt wie pip verhält, kann ich nicht so leicht umsteigen. Wenn Nutzer bei pip install xxx einen Fehler erleben, muss ich ihn in derselben Umgebung reproduzieren und debuggen können

    • Es verhält sich nicht zu 100 % identisch zu pip. Die wichtigsten Unterschiede werden in der Kompatibilitätsdokumentation behandelt. Ein Teil davon sind Unterschiede im Übergang zu standardkonformem Verhalten, ein anderer Teil sind Designentscheidungen von uv selbst
  • Ich denke, uv ist eine der positivsten Veränderungen im neueren Python-Packaging: einfach ausführen und man bekommt gute Ergebnisse

  • Es wird auch eine hervorragende Anleitung zur Verwendung von uv für Produktionscontainer empfohlen (Guide ansehen)