uv unter macOS: Python-Versionen, Projekte und Tools verwalten

Automatische Übersetzung Dieser Artikel wurde automatisch aus der englischen Originalversion übersetzt.

Ich habe meinen Python-Workflow auf uv umgestellt. Damit wurden die Wechsel zwischen pip, virtuellen Umgebungen, pip-tools, pipx und Projektmanagern ersetzt, die ich zuvor manuell durchgeführt habe. Eine einzige ausführbare Datei deckt nun den größten Teil dieser Aufgaben ab.

Die Workflows unterscheiden sich weiterhin. Für Python-Entwickler, die unter macOS von pip, virtuellen Umgebungen, pip-tools oder pipx zu uv wechseln, gehören ein Projekt, ein Script mit Inline-Metadaten, ein einmalig ausgeführtes CLI und ein installiertes CLI jeweils zu einer anderen Umgebung und einem anderen Lifecycle. Die folgenden Befehle zeigen, wie man zwischen ihnen wählt.

Schnellstart

# Install uv with Homebrew
brew install uv

# Start a project
uv init example-app
cd example-app
uv add httpx
uv run python main.py

# Run an isolated CLI without installing it permanently
uvx ruff check .

TL;DR. In Projekten verwende ich uv add, uv lock, uv sync und uv run. Für eigenständige Scripts verwende ich PEP-723-Metadaten, für einmalige Tools uvx und für Befehle, die auf PATH verfügbar bleiben müssen, uv tool install. In CI prüft --locked, ob die Projektmetadaten mit uv.lock übereinstimmen. --frozen vertraut auf den vorhandenen Lock, ohne dessen Aktualität zu prüfen.

uv mit einer einzigen zuständigen Installationsmethode installieren

Homebrew ist eine bequeme Installationsmethode unter macOS. Die unterstützten Methoden findest du in uvʼs Installationsanleitung:

brew install uv
uv --version

Wenn Homebrew uv installiert hat, sollte Homebrew auch die Aktualisierung übernehmen:

brew upgrade uv

uv self update ist für uvʼs eigenständige Installationsmethode vorgesehen und bei Installationen über Paketmanager deaktiviert. Verhindere, dass zwei Installer um dieselbe ausführbare Datei konkurrieren.

Nützliche Befehle zur Identitätsprüfung:

command -v uv
uv python dir
uv tool dir
uv cache dir

Verwaltete Python-Installationen, persistente Tools und flüchtige Cache-Einträge liegen in getrennten Verzeichnissen. Der Cache kann gelöscht und neu aufgebaut werden. Er ist keine Source of Truth.

Die vier Grenzen der uv-WorkflowsDie vier Grenzen der uv-Workflows

Projekte: Deklarationen, Auflösung und Umgebung

Ein uv-Projekt besteht normalerweise aus drei Artefakten:

  • pyproject.toml deklariert Projektmetadaten und direkte Requirements.
  • uv.lock speichert die plattformübergreifende Auflösung von uv.
  • .venv ist die lokal installierte Umgebung und sollte wegwerfbar sein.

Inputs und abgeleiteter Zustand in einem uv-ProjektInputs und abgeleiteter Zustand in einem uv-Projekt

Erstelle eine Anwendung und füge Runtime- und Test-Requirements hinzu:

uv init forecast-app
cd forecast-app

uv add httpx
uv add --group test pytest
uv run python main.py
uv run --group test pytest

Aktuelle uv-Anwendungstemplates erstellen main.py, pyproject.toml, README.md und .python-version. Standardmäßig definieren sie kein Build-System. Verwende uv init --lib für eine paketierte Library mit einem src-Layout und Build-Backend.

uv run prüft das Projekt, aktualisiert bei Bedarf den Lock, synchronisiert die erforderlichen Dependencies und führt den Befehl aus. Eine Beschreibung dieses Lifecycles findest du in uvʼs Dokumentation zu Projekt-Layout und Sync. Die erste Projektoperation erstellt bei Bedarf .venv und uv.lock. uv init selbst erstellt nur die Projektdateien.

Inputs committen, nicht die Umgebung

Committe pyproject.toml, uv.lock, den Source-Code und einen bewusst gepflegten .python-version. Ignoriere .venv und die Caches von uv.

Der Lock hält eine Auflösung über unterstützte Marker und Plattformen hinweg fest. Er macht native Wheels für macOS, Linux, Intel und Apple Silicon jedoch nicht identisch. Testen Sie jede Deployment-Plattform.

Aktualität des Locks: --locked ist nicht --frozen

!!! Byte sagt

Ich habe `--frozen` in CI verwendet, um die Auflösung zu überspringen. Dabei wurde auch der Drift-Check übersprungen. Der Lock blieb einen Monat hinter `pyproject.toml` zurück, ohne dass irgendwo ein Fehler signalisiert wurde.

Standardmäßig können Projektbefehle uv.lock aktualisieren, wenn sich Deklarationen ändern. Weitere Informationen zu den Flags für Aktualität und Exaktheit finden Sie in der Dokumentation zum Locking und Synchronisieren von uv.

Verwenden Sie --locked, um zu verlangen, dass der Lock mit den Projektmetadaten aktuell ist:

uv lock --check
uv sync --locked --group test
uv run --locked --group test pytest

Wenn pyproject.toml und uv.lock nicht übereinstimmen, schlagen diese Befehle fehl, anstatt einen neuen Lock aufzulösen. Dieser Fehler ist normalerweise genau das CI-Gate, das Sie benötigen.

Verwenden Sie --frozen nur, wenn uv den bestehenden Lock absichtlich verwenden soll, ohne dessen Aktualität zu prüfen:

uv sync --frozen

Das kann in einer kontrollierten Build-Phase nützlich sein, in der der Lock bereits validiert wurde. Es ist jedoch kein Detektor für veraltete Locks.

Exaktheit ist eine separate Einstellung von Aktualität. uv sync ist standardmäßig exakt und entfernt überflüssige Packages. uv run führt standardmäßig eine nicht exakte Synchronisierung durch, sofern nicht --exact angefordert wird.

Verwaltetes Python: eine Versionsanforderung, kein universelles Binary

uv kann Python-Distributionen herunterladen und verwalten:

uv python install 3.12 3.13
uv python list --only-installed
uv python pin 3.12

Die verwalteten CPython-Builds von uv stammen aus dem Projekt python-build-standalone. uv kann außerdem System-, Homebrew-, pyenv-, Conda- und andere Interpreter erkennen. Weitere Informationen zu den unterstützten Quellen und zum Erkennungsverhalten finden Sie in der Dokumentation zu Python-Versionen von uv.

.python-version ist eine von uv und anderen kompatiblen Tools erkannte Versionsanforderung. requires-python in pyproject.toml ist der Kompatibilitätsvertrag des Projekts. Legen Sie beide bewusst fest:

[project]
requires-python = ">=3.12,<3.14"

Das Festlegen von 3.12 garantiert weder dauerhaft denselben Patch-Build noch dasselbe Artefakt auf jedem Betriebssystem. Fordern Sie eine exakte Patch-Version an, wenn Sie eine benötigen, und lassen Sie CI den Interpreter explizit auswählen und ausgeben.

Verwenden Sie einen anderen Python-Manager, wenn ein Projekt eine Distribution oder Build-Konfiguration benötigt, die uv nicht bereitstellt. uv kann diesen Interpreter weiterhin über --python oder die normale Erkennung verwenden.

Wählen zwischen uv run, uvx und installierten Tools

Auswahl einer projektgebundenen, kurzlebigen oder persistenten Tool-UmgebungAuswahl einer projektgebundenen, kurzlebigen oder persistenten Tool-Umgebung

Projektgebundene Tools: uv run

Wenn pytest, mypy, ein Codegenerator oder ein anderes Tool das Projekt importieren oder dessen gelockte Plugins verwenden muss, deklarieren Sie es in einer Dependency Group:

uv add --group lint ruff
uv run --group lint ruff check .

Wenn Sie dieses Tool über uvx ausführen, wird es vom Projekt isoliert und erkennt möglicherweise das benötigte installierte Package oder die benötigten Plugins nicht.

Ad-hoc Tools: uvx

uvx ist ein Alias für uv tool run. Damit wird eine isolierte Umgebung im temporären uv-Cache erstellt. Die uv-Tools-Dokumentation beschreibt diesen Cache und den Lebenszyklus persistenter Tools:

uvx ruff@0.12.0 check .
uvx --from jupyterlab jupyter lab
uvx --with mkdocs-material mkdocs --help

Fixiere die Tool-Version in CI oder in der Dokumentation. Beim ersten Aufruf ohne Versionsangabe wird eine aktuelle Version ausgewählt; spätere Aufrufe können den Cache-Zustand wiederverwenden.

Persistente Executables: uv tool install

Installiere ein Tool, wenn Skripte außerhalb deiner Kontrolle dessen Kommando auf PATH benötigen oder wenn das Setup-Manifest deines Rechners es verwalten soll:

uv tool install ruff
uv tool list
uv tool upgrade ruff
uv tool uninstall ruff

Auch persistente Tools verwenden isolierte Umgebungen. Verändere diese Umgebungen nicht manuell mit pip.

Skripte: Eine Datei als Release-Einheit

PEP 723 definiert Inline-Skript-Metadaten. Ein kompatibler Runner kann den Kommentarblock lesen und eine isolierte Umgebung erstellen. Weitere Informationen zum Metadatenformat und zum Verhalten des Runners findest du im uv-Skripte-Leitfaden und in PEP 723.

Aufbau eines PEP-723-SkriptsAufbau eines PEP-723-Skripts

# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "httpx>=0.27,<1",
# ]
# ///
import httpx

response = httpx.get("https://example.com", timeout=10)
response.raise_for_status()
print(response.status_code)

Führe das Skript aus und bearbeite die Metadaten mit uv:

uv run fetch.py
uv add --script fetch.py rich
uv remove --script fetch.py rich

Normales python fetch.py ignoriert die Kommentar-Metadaten. Das Skript benötigt daher einen kompatiblen Runner, obwohl die Python-Syntax weiterhin gültig ist.

Wenn ein Skript später reproduzierbar aufgelöst werden muss, erstelle eine benachbarte Lock-Datei:

uv lock --script fetch.py
uv run --script fetch.py

Dadurch wird fetch.py.lock geschrieben. Ein exclude-newer-Zeitstempel kann die Daten der infrage kommenden Distributionen einschränken, ist jedoch schwächer als eine exakt gelockte Auflösung und garantiert nicht, dass ein Artefakt weiterhin verfügbar ist.

Verwende stattdessen ein Projekt, wenn mehrere Dateien Abhängigkeiten gemeinsam nutzen, der Code als Package importierbar ist, Tests den Projektzustand benötigen oder mehrere Skripte gemeinsam ausgeliefert werden müssen.

Requirements-Dateien: Kompatibilität statt Sackgasse

Eine Datei vom Typ requirements.txt kann unspezifizierte Eingaben, exakte Pins, Hashes, Constraints, Indexe, URLs oder einen von einem Resolver erzeugten Requirements-Export enthalten. Ihre Reproduzierbarkeit hängt davon ab, wie sie erstellt und verwendet wurde. Der Dateiname allein sagt nichts aus.

Verwende die pip-kompatible Schnittstelle von uv, ohne zu migrieren:

uv venv
uv pip sync requirements.txt
uv run python app.py

uv pip sync sorgt dafür, dass die Umgebung mit der Datei übereinstimmt. uv pip install -r fügt lediglich weitere Pakete hinzu.

Für eine von dir verwaltete Anwendung kann eine schrittweise Migration sinnvoll sein:

uv init --bare
uv add -r requirements.in
uv add --group test -r requirements-test.in
uv lock
uv sync --locked

Führe den vollständigen Test- und Deployment-Pfad aus, bevor du alte Dateien löschst. Wenn ein nachgelagertes System weiterhin das pip-Format erwartet, leite es mit uvs Export-Kommando aus dem validierten uv-Lock ab:

uv export --format requirements.txt \
  --output-file requirements.txt

Verwalte uv.lock und einen manuell bearbeiteten Export nicht als zwei konkurrierende Auflösungen.

Cache, Indexe und Grenzen der Supply Chain

Der uv-Cache beschleunigt wiederholte Installationen, bleibt aber temporär:

uv cache prune
uv cache clean

Bevorzuge prune für die regelmäßige Bereinigung. clean entfernt alle Cache-Einträge und erzwingt bei späteren Aufrufen erneute Downloads und Builds.

Eine Lockfile verbessert die Wiederholbarkeit. Sie macht Dependencies jedoch nicht vertrauenswürdig. Prüfe Paketquellen, Index-Konfiguration, Git-Revisions, Build-Backends, Lizenzen und Credentials. Halte authentifizierte Index-Konfiguration aus committeten Dateien heraus. Eine Ausnahme ist ein nicht geheimes Verweisziel auf einen freigegebenen Credential-Mechanismus.

Bei nativen Packages solltest du die Deployment-Architektur dokumentieren und die Verfügbarkeit von Wheels testen. Andernfalls kann ein Resolver auf einen Source-Build zurückfallen, der Compiler und Systembibliotheken benötigt, die in CI oder der Produktion fehlen.

Eine kompakte Betriebs-Checkliste

Für jedes Projekt:

  1. Definiere requires-python und direkte Dependencies in pyproject.toml.
  2. Trenne veröffentlichte Extras von lokalen Dependency Groups.
  3. Committe uv.lock. Ignoriere .venv und den Cache-Zustand.
  4. Führe projektgebundene Tools über die Projektumgebung aus.
  5. Verwende uv lock --check oder --locked in CI.
  6. Teste jedes Zielbetriebssystem und jede Zielarchitektur, die durch die Lockfile abgedeckt sind.
  7. Exportiere Kompatibilitätsformate nur für ausdrücklich benannte nachgelagerte Consumer.
  8. Prüfe Source- und Credential-Richtlinien unabhängig von der Resolution.

uv ist besonders nützlich, wenn diese Zuständigkeitsgrenzen sichtbar bleiben. Ein einziges Binary kann sie alle verwalten, ohne sie in eine einzige Umgebung zu verwandeln. Weniger Tools – und die vier Workflows bleiben getrennt. Deshalb verwende ich uv unter macOS weiterhin als mein Standard-Tool für Python-Projekte.

Referenzen