Parenthèse Personnelle - Pourquoi j’ai adopté uv
Je suis entré dans le monde de la Data sept ans plus tôt, bombardé dans un environnement de développement Spark/Scala. J’en suis venu à apprécier l’univers JVM et à considérer ses outils de build comme acquis, d’abord Sbt, puis surtout Maven. Qu’on ne me comprenne pas de travers, je pense toujours que Maven est une créature de Frankenstein beaucoup trop verbeuse avec un système de Plugin/convention-over-configuration qui le rend difficile d’accès. Cependant il s’agit d’un outil puissant, et surtout admis par la communauté. C’est d'ailleurs la seule raison qui m’a fait le croiser dans un cadre de développement Scala, plutôt que d’utiliser l’outil de construction de projet qui s’appelle littéralement Scala Build Tool…
Puis j’ai dû évoluer, et ma stack technique aussi. Adieu Scala, Maven ou Sbt, bonjour PySpark, dbt, Snowflake et compagnie. Et bonjour Python, pip install dbt-core dbt-snowflake snowflake-cli
.
Entre pip, conda, setuptools, venv, pyenv, un requirements.txt fait une fois sur deux à la main, une fois sur deux résultant d’un pip freeze
, impossible de reproduire l’environnement de travail du voisin.
Divers outils se sont construits par dessus ce plat de nouilles pour adresser diverses parties de ce scope. On peut citer par exemple Poetry, Hatchling, on a aussi déjà parlé de Conda, etc. Bien que certains aient atteint une certaine popularité et une efficacité incontestable sur leur domaine, aucun réel consensus n’a abouti.

uv est l’un de ces outils. Malgré tout, ce n’est pas un outil parmi les autres, il s'agit de celui qui m’a réconcilié avec la gestion des packages Python et m’a permis de retrouver l’approche descriptive que pouvait offrir Maven sur la JVM.
Qu’est-ce qu’uv ?
uv, c’est le gestionnaire de package d’Astral, l’entreprise également derrière Ruff, le dernier Linter/Formatter Python à la mode. C’est important pour plusieurs raisons :
- Astral surfe sur la vague “Python in Rust”. Ses utilitaires sont créés pour Python, en Rust, ce qui les rend incomparables en termes de performance1.
- Il s’agit d’une entreprise. Contrairement à la majorité de l’outillage Python existant, il existe toute une équipe dédiée à la maintenance et à l’évolution d’uv et Ruff. L’équipe compte aujourd’hui une vingtaine de personnes, allant croissant.
- Cet avantage en termes de pérennité se traduit cependant par une dépendance à Astral. Un changement de licence est vite arrivé (pour ceux qui comme moi viennent de Scala, on se remémore le drame des migrations Scala 3 avec Akka2).
Positionnement
En tant qu’”outil par dessus le plat de nouilles” à part entière, uv adresse certaines problématiques et délègue les autres. Je trouve très pertinente la catégorisation d’Anna-Lena Popkes à ce sujet. En voici le résumé, je vous laisse consulter l’article entier ici.

On comprend immédiatement qu’uv n’est pas au centre des fonctionnalités. Si l’on décompose son périmètre :
- environment management : comme venv, uv permet de créer et maintenir des environnements virtuels. Il permet l’encapsulation de ses dépendances.
- package management : comme pip, uv permet de télécharger des dépendances Python depuis le dépôt officiel Pypi.
- Python version management : comme pyenv, uv permet de gérer plusieurs versions de Python.
Ce qu’uv ne fait pas :
- package building : uv ne permet pas de créer une distribution sans outil complémentaire.
- package publishing : il ne permet pas non plus d’aller à lui tout seul publier dans Pypi.
En fait, uv est un outil de build front-end. C’est lui qui va se charger de gérer les dépendances et l’environnement, et déléguer à un outil de build back-end le soin de construire notre distribution (wheel3). Dans la suite de cet article, on utilisera Hatchling4 pour réaliser ces fonctions.

Ainsi, on a bien uv qui se charge de l’environnement virtuel, de la gestion de Python et des dépendances, et Hatchling qui se charge des opérations complémentaires, nous fournissant un outillage complet, sur les cinq catégories décrites initialement.
Un mot sur pyproject.toml
Les PEP517 et PEP621 formalisent respectivement la description du back end et du front end de build au sein du fichier pyproject.toml. Pour les développeurs JVM, on peut voir ce fichier comme un équivalent du pom.xml de Maven. Concrètement, voici un exemple de fichier pyproject.toml :
[project]
name = "my-module"
version = "0.1.0"
description = "a sample module definition"
requires-python = ">=3.12"
dependencies = ["pytest>=8.3.5"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Il se compose de deux tables différentes : project et build-system. La partie project contient les métadonnées du projet et la partie build-system contient les informations relatives au back end de build. Un outil front-end comme uv va lire ce fichier, télécharger le back end correspondant et lui déléguer l’exécution du build.
Les comparaisons évidentes
J’utilise Conda depuis 10 ans, pourquoi je changerai pour un outil qui fait pareil ?
Conda est un peu le mouton noir de la grande famille du tooling Python. Il fait plein de choses, mais dans son coin. Premièrement, il ne se base pas sur Pypi, le dépôt officiel, mais sur son propre dépôt Anaconda. Il est dépendant d’une installation pip supplémentaire pour installer depuis Pypi.
Deuxièmement, l’outil n’est absolument pas aligné sur les standards de packaging, PEP517 et PEP621. Cela signifie qu’il ne respecte pas le contrat défini par le fichier de configuration pyproject.toml.
Ton schéma dit que Poetry fait plus de choses, pourquoi je changerai pour un outil moins puissant ?
C’est à la fois vrai et faux. En fait, le Poetry décrit dans ce schéma regroupe deux outils, Poetry et Poetry-core, respectivement le front end et le back end.
Effectivement, la combinaison des deux fait plus de choses, mais il serait plus juste de comparer uv avec la partie front-end de Poetry. Rien n’empêche en effet d’utiliser uv avec Poetry-core, si on le configure au sein du fichier pyproject.toml.
Tour d’horizon des fonctionnalités
Après cette dose conséquente de théorie, il est plus que temps de voir ENFIN comment uv fonctionne. Commençons par l’installation. Il s’agit du seul outil qu’il sera nécessaire d’installer en dehors d’un environnement virtuel. Pour la méthode, au choix : cUrl, pipx, homebrew, cargo, WinGet, etc. Toutes les méthodes sont décrites sur la documentation officielle bien mieux que je ne pourrais le faire.
Commandes principales
uv init
# Options principales
uv init [<MODULE>] [--app | --lib | --bare] [--build-backend <BACKEND>] [--python <PYTHON_VERSION>]
La commande init
crée un module managé par uv. Par exemple, uv init mon-module
va initialiser un répertoire mon-module tel que :

uv init
On a vu tout à l’heure qu’uv a besoin d’un back end de build pour être exhaustif. --build-backend initialise la table [build-system] du pyproject.toml. La documentation liste les back ends compatibles actuellement :
--build-backend <BUILD_BACKEND> Initialize a build-backend of choice for the project [possible values: hatch, flit, pdm, setuptools, maturin, scikit]
--app
, --lib
ou --bare
font partie des commandes modifiant les fichiers générés. Leur fonctionnement est un peu compliqué et dépend des autres options utilisées (en fait de la présence ou non de l’option build-backend).

--app
/ --lib
/ --bare
et --build-backend
--python
, enfin, est une option un peu plus fine qu’elle ne le laisse penser. Plutôt qu’une interprétation impérative qui aurait le sens de “j’associe cette version de Python à ce module”, il faut la comprendre de manière plus déclarative comme “voici la version de Python minimum avec laquelle mon module peut fonctionner”. Il existe plusieurs moyens de déterminer ensuite une version d’exécution de Python suivant le contexte, nous en verrons quelques une par la suite.
uv add / uv remove
# principales options
uv add <PACKAGES> [--group <GROUP>]
uv remove <PACKAGES> [--group <GROUP>]
uv offre une interface ligne de commande pour l’ajout et la suppression de package qui évite d’interagir directement avec le fichier pyproject.toml. Par exemple, dans le module mon-module créé précédemment, uv add pyspark
va éditer automatiquement le fichier :
Avant | Après |
---|---|
[project] name = "mon-module" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.12" |
[project] name = "mon-module" version = "0.1.0" description = "Add your description here" readme = "README.md" requires-python = ">=3.12" dependencies = [ "pyspark>=3.5.5", ] |