[!NOTE] Traduction automatique Cet article a été traduit automatiquement depuis la version originale en anglais.

uv sur macOS : Gestion des versions de Python, des projets et des outils

J’ai migré mon flux de travail en Python vers uv, car il remplace l’alternance de outils que je devais effectuer entre pip, des environnements virtuels, pip-tools, pipx et des gestionnaires de projet. Un seul exécutable suffit désormais pour gérer la majeure partie de ces tâches.

Les flux de travail restent distincts. Un projet, un script de métadonnées intégrées, une commande CLI ponctuelle et une commande CLI installée possèdent chacun leur propre environnement et leur propre cycle de vie. Ce guide présente les commandes que j’utilise ainsi que les limites qui définissent le champ d’action de chacune d’elles.

Début rapide

# 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 .

# Run a script that declares PEP 723 dependencies
uv run fetch.py

En résumé. J’utilise uv add, uv lock, uv sync, et uv run à l’intérieur des projets ; les métadonnées PEP 723 pour les scripts autocontenus ; uvx pour les outils ponctuels ; et uv tool install pour les commandes qui doivent rester en ligne PATH. Dans les pipelines CI, --locked vérifie que les métadonnées du projet correspondent aux uv.lock; --frozen Il fait confiance au verrou existant sans vérifier sa fraîcheur.

Installer uv avec un propriétaire unique

Homebrew constitue un chemin d’installation pratique sous macOS :

brew install uv
uv --version

Si Homebrew a installé uv, il devrait le mettre à jour :

brew upgrade uv

uv self update Il s’agit de la méthode d’installation autonome des UV et est désactivé pour les installations via un gestionnaire de paquets. Il ne faut pas permettre à deux installateurs de concurrencer pour le même exécutable.

Commandes d’identité utiles :

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

Les installations Python gérées, les outils persistants ainsi que les entrées de cache temporaires disposent de répertoires distincts. Le cache peut être supprimé puis reconstruit ; il ne constitue pas une source fiable d’information.

Les quatre limites du flux de travail UV

Projets : déclarations, résolution et environnement

Un projet UV comporte normalement trois artefacts distincts :

Les entrées et l’état dérivé dans un projet UV

Créez une application, ajoutez runtime ainsi que les exigences de test :

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

Les modèles actuels d’application UV génèrent main.py, pyproject.toml, README.md, et .python-version. Par défaut, ils ne définissent pas de système de compilation. Utilisez uv init --lib pour une bibliothèque emballée disposant d’un src Mise en page et compilation backend.

uv run vérifie le projet, met à jour le verrou lorsque c’est nécessaire, synchronise les dépendances requises, et exécute la commande. La première opération sur le projet crée .venv et uv.lock selon les besoins ; uv init Il ne crée que les fichiers du projet lui-même.

Soumettre les entrées, et non l’environnement

Valider pyproject.toml, uv.lock, source, ainsi qu’un intentionnel .python-version. Ignorer .venv ainsi que les caches des UV.

Le verrou enregistre une résolution valable pour les marqueurs et plateformes pris en charge ; il ne rend pas les modules natifs identiques sur macOS, Linux, Intel et Apple Silicon. Tester chaque plateforme de déploiement.

Verrouillage de la fraîcheur : --locked n’est pas --frozen

Par défaut, les commandes de projet peuvent être mises à jour uv.lock lorsque les déclarations changent.

Utiliser --locked pour exiger que le verrou soit à jour par rapport aux métadonnées du projet :

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

Si pyproject.toml et uv.lock Je ne suis pas d’accord : ces commandes échouent au lieu de résoudre un nouveau verrouillage. Or, c’est généralement cette étape du pipeline CI qui s’avère utile.

Utiliser --frozen uniquement lorsque l’on souhaite délibérément que le UV utilise le verrou existant sans vérifier s’il est actuel :

uv sync --frozen

Cela peut s’avérer utile lors d’une étape de construction contrôlée où le verrouillage a déjà été validé, mais il ne s’agit pas d’un détecteur de verrouillage obsolète. uv sync Il est par défaut précis et supprime les paquets superflus ; uv run utilise une synchronisation imparfaite par défaut, sauf si --exact Cela est demandé.

Python géré : une demande de version, et non un binaire universel

uv permet de télécharger et de gérer des distributions Python :

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

les builds gérées de CPython fournies par uv proviennent du python-build-standalone projet. uv peut également détecter les interpréteurs système, Homebrew, pyenv, Conda et d’autres.

.python-version Il s’agit d’une demande de version détectée par UV ainsi que par d’autres outils compatibles. requires-python dans pyproject.toml Il s’agit du contrat de compatibilité du projet. Il convient de préserver les deux aspects intentionnels :

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

Fixation 3.12 Cela ne garantit pas une version du patch identique indéfiniment, ni le même artefact sur tous les systèmes d’exploitation. Demandez un patch spécifique uniquement lorsque c’est réellement nécessaire, et faites en sorte que les pipelines CI sélectionnent et indiquent explicitement l’interpréteur utilisé.

Utiliser un autre gestionnaire Python lorsque le projet nécessite une configuration de distribution ou de compilation que uv ne fournit pas. uv peut néanmoins continuer à utiliser cet interpréteur via --python ou découverte normale.

Choisissez parmi uv run, uvx, ainsi que les outils installés

Sélectionner un environnement d’outil, éphémère ou persistant, pour le projet

Outils couplés au projet : uv run

Si pytest, mypy, un générateur de code ou un autre outil doit importer le projet ou utiliser ses plugins verrouillés, déclarez-le dans un groupe de dépendances :

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

Exécuter cet outil en passant par uvx cela le isolerait du projet et pourrait cacher le paquet ou les plugins installés dont il a besoin.

Outils ad hoc : uvx

uvx est un alias pour uv tool run. Il crée un environnement isolé stocké dans le cache UV éphémère :

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

Fixez la version de l’outil dans le CI ou dans la documentation. Une première exécution sans spécification de version sélectionne une version en cours d’utilisation, et les exécutions ultérieures peuvent réutiliser l’état du cache.

Exécutables persistants : uv tool install

Installez un outil lorsque des scripts hors de votre contrôle ont besoin de sa commande. PATH, ou lorsque le manifeste de machine en est délibérément propriétaire :

uv tool install 'ruff==0.12.0'
uv tool list
uv tool upgrade ruff
uv tool uninstall ruff

Les outils persistants utilisent toujours des environnements isolés. Ne modifiez pas manuellement ces environnements à l’aide de pip.

Scripts : faire d’un seul fichier l’unité de déploiement

PEP 723 définit les métadonnées de script en ligne de commande. Un exécuteur compatible peut lire le bloc de commentaires afin de créer un environnement isolé.

Anatomie d’un script PEP 723

# /// 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)

Exécuter et modifier les métadonnées à l’aide de uv :

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

Texte brut python fetch.py Il ignore les métadonnées de commentaire. Par conséquent, le script dépend d’un exécuteur compatible, même si la syntaxe Python reste valide.

Pour un script qui doit reproduire une résolution ultérieurement, créez un verrou adjacent :

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

Ce texte est écrit. fetch.py.lock. Un exclude-newer La date de timestamp peut restreindre les dates de distribution des candidats, mais son efficacité est moindre par rapport à une résolution verrouillée avec précision, et elle ne garantit pas que l’artefact reste accessible.

Préférez un projet lorsque plusieurs fichiers partagent des dépendances, que le code peut être importé en tant que paquet, que les tests nécessitent l’état du projet, ou encore lorsque plusieurs scripts doivent être exécutés ensemble.

Fichiers de spécifications : compatibilité, et non échec

A requirements.txt Un fichier peut contenir des entrées brutes, des broches précises, des hachages, des contraintes, des index, des URL, ou encore un environnement compilé. Sa reproductibilité dépend de la manière dont il a été généré et consommé ; le simple nom du fichier ne renseigne en rien à ce sujet.

Utilisez l’interface compatible avec pip d’uv sans migration :

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

uv pip sync fait en sorte que l’environnement corresponde au fichier, tandis que uv pip install -r Il est additif.

Pour une application propriétaire, une migration par étapes peut s’avérer utile :

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

Exécutez l’ensemble du parcours de test et de déploiement avant de supprimer les fichiers anciens. Si un système intermédiaire attend toujours le format Pip, dérivez-le à partir du fichier uv lock déjà validé :

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

Ne pas maintenir uv.lock ainsi qu’une exportation manuellement modifiée sous deux résolutions concurrentes.

Cache, indexations et limites de la chaîne d’approvisionnement

Le cache UV améliore les installations répétées, mais reste éphémère :

uv cache prune
uv cache clean

Préférer prune pour le nettoyage de routine. clean Supprime toutes les entrées de cache et force des téléchargements ainsi que des compilations ultérieurs.

Un fichier de verrouillage améliore la reproductibilité ; il ne rend pas les dépendances fiables pour autant. Vérifiez les sources des paquets, la configuration de l’index, les révisions Git, les backends de compilation, les licences ainsi que les identifiants. Gardez la configuration de l’index authentifiée hors des fichiers commités, sauf si le dépôt ne contient que des références non secrètes vers un mécanisme d’authentification approuvé.

Pour les paquets natifs, enregistrer l’architecture de déploiement ainsi que la disponibilité du wheel de test. Sinon, un résolveur pourrait être contraint d’utiliser une compilation locale qui nécessite des compilateurs et des bibliothèques système absents dans l’environnement CI ou en production.

Une liste de contrôle opérationnelle compacte

Pour chaque projet :

  1. Définir requires-python ainsi que les dépendances directes dans pyproject.toml.
  2. Séparer les extras publiés des groupes de dépendances locaux.
  3. Faire un commit uv.lock; ignorer .venv et l’état du cache.
  4. Exécuter les outils couplés au projet à l’intérieur de son environnement.
  5. Utiliser uv lock --check ou --locked dans le CI.
  6. Tester chaque système d’exploitation et architecture cibles indiqués par le lock.
  7. Exporter les formats de compatibilité uniquement pour les consommateurs désignés en aval.
  8. Évaluer la politique de sources et de identifiants de manière indépendante de la résolution.

UV est particulièrement utile lorsque ces limites de propriété restent visibles. Un seul binaire peut gérer l’ensemble sans les fusionner en un seul environnement. Cette combinaison — moins d’outils, sans prétendre que chaque flux de travail est identique — explique pourquoi je continue à l’utiliser comme outil par défaut pour mes projets Python sous macOS.

Références