7 points par GN⁺ 2025-06-25 | 2 commentaires | Partager sur WhatsApp
  • En passant à uv, la vitesse d’installation des dépendances Python devient environ 10 fois plus rapide qu’avec pip, et l’exécution reste possible avec un utilisateur non root sans venv séparé
  • Avec pyproject.toml comme base, il suffit de déclarer les dépendances de premier niveau et uv gère automatiquement le fichier de lock, avec une gestion de l’arbre des dépendances et des versions exactes supérieure à pip freeze
  • Dans le Dockerfile, des changements étape par étape sont nécessaires, notamment la copie des binaires uv et uvx, l’utilisation des fichiers pyproject.toml/uv.lock et la configuration de variables d’environnement
  • Des commandes comme uv sync/add/remove et uv:outdated permettent de gérer facilement l’ajout, la suppression, la mise à jour des dépendances ainsi que la vérification des dernières versions des paquets
  • Une gestion régulière du fichier de lock et des mises à jour de dépendances devient possible, ce qui améliore la cohérence en environnement de collaboration et de déploiement

Installation des dépendances 10 fois plus rapide, sans venv, avec un environnement non root

  • uv est un outil qui améliore fortement la vitesse d’installation des dépendances dans les projets Python par rapport à pip
  • Avec l’adoption d’uv, il est possible de constater une vitesse d’installation environ 10 fois plus rapide que pip dans divers projets comme Flask ou Django
  • Même sans environnement virtuel séparé (venv), l’exécution dans le conteneur reste sûre avec un utilisateur non root

pyproject.toml vs requirements.txt

  • Au lieu du traditionnel requirements.txt, il suffit d’indiquer uniquement les dépendances de premier niveau dans le fichier pyproject.toml, puis uv génère automatiquement le fichier uv.lock
    • Ajouter la section [project] dependencies dans pyproject.toml
    • Supprimer l’ancien requirements.txt
  • Le fichier de lock d’uv ressemble au résultat de pip freeze, mais inclut un arbre de dépendances précis et des informations de version exactes

Modifications de la configuration du Dockerfile

  • Utiliser les binaires uv et uvx copiés dans le conteneur (binaires Rust compilés statiquement)
  • Copier les fichiers pyproject.toml, uv.lock\* à la place des anciens requirements*.txt
  • Ajouter des variables d’environnement :
    • UV_COMPILE_BYTECODE=1 : précompiler en bytecode pendant l’étape de build
    • UV_PROJECT_ENVIRONMENT="/home/python/.local" : installer les paquets dans un chemin spécifique sans créer de venv séparé
  • Remplacer aussi la commande d’installation des dépendances, de pip3-install vers uv-install
    • Exemple : RUN chmod 0755 bin/* && bin/uv-install

Gestion des dépendances : ajout, suppression, mise à jour, etc.

  • Il est possible d’exécuter les commandes uv dans le conteneur via un script run dédié
    • ./run deps:install : installe après build de l’image tout en exportant le fichier de lock vers l’hôte
    • ./run deps:install --no-build : met à jour uniquement le fichier de lock sans rebuild
    • ./run uv add mypackage --no-sync : met à jour uniquement pyproject.toml et le fichier de lock, l’installation réelle étant lancée séparément
    • ./run uv remove mypackage --no-sync : supprime un paquet
    • ./run uv:outdated : vérifie les dernières versions disponibles des dépendances actuelles

Vidéo et guide pratique fournis

  • Une démo réelle et des exemples de git diff sont fournis pour l’adoption d’uv, la rédaction de pyproject.toml, les modifications du Dockerfile, les commandes lock/sync, l’ajout/la suppression de dépendances et la vérification des versions les plus récentes
  • Il est aussi possible de consulter les diff de migration pour deux projets Flask et Django

2 commentaires

 
yangeok 2025-06-26

J’envisageais justement de migrer ce que je déployais avec Poetry, et ça a l’air stable et simple ^^

 
GN⁺ 2025-06-25
Commentaire Hacker News
  • Il faut noter que uv prend en charge un workflow qui remplace directement pyenv, virtualenv et pip. Ce n’est pas un outil qui impose forcément un lockfile ou pyproject.toml. Avec la commande uv python pin <version>, il crée un fichier .python-version dans le répertoire courant ; avec uv virtualenv, il télécharge la version de Python correspondante comme pyenv puis crée un environnement virtuel .venv ; avec uv pip install -r requirements.txt, il installe les paquets de requirements.txt ; avec uv run <command>, il peut exécuter une commande en incluant les variables d’environnement du fichier .env. Attention toutefois aux problèmes de priorité des variables d’environnement (issue liée)

    • La flexibilité de uv est vraiment impressionnante. Une tâche qui prend 10 minutes avec pip se fait en 20 à 30 secondes avec uv
    • C’est exactement pour ça que j’utilise uv. C’est extrêmement pratique. En revanche, il arrive que uv pip soit lent et je ne sais pas pourquoi ; c’est peut-être lié au réseau de l’entreprise
    • Il me semblait que les informations de version de Python étaient aussi stockées dans pyproject.toml, donc je me demande si le fichier .python-version est vraiment nécessaire
  • # Script qui garantit toujours un lock file à jour
    if ! test -f uv.lock || ! uv lock --check 2>/dev/null; then
      uv lock
    fi
    

    Cette approche vide en grande partie le lock file de sa raison d’être. Si le fichier est absent ou invalide, cela signifie qu’il y a un vrai problème avec le lock file, et il vaut mieux qu’une personne familière du projet concerné intervienne directement. Sinon, il n’y a pas vraiment de raison d’avoir un lock file. En CI, remplacer automatiquement le lock file peut créer de la confusion

    • (Réponse de l’auteur) Si le lock file est invalide, on ne passe pas silencieusement à côté pour en créer un nouveau. uv lock échoue avec un message explicite, et le errexit du script shell interrompt immédiatement l’exécution. La redirection d’erreur de uv lock --check sert à éviter que la même erreur soit affichée deux fois. Si on casse volontairement le lock file puis qu’on exécute le script, le build s’arrête avec un message d’erreur précis. Le script a été réécrit avec if-else pour être plus clair. Si le lock file n’existe pas, le bon flux est bien d’en générer un nouveau. Il suffit ensuite de le commit
    • Ce point est couvert par l’option uv sync --locked. Si le lock file est absent ou obsolète, l’échec est explicite. Je recommande de toujours faire les builds avec l’option --locked
    • Dans l’univers Python, on met souvent les lock files hors du contrôle de version et on les traite comme une « étape bizarre » du processus d’installation
    • Cette approche a un bug sérieux. Avec le flag --frozen, on s’attend justement à ce que le lock file ne soit pas mis à jour, mais en pratique c’est l’inverse qui se produit. Je suis d’accord sur le fait que si le lock file est absent ou ne correspond pas, il faut une intervention humaine
    • Malgré tout, si le lock file est absent, c’est soit un premier lancement, soit il sera de toute façon écrasé via le git upstream. S’il est cassé, c’est que quelqu’un a fait une erreur à l’installation, et en recréer un est en pratique la seule solution raisonnable. C’est un cas rare, mais comme traitement simple, c’est suffisant
  • Je suis complètement opposé au fait que des outils Python soient développés dans autre chose que Python. On a déjà le C, CPython est standardisé, donc il n’y a pas besoin d’un nouveau langage comme Rust. Le package Pendulum a mis plus de 7 mois à supporter 3.13, et je pense que c’est parce qu’il y avait trop peu de personnes capables de corriger le problème à cause du code natif Rust. Si ça avait été du C, je l’aurais corrigé moi-même. (issue liée) Dans l’idéal, si on veut un datetime rapide en Rust ou dans un autre langage externe, il vaudrait mieux le faire sous une forme exploitable via FFI par plusieurs langages. Les bases en Rust ne m’enthousiasment toujours pas, et je comprends mieux pourquoi la communauté Linux est réticente

    • Je respecte ce point de vue, mais je pense que construire un outil comme uv en Rust est une bonne idée. Si on écrit les outils de gestion Python en Python, on se retrouve avec un problème de l’œuf et de la poule. Pour utiliser un outil Python, il faut d’abord que Python lui-même soit installé ; il faut aussi gérer quelle version de Python est utilisée, les conflits possibles entre les bibliothèques de l’outil et celles de l’application réelle, la gestion des variables d’environnement, le débogage, etc. À l’inverse, avec un outil binaire compilé en Rust ou autre, on le télécharge et il fonctionne immédiatement sans avoir à se soucier de tout ça. Au fond, l’utilisateur n’a pas vraiment à se préoccuper du langage dans lequel l’outil est écrit
    • J’aime Python, mais la simplicité et la vitesse de uv sont incomparables. Quand il faut une version récente de Python sur un serveur en fin de vie, ou quand on veut juste installer rapidement les dépendances d’un petit script, uv est ce qu’il y a de mieux. Je suis d’accord sur certains points : avant j’écrivais uniquement en pur Python, puis j’ai commencé à utiliser des extensions C, et en touchant les limites je me suis dit qu’au fond j’aurais presque envie de tout écrire en C. Comme le C est difficile, je refactorise en Rust ces temps-ci. Quand la quantité de code externe dépasse celle du code interne, autant tout basculer dans un autre langage
    • Si tu tiens absolument à ce que les outils soient écrits uniquement en Python, pendant que tu attends Pylint et sa lenteur, moi je vais me promener
    • Le support de plusieurs langages n’est pas vraiment une charge pour l’utilisateur. Il suffit que l’outil soit rapide et résolve bien le problème. Et en pratique, c’est bien plus rapide. Un outil de gestion est un outil pour les développeurs, pas pour la cible d’usage finale
    • Peu m’importe le langage dans lequel c’est écrit, tant que ça fait bien le travail. Bien sûr, le fait que des utilisateurs Python puissent contribuer à l’outil est un avantage, mais si l’outil remplit bien son rôle, le langage n’a pas d’importance. Au contraire, quand on se heurte à des problèmes d’environnement, un outil écrit en Python peut lui aussi se retrouver affecté par ces mêmes problèmes
  • Il faut être prudent quand on utilise uv à la place de pip. Par défaut, il ne génère pas de fichiers pyc, ce qui peut ralentir le démarrage du service (référence)

  • Quand on essaie uv dans un conteneur Flask, la différence de temps de build devient presque ennuyeusement énorme, et le processus d’installation devient en plus très prévisible. On n’a plus la mauvaise surprise de versions de dépendances qui changent avec pip. On utilise pyproject.toml, on lance uv lock, et c’est terminé. Avec Docker, il suffit de copier uniquement pyproject.toml et uv.lock (HOT COPY), puis d’exécuter uv sync --frozen --no-install-project : cela permet de mettre en cache la couche d’installation sans inclure le code de l’application. Quiconque a déjà souffert d’une reconstruction complète de la couche pour un simple changement de paquet comprend pourquoi c’est important. Avec la variable d’environnement UV_PROJECT_ENVIRONMENT=/home/python/.local, on peut préchauffer l’image de base sans venv, ce qui permet de partager les builds et de réduire les coûts d’infrastructure. L’option UV_COMPILE_BYTECODE=1 génère les fichiers .pyc au moment du build. On supprime ainsi les environnements mutables et on impose la reproductibilité ; désormais, si le build échoue, la responsabilité du lockfile est claire

  • Même en 2025, le packaging Python et la gestion des dépendances restent toujours aussi confus

    • À mon avis, c’est justement parce que tout le monde n’utilise pas uv que ça reste confus
    • C’est une leçon sur l’importance de bien poser ces bases dès la conception d’un langage. Il ne faut pas remettre ça à une v2.0, ni ajouter des métadonnées dans les scripts exécutables sans y réfléchir plusieurs fois ; certaines approches conviennent à certains langages mais pas à Python
    • Je n’ai jamais eu le moindre problème de dépendances. requirements.txt et venv me suffisent largement
    • La gestion des dépendances est toujours aussi bancale, et maintenant on y ajoute même Rust
  • Je suis curieux d’avoir une comparaison de sécurité entre les gestionnaires de paquets Python comme uv, pip, conda, etc. La vitesse est appréciable, mais la sécurité d’un gestionnaire de paquets me paraît bien plus importante

    • uv est plus sûr que pip. Il analyse les dépendances sans exécuter de code arbitraire, vérifie par défaut les hash des paquets et évite plusieurs risques comme le typosquatting. Il est aussi excellent en vitesse et en reproductibilité (présentation technique, documentation de compatibilité)
    • Mais au fond, tous les gestionnaires de paquets téléchargent et exécutent du code tiers non vérifié. Plus que les différences de sécurité d’implémentation entre uv et pip, le vrai risque est souvent l’absence de politique vis-à-vis du code externe dès le départ
  • Comme je publie des paquets sur PyPI, j’aimerais personnellement utiliser uv pour sa rapidité, mais s’il n’y a pas de garantie qu’il se comporte exactement comme pip, je ne peux pas facilement basculer. Si un utilisateur rencontre une erreur avec « pip install xxx », je dois pouvoir la reproduire et la déboguer dans le même environnement

    • Ce n’est pas identique à pip à 100 %. Les principales différences sont traitées dans la documentation de compatibilité. Certaines tiennent à l’évolution vers le respect des standards, d’autres à des choix de conception propres à uv
  • J’ai l’impression que UV fait partie des évolutions les plus positives du packaging Python récent : on l’exécute et il donne de bons résultats

  • Il mentionne aussi un excellent guide sur l’utilisation de uv pour construire des conteneurs de production (voir le guide)