uv sur macOS : gérer les versions de Python, les projets et les outils
Traduction automatique Cet article a été traduit automatiquement depuis la version originale en anglais.
J’ai basculé mon workflow Python vers uv. Il a remplacé les changements d’outils que j’effectuais auparavant entre pip, les environnements virtuels, pip-tools, pipx et les gestionnaires de projets. Un seul exécutable couvre désormais l’essentiel de ces tâches.
Les workflows restent toutefois différents. Pour les développeurs Python qui passent à uv sur macOS depuis pip, les environnements virtuels, pip-tools ou pipx, un projet, un script avec métadonnées inline, un outil CLI ponctuel et un CLI installé relèvent chacun d’un environnement et d’un cycle de vie différents. Les commandes ci-dessous montrent comment choisir entre ces options.
Démarrage 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 .
En bref. J’utilise
uv add,uv lock,uv syncetuv rundans les projets. J’utilise les métadonnées PEP 723 pour les scripts autonomes,uvxpour les outils ponctuels etuv tool installpour les commandes qui doivent rester surPATH. En CI,--lockedvérifie que les métadonnées du projet correspondent àuv.lock.--frozenfait confiance au lock existant sans vérifier sa fraîcheur.
Installer uv avec un seul gestionnaire
Homebrew est une méthode d’installation pratique sur macOS. Consultez le guide d’installation de uv pour connaître les méthodes prises en charge :
brew install uv
uv --version
Si Homebrew a installé uv, c’est Homebrew qui doit le mettre à niveau :
brew upgrade uv
uv self update concerne la méthode d’installation autonome de uv et est désactivé pour les installations via un gestionnaire de paquets. Ne laissez pas deux installateurs se disputer le même exécutable.
Commandes utiles pour vérifier l’identité :
command -v uv
uv python dir
uv tool dir
uv cache dir
Les installations de Python gérées, les outils persistants et les entrées temporaires du cache utilisent des répertoires distincts. Le cache peut être supprimé puis recréé. Ce n’est pas une source de vérité.
Projets : déclarations, résolution et environnement
Un projet uv possède généralement trois artefacts :
pyproject.tomldéclare les métadonnées du projet et ses dépendances directes.uv.lockstocke la résolution multiplateforme de uv..venvest l’environnement installé localement et doit pouvoir être supprimé.
Créez une application et ajoutez ses dépendances d’exécution et 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 templates actuels d’applications uv créent main.py, pyproject.toml, README.md et .python-version. Ils ne définissent pas de système de build par défaut. Utilisez uv init --lib pour une bibliothèque packagée avec une structure src et un backend de build.
uv run vérifie le projet, met à jour le lock si nécessaire, synchronise les dépendances requises et exécute la commande. Consultez la documentation de uv sur la structure des projets et la synchronisation pour comprendre ce cycle de vie. La première opération sur le projet crée .venv et uv.lock si nécessaire. uv init crée uniquement les fichiers du projet.
Versionner les entrées, pas l’environnement
Versionnez pyproject.toml, uv.lock, le code source et un .python-version intentionnel. Ignorez .venv et les caches de uv.
Le lock enregistre une résolution couvrant les marqueurs et les plateformes prises en charge. Il ne rend pas les wheels natives identiques sur macOS, Linux, Intel et Apple Silicon. Testez chaque plateforme de déploiement.
Fraîcheur du lock : --locked n’est pas --frozen
Par défaut, les commandes de projet peuvent mettre à jour uv.lock lorsque les déclarations changent. Consultez la documentation de uv sur le verrouillage et la synchronisation pour connaître les options de fraîcheur et d’exactitude.
Utilisez --locked pour exiger que le lock soit à jour par rapport aux métadonnées du projet :
uv lock --check
uv sync --locked --group test
uv run --locked --group test pytest
Si pyproject.toml et uv.lock divergent, ces commandes échouent au lieu de générer un nouveau lock. Cet échec constitue généralement la barrière CI recherchée.
Utilisez --frozen uniquement lorsque vous voulez délibérément que uv utilise le lock existant sans vérifier qu’il est à jour :
uv sync --frozen
Cela peut être utile dans une étape de build contrôlée, lorsque le lock a déjà été validé, mais ce n’est pas un détecteur de lock obsolète.
L’exactitude est un réglage distinct de la fraîcheur. uv sync est exact par défaut et supprime les paquets superflus. uv run effectue par défaut une synchronisation non exacte, sauf si --exact est demandé.
Python géré : une demande de version, pas un binaire universel
uv peut télécharger et gérer des distributions Python :
uv python install 3.12 3.13
uv python list --only-installed
uv python pin 3.12
Les builds CPython gérés par uv proviennent du projet python-build-standalone. uv peut également détecter les interpréteurs système, Homebrew, pyenv, Conda et d’autres interpréteurs. Consultez la documentation de uv sur les versions de Python pour connaître les sources prises en charge et le comportement de détection.
.python-version est une demande de version détectée par uv et par d’autres outils compatibles. requires-python dans pyproject.toml constitue le contrat de compatibilité du projet. Rendez ces deux éléments explicites :
[project]
requires-python = ">=3.12,<3.14"
Épingler 3.12 ne garantit ni le même build de patch pour toujours, ni le même artefact sur tous les systèmes d’exploitation. Demandez un patch exact lorsque cela est nécessaire, et faites en sorte que la CI sélectionne et signale explicitement l’interpréteur.
Utilisez un autre gestionnaire Python lorsqu’un projet a besoin d’une distribution ou d’une configuration de build que uv ne fournit pas. uv peut tout de même utiliser cet interpréteur via --python ou grâce à la détection standard.
Choisir entre uv run, uvx et les outils installés
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 via uvx l’isolerait du projet et pourrait masquer le paquet installé ou les plugins dont il a besoin.
Outils ponctuels : uvx
uvx est un alias de uv tool run. Il crée un environnement isolé stocké dans le cache uv, qui peut être supprimé. La documentation de uv sur les outils décrit ce cache et le cycle de vie des outils persistants :
uvx ruff@0.12.0 check .
uvx --from jupyterlab jupyter lab
uvx --with mkdocs-material mkdocs --help
Épinglez la version de l’outil en CI ou dans la documentation. Une première invocation sans version sélectionne une release actuelle ; les invocations suivantes peuvent réutiliser l’état du cache.
Exécutables persistants : uv tool install
Installez un outil lorsque des scripts que vous ne contrôlez pas doivent disposer de sa commande sur PATH, ou lorsque le manifeste de configuration de votre machine doit en assurer la gestion :
uv tool install ruff
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 avec pip.
Scripts : faire d’un fichier l’unité de release
PEP 723 définit les métadonnées inline des scripts. Un runner compatible peut lire le bloc de commentaires et créer un environnement isolé. Consultez le guide des scripts uv et PEP 723 pour connaître le format des métadonnées et le comportement du runner.
# /// 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écutez le script et modifiez ses métadonnées avec uv :
uv run fetch.py
uv add --script fetch.py rich
uv remove --script fetch.py rich
Un simple python fetch.py ignore les métadonnées présentes dans les commentaires. Le script dépend donc d’un runner compatible, même si sa syntaxe Python reste valide.
Pour un script qui doit reproduire ultérieurement une résolution, créez un lock adjacent :
uv lock --script fetch.py
uv run --script fetch.py
Cela écrit fetch.py.lock. Un timestamp exclude-newer peut limiter les dates des distributions candidates, mais il est moins fiable qu’une résolution exacte verrouillée et ne garantit pas qu’un artefact restera disponible.
Utilisez plutôt un projet lorsque plusieurs fichiers partagent des dépendances, que le code est importable comme un paquet, que les tests ont besoin de l’état du projet ou que plusieurs scripts doivent évoluer ensemble.
Fichiers de requirements : compatibilité, pas échec
Un fichier requirements.txt peut contenir des entrées souples, des pins exacts, des hashes, des contraintes, des index, des URLs ou un export de requirements généré par un resolver. Sa reproductibilité dépend de la manière dont il a été produit et consommé. Le nom du fichier ne permet à lui seul de rien conclure.
Utilisez l’interface compatible avec pip de uv sans migrer :
uv venv
uv pip sync requirements.txt
uv run python app.py
uv pip sync fait correspondre l’environnement au fichier. uv pip install -r est additif.
Pour une application dont vous assurez la gestion, une migration progressive peut être 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’intégralité du parcours de test et de déploiement avant de supprimer les anciens fichiers. Si un système en aval attend toujours le format pip, dérivez-le du lock uv validé avec la commande d’export de uv :
uv export --format requirements.txt \
--output-file requirements.txt
Ne gérez pas uv.lock et un export édité manuellement comme deux résolutions concurrentes.
Cache, index et limites de la supply chain
Le cache uv accélère les installations répétées, mais reste supprimable :
uv cache prune
uv cache clean
Préférez prune pour le nettoyage courant. clean supprime toutes les entrées du cache et force les téléchargements et builds ultérieurs.
Un lockfile améliore la reproductibilité. Il ne rend pas les dépendances fiables pour autant. Vérifiez les sources des paquets, la configuration des index, les révisions Git, les backends de build, les licences et les identifiants. Ne versionnez pas la configuration d’un index authentifié. L’exception est une référence non secrète vers un mécanisme d’authentification approuvé.
Pour les paquets natifs, consignez l’architecture de déploiement et testez la disponibilité des wheels. Dans le cas contraire, un resolver peut basculer vers un build depuis les sources nécessitant des compilateurs et des bibliothèques système absents de la CI ou de la production.
Checklist opérationnelle compacte
Pour chaque projet :
- Définissez
requires-pythonet les dépendances directes danspyproject.toml. - Séparez les extras publiés des groupes de dépendances locaux.
- Versionnez
uv.lock. Ignorez.venvet l’état du cache. - Exécutez les outils couplés au projet dans l’environnement du projet.
- Utilisez
uv lock --checkou--lockeden CI. - Testez chaque système d’exploitation et chaque architecture cible représentés par le lock.
- N’exportez les formats de compatibilité que pour des consommateurs en aval explicitement identifiés.
- Vérifiez séparément la politique relative aux sources et aux identifiants, indépendamment de la résolution.
uv est particulièrement utile lorsque ces frontières de responsabilité restent visibles. Un seul binaire peut toutes les gérer sans les transformer en un seul environnement. Moins d’outils, tout en conservant les quatre workflows séparés. C’est pourquoi uv reste mon outil Python par défaut pour les projets sur macOS.