
Nous allons aborder ensemble trois use cases que vous allez fréquemment rencontrer dans votre pratique d’UV :
Migrer un repo legacy, utilisant pip, vers UV : Souvent, des repos maintenus ou lancés par des profils plus junior, ou pas sensibilisé à l’importance du dependency hell
Onboarder un nouveau collègue à votre code : Autrement dit, quelles manips UV doit réaliser votre collègue pour s’approprier le code de votre repo Git ?
Upgrader vos packages aux versions récentes : Vous voulez bénéficier des nouvelles fonctionnalités intéressantes de vos packages sans tomber dans le dependency hell ? Comment faire avec UV ?
Commençons par le premier cas d’usage. Comment on migre d’un projet sur pip vers un projet sur poetry ? Eh bien, le projet utilisant pip devrait normalement figurer un fichier requirements.txt . C’est un équivalent moins robuste du uv.lock , étant donné que pip ne va pas résoudre les problèmes de conflits de sous-dépendances lors d’installations de packages.
Si vous venez justement d’un univers utilisant pip, vous utiliserez la commande :
‘’’(bash)
pip install -r requirements.txt
‘’’Pour installer vos packages. Bonne nouvelle pour vous, UV est compatible avec la syntaxe de pip. Il suffit donc d’utiliser la commande suivante pour migrer vers pip :
‘’’(bash)
uv pip install -r requirements.txt
‘’’Toutefois, l’objectif est bien d’utiliser la syntaxe propre à UV à terme, auquel cas vous pouvez faire :
‘’’(bash)
uv add -r requirements.txt
‘’’Dans les deux cas, les commandes vont rafraîchir votre fichier pyproject.toml et créer un fichier uv.lock , en scannant tout ce qu’il y avait à l’intérieur du requirements.txt . ;) Si vous voulez vous amuser à installer des packages dans un projet avec pip, puis avec uv pour comparer les environnements, vous pouvez générer le fichier requirements.txt avec la commande pip freeze et le comparer avec le fichier uv.lock que vous savez déjà comment générer. ;)
Passons au deuxième cas d’usage maintenant, votre collègue découvre votre repo et il remarque la présence de deux nouveaux fichiers pour lui : un pyproject.toml et un uv.lock ! S’il n’a pas le temps de bien comprendre ce que fait UV, que doit-il faire en quick & dirty pour reproduire l’environnement virtuel ?
C’est très simple, il doit d’abord installer uv si ce n’est pas déjà fait. Ensuite, il doit utiliser la commande :
‘’’(bash)
uv venv
‘’’Cela va venir créer l’environnement virtuel, et éventuellement installer une version de python différente de celle installée dans son PC (en fonction du contenu du pyproject.toml ). En revanche, cette commande toute seule n’installe pas les packages du uv.lock ! Tout ce qu’elle fait, c’est créer un dossier .venv , avec une version de Python à l’intérieur, mais sans aucun package.
C’est uniquement avec la commande :
‘’’(bash)
uv lock
‘’’que l’on va lire le fichieruv.lock et installer toutes les dépendances et reproduire l’environnement tel qu’il était dans Git.
Pourquoi ne pas pousser dans Git directement le dossier .venv alors ?
Techniquement vous pouvez, mais ce n’est pas une bonne pratique de pousser et versionner des fichiers très lourds (comme des fichiers de données ou des modèles de ML) dans Git. Croyez-moi, votre dossier .venv va peser rapidement très lourd ! Entre le fait qu’elle possède sa propre installation de Python, et les packages qui s'ajoutent au fur et à mesure, on est au minimum dans les centaines de Mo, si ce n’est quelques Go.
Enfin, le troisième cas d’usage ! Les packages phares de projets Data Science vont sortir de temps en temps des refontes choc avec des fonctionnalités très intéressantes. C’était notamment le cas quand on est passé de Pandas 1 à Pandas 2, ou plus récemment de MLflow 2 à MLflow 3. Comment mettre à jour un package sans s’arracher les cheveux avec des conflits de dépendances ?
C’est très simple :
‘’’(bash)
uv lock —upgrade
‘’’Et voilà ! Si la commande uv lock “simple” se contente bêtement d'installer ce qui se trouve dans le fichier uv.lock, l'argument supplémentaire--upgrade va ignorer le fichier uv.lock actuel, aller lire le pyproject.toml de nouveau, et installer les versions les plus récentes du package tout en respectant les contraintes du pyproject.toml !
Dans le cycle de vie d’un projet de data, il y a certains packages que vous allez utiliser intensément pendant une certaine phase du projet, mais moins, voire pas du tout pendant d’autres phases.
Prenons un exemple très simple : Les notebooks Python, fidèles alliés des Datas Scientists. Que ce soit sous la forme d’un Jupyter Notebook ou d’une extension VSCode, coder en mode notebook avec des cellules est très pratique. En réalité, c’est le package python ipykernel qui rend cette fonctionnalité possible.
Cependant, une fois en production, vous n’utilisez plus un notebook ! Ainsi, si vous utilisez un environnement virtuel en production qui installe ipykernel, vous allez trimbaler un environnement plus lourd que nécessaire.
C’est pour cela qu’il est très utile de séparer nos packages en groupes. Un groupe de dépendances principales, indispensables pour le fonctionnement du code à n’importe quel moment. Ensuite, des groupes de dépendances utiles pendant une phase spécifique du projet.
Si je reprends mon exemple du package ipykernel. On pourrait imaginer qu’il fasse partie d’un groupe de dépendances qui s’appellerait quelque chose comme “data_science_explo”. Ce groupe centralise toutes les dépendances qui ne servent que pendant une phase d’analyse exploratoire d’un projet Data Science. On peut imaginer par exemple d’autres packages comme :
openpyxl : Ne sert que pour exporter des dataframes en Excel. A moins que votre projet en prod prévoit des exports, ce n’est pas très utile en dehors d’une explo de données.
tabulate : Permet d’afficher des data frames dans un terminal de manière lisible. Utile quand on veut débugger du code en local.
Mais où est-ce que je vais aller définir ou consulter mes groupes de dépendances ?
Alors pour les définir, c’est toujours avec la même commandeuv add ! Mais avec un petit twist :
‘’’(bash)
uv add --group data_science_explo ipykernel
‘’’ Vous voyez l’idée : On installe le package ipykernel dans un groupe data_science_explo. Si celui-ci n’existe pas, la commande va le créer.
A l’inverse, si nous souhaitons installer uniquement les dépendances principales. Il faudra utiliser la commande :
‘’’(bash)
uv sync --no-dev
‘’’Maintenant pour consulter ces groupes, c’est toujours la même chose, on va aller regarder notre fichier pyproject.toml ! En effet, ce fichier centralise toute la logique, les instructions et les contraintes qu’UV doit respecter dans n’importe quelle commande. S’il y a un endroit qu’il faut consulter pour comprendre l’usage UV dans un projet, ce sera systématiquement ce fichier.
Voici à quoi ressemble le fichier avec plusieurs groupes de dépendances, dont un groupe data_science_explo comme évoqué :
’’(toml)
[build-system]
requires = ["setuptools>=45", "wheel", "build"]
build-backend = "setuptools.build_meta"
[project]
name = "your-package"
version = "0.1.0"
description = "Your package description"
authors = [{name = "Your Name", email = "your.email@gmail.com"}]
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"pandas",
"scikit-learn",
"pandera",
"sqlalchemy[asyncio]",
"psycopg2-binary",
"tqdm",
"python-dotenv"
]
[dependency-groups]
data_science_explo = [
"ipykernel",
"tabulate",
"openpyxl",
"jedi"
]
checks = [
"ruff",
"pre-commit",
]
tests = [
"pytest",
"pytest-cov",
]
dashboard_building = [
"streamlit"
]
‘’Comme vous le voyez, les dépendances principales sont toutes au sein de la liste nommée “dependences”, alors que les autres groupes viennent après, dans une section séparée nommée “dependecy-groups”.
En réalité, on peut créer autant de groupes que nous le souhaitons ! La bonne pratique est d’organiser les groupes en fonction des phases où ils interviennent dans le projet. On peut imaginer par exemple, en plus d’un groupe pour les dépendances de dev en local, un groupe uniquement pour une pipeline CI/CD, avec des packages comme pre-commit ou ruff.
Finissons ce cours en parlant d’une autre fonctionnalité très utile de notre fidèlepyproject.toml. Il s’agit de la possibilité de définir des tools. Plutôt que de donner une définition, vous allez plus facilement comprendre à travers un exemple.
Le premier exemple concerne des packages Python complexes, où il ne suffit pas de simplement déclarer la dépendance avec uv add et fermer les yeux. Un exemple très classique dans le monde de la data science est celui de PyTorch.
PyTorch est un package incontournable pour tous ceux qui veulent utiliser des LLMs en local, via Hugging Face par exemple, ou mener des projets de Computer Vision. Cependant, son installation peut souvent donner du fil à retordre, car elle est assez dépendante de la configuration de votre PC ! Effectivement, PyTorch réussit à exécuter des modèles de Deep Learning (LLM ou non) rapidement si votre PC est équipé de GPUs et en particulier de la technologie CUDA de chez Nvidia.
Pourquoi je parle de tout ça ? Parce que tous les PC ne sont pas équipés de GPUs, et même des PC avec GPUs peuvent avoir des configurations CUDA différentes !
Ces contraintes font que l’on peut très bien installer PyTorch avec un simple uv add torch , partager notre fichier pyprojet.toml et pour autant ne pas reproduire notre environnement à l’identique chez un collègue.
Il va falloir contraindre UV à installer PyTorch d’une certaine manière, ce qui est tout à fait possible à mettre en place via notre pyproject.toml ! C’est cette syntaxe qui le permets :
‘’’(toml)
[tool.uv.sources]
torch = { index = "pytorch-cpu" }
[[tool.uv.index]]
url = "https://download.pytorch.org/whl/cpu" explicit = true
‘’’Vous voyez que la configuration se fait en deux temps :
D’abord, une section sources, qui va venir lister tous les packages où nous devons ignorer le comportement par défaut d’installation (c’est-à-dire aller chercher le package dans PyPi en ligne) et aller chercher une autre source (ou un autre index, pour employer le langage d’UV)
En résulte ensuite une section index justement pour préciser exactement l’URL de provenance, avec la commande explicit = true, qui précise à UV de n’utiliser cet index que pour ce package, et pas un autre.
C’est un exemple de tool management ! Dans notre cas précis, nous pouvons préciser à UV de télécharger UNIQUEMENT la version CPU de PyTorch, pour rendre le projet reproductible dans une certaine mesure, dans n’importe quel PC.
Je dis bien, dans une certaine mesure. Car sans GPU, il est beaucoup moins pratique d’utiliser un LLM avec une vitesse raisonnable, quand nous utilisons des tâches complexes. Autrement, si les configurations des GPU sont les mêmes chez vos collègues (ce qui est facile à garantir quand nous travaillons avec des machines virtuelles dans le Cloud), alors on peut imposer un URL d’une version spécifique de PyTorch, parfaitement compatible avec les ressources de ces machines.
Pour d’autres exemples de tool management, je vous renvoie vers cette ressource qui présente comment utiliser du linting, formatage et des tests unitaires ;)
UV donne la possibilité de migrer un projet Python utilisant pip en utilisant une syntaxe simple et familière
L'environnement virtuel peut être personnalisé en définissant des groupes de dépendances au sein du fichier pyproject.toml
Le tool management permet d’être très fin dans la définition de votre environnement virtuel ou la configuration d’outils tiers
Vous êtes arrivé à la fin de ce cours ! Bravo ! Avant de conclure, je vous propose de vérifier vos acquis en participant au quiz final !