2 points par GN⁺ 5 시간 전 | 1 commentaires | Partager sur WhatsApp
  • uv se distingue par sa rapidité, la gestion des versions de Python et l’intégration dans un binaire unique, mais l’UX de gestion des paquets reste encore maladroite en phase de maintenance
  • Les paquets obsolètes peuvent être vérifiés avec uv pip list --outdated, mais comme la commande n’est pas de premier niveau et se trouve sous un espace de noms compatible pip, sa découvrabilité est faible
  • uv add pydantic ajoute par défaut une contrainte sans borne supérieure comme pydantic>=2.13.4, ce qui autorise même les montées de version majeure
  • La mise à niveau complète se fait avec uv lock --upgrade, et pour plusieurs paquets précis il faut répéter --upgrade-package, ce qui rend la commande verbeuse
  • Le réglage add-bounds = "major" permet de définir des contraintes par défaut plus sûres, mais il s’agit d’une fonctionnalité en préversion et les applications ont besoin d’une UX de mise à jour plus intuitive

Les points forts de uv et les limites en phase de maintenance

  • uv d’Astral se distingue par sa rapidité, la gestion des versions de Python et sa capacité à remplacer plusieurs outils par un binaire unique
  • Démarrer un nouveau projet Python et ajouter une première dépendance est simple, mais une fois le projet entré en phase de maintenance, l’UX pour repérer les paquets obsolètes et effectuer des mises à jour régulières paraît plus maladroite qu’avec pnpm ou Poetry
  • Les principaux irritants concernent la découvrabilité de la commande de vérification des paquets obsolètes, l’absence de borne supérieure dans les contraintes de version par défaut et la verbosité des commandes de mise à niveau

Vérifier les paquets obsolètes

  • Dans les projets JavaScript, pnpm outdated permet de voir de façon concise les paquets obsolètes, la version actuelle, la dernière version et la version autorisée par la contrainte
  • uv ne propose pas de commande de premier niveau uv outdated, et au départ la commande suivante servait d’alternative
$ uv tree --outdated --depth 1
  • uv tree --outdated --depth 1 ne filtre pas uniquement les éléments obsolètes : il affiche tout l’arbre des dépendances de premier niveau, puis ajoute une petite annotation à côté des éléments pouvant être mis à jour
  • Même avec 50 dépendances et seulement 2 paquets obsolètes, il faut parcourir une liste de 50 lignes
  • poetry show --outdated a lui aussi un nom de commande moins intuitif, mais dans les faits sa sortie n’affiche que les paquets obsolètes

Le risque des contraintes de version par défaut

  • L’approche par défaut de pnpm et Poetry

    • pnpm add enregistre dans package.json une exigence caret comme ^1.23.4
    • ^1.23.4 autorise les versions 1.x.x, mais pas la mise à jour vers 2.0.0
    • Poetry utilise lui aussi par défaut un format comme >=1.23.4,<2.0.0 ; c’est moins lisible, mais l’effet est le même
    • Avec ces deux outils, tant que les paquets respectent SemVer, exécuter pnpm update ou poetry update réduit le risque de casser le build à cause de changements majeurs d’API
  • L’approche par défaut de uv

    • uv add pydantic ajoute dans pyproject.toml une contrainte sans borne supérieure, comme ci-dessous
dependencies = [
    "pydantic>=2.13.4",
]
  • Avec cette contrainte, les versions 2, 3 ou 100 de pydantic sont toutes autorisées
  • Lors d’une mise à jour massive, on ne récupère donc pas seulement des correctifs, mais aussi tous les changements incompatibles publiés par les mainteneurs à travers le graphe de dépendances
  • En maintenance d’application, cela peut notamment créer un risque de stabilité

L’UX des commandes de mise à niveau

  • Avec pnpm et Poetry, une mise à jour complète reste simple
$ pnpm update
$ poetry update
  • Avec uv, la mise à niveau complète utilise la commande suivante
$ uv lock --upgrade
  • uv lock --upgrade n’est ni uv update ni uv upgrade, mais une option de la commande lock, ce qui le rend moins intuitif comme commande de gestion des paquets pour un humain
  • Combiné à des contraintes sans borne supérieure, uv lock --upgrade revient à pousser tous les paquets du lockfile vers la version la plus récente possible
  • Cela inclut aussi des dépendances profondément imbriquées que l’on ne connaît pas forcément directement
  • Pour mettre à jour seulement certains paquets, pnpm permet simplement d’énumérer leurs noms
$ pnpm update pydantic httpx uvicorn
  • Avec uv, il faut répéter le flag --upgrade-package pour chaque paquet
$ uv lock --upgrade-package pydantic --upgrade-package httpx --upgrade-package uvicorn
  • Lorsqu’on veut monter plusieurs paquets d’un coup, cette répétition du flag devient une vraie gêne

Le flag --bounds et la configuration

  • uv a récemment ajouté l’option --bounds pour uv add
$ uv add pydantic --bounds major
  • Cette commande génère une contrainte plus sûre : pydantic>=2.13.4,<3.0.0
  • --bounds major est pour l’instant une fonctionnalité en préversion, à activer explicitement dans chaque commande
  • Il s’est ensuite avéré qu’il est possible de définir une valeur par défaut une fois pour toutes dans pyproject.toml
[tool.uv]
add-bounds = "major"
  • Avec ce réglage, il n’est plus nécessaire de taper --bounds major à chaque fois pour obtenir des valeurs par défaut plus raisonnables avec uv add
  • Pour les applications, il vaudrait mieux que ce comportement soit celui par défaut, mais dans la pratique l’usage n’est pas aussi pénible qu’il avait été décrit au départ

La différence entre applications et bibliothèques

  • Le conseil standard du packaging Python est qu’une bibliothèque publiée sur PyPI ne doit pas figer de borne supérieure, et ce conseil est pertinent
  • Si toutes les bibliothèques fixent une borne supérieure, l’arbre de dépendances des consommateurs en aval peut devenir impossible à résoudre
  • À l’inverse, une application est un nœud terminal du graphe de dépendances, et aucun autre utilisateur ne résout ses dépendances à partir de ces contraintes
  • Pour une application, il n’y a pas de coût à poser une borne supérieure, et cela protège des montées inattendues de versions majeures
  • Le sujet ici concerne la maintenance d’applications comme des sites web, des services ou des outils internes ; pour la distribution de bibliothèques, une valeur par défaut sans borne supérieure peut rester raisonnable

Les points corrigés et les problèmes qui restent

  • uv pip list --outdated permet d’afficher uniquement les paquets obsolètes
$ uv pip list --outdated
  • Cela affaiblit la critique visant la sortie bruyante de uv tree --outdated --depth 1
  • Le problème qui reste est que cette fonctionnalité n’est pas une commande de premier niveau mais se trouve sous l’espace de noms compatible pip, ce qui nuit à sa découvrabilité
  • Le réglage add-bounds = "major" permet de définir des bornes par défaut, donc l’alternative binaire entre tout éditer à la main ou accepter le risque n’est pas exacte
  • Malgré cela, la fonctionnalité reste en préversion et la gestion des paquets pour les applications a toujours besoin de contraintes par défaut plus sûres et de commandes de mise à jour plus intuitives

Les améliorations souhaitées

  • Il faut une commande dédiée uv outdated qui montre clairement uniquement les paquets obsolètes
  • Il faut une commande update plus ergonomique, qui permette de mettre à jour plusieurs paquets sans répéter des flags
  • Les contraintes de version par défaut devraient mieux refléter les attentes de stabilité liées au versionnage sémantique (SemVer)
  • En l’état, il reste une charge mentale : il faut examiner avec suspicion chaque ligne modifiée dans le lockfile

1 commentaires

 
GN⁺ 5 시간 전
Réactions sur Hacker News
  • La plage de versions par défaut de uv add peut être définie comme un paramètre persistant, donc pas besoin de la repasser à chaque fois
    Référence : https://docs.astral.sh/uv/reference/settings/#add-bounds
    La raison pour laquelle il n’y a pas de borne supérieure par défaut est que cela crée beaucoup de conflits inutiles dans l’écosystème, et à l’époque où j’utilisais Poetry, j’avais aussi rassemblé des ressources à ce sujet : https://github.com/zanieb/poetry-relax#references

    • Je comprends qu’il soit important de supprimer les bornes supérieures lorsqu’on publie une bibliothèque, mais l’article est écrit du point de vue de quelqu’un qui construit un site web, pas une bibliothèque
      Dans un projet web, quand j’utilise des dépendances, je veux des bornes supérieures pour éviter les changements cassants, en partant du principe que les dépendances respectent SemVer
    • La communauté Haskell a aussi lutté avec ce problème pendant des années, et l’approche la plus efficace à un moment a été Stackage
      L’idée était d’encourager à ne pas mettre de bornes supérieures défensives, puis de construire en continu, à chaque release, une grande partie active de l’écosystème pour détecter les vrais problèmes de compatibilité, envoyer des alertes automatiques aux mainteneurs et donner un calendrier clair pour rester dans la prochaine release « LTS »
      Aujourd’hui, le solveur de Cabal semble assez stable à lui seul, mais des builds nocturnes à grande échelle, avec des échecs et blocages visibles, ont probablement contribué à garder l’écosystème résoluble
    • Je viens d’apprendre l’existence du paramètre add-bounds, et même si le verrouillage précis des dépendances est important, c’est utile pour les projets que des développeurs moins expérimentés pourraient facilement négliger, surtout quand il s’agit de produits finaux plutôt que de bibliothèques
    • Je me demande s’il existe un moyen de définir le flag --native-tls de façon permanente
      À cause de la configuration Zscaler de mon entreprise, UV échoue toujours sans ce flag
      Je me demande aussi s’il est prévu de prendre en charge la possibilité de spécifier une version de Python compatible avec une architecture donnée. Un package maintenu par mon entreprise doit utiliser Python 32 bits, donc je dois toujours passer --python /path/to/32bit
    • C’est peut-être une question un peu mal formulée, mais je me demande s’il existe un moyen de faire en sorte que uv respecte exclude-newer dans pyproject.toml
      Quand j’exécute uv run, exclude-newer est supprimé de pyproject.toml
      Je pourrais lancer uv run —-frozen ou uv run --exclude-newer à chaque fois, mais ça ne semble pas être le bon flux, donc je me demande s’il existe une manière idiomatique de faire que je rate
  • uv a besoin d’un résultat de résolution unique, donc l’absence de borne supérieure est un choix de conception intentionnel
    npm peut installer des résolutions différentes à différents endroits de l’arbre, mais en Python ce n’est pas une option. J’ai dû prendre la même décision dans Rye, et il n’y a pas de meilleure solution
    Mettre des bornes supérieures peut en pratique créer un arbre qu’on ne peut plus résoudre. Une partie de l’écosystème Python a même distribué des overrides pour de vieux packages publiés avec de mauvaises hypothèses sur les bornes supérieures
    Aujourd’hui, on ne peut pas savoir si son package sera compatible ou non avec un package qui n’a pas encore été publié

    • Personnellement, je préfère recevoir de uv une erreur disant que les packages ne sont pas compatibles au moment de la mise à jour, et pouvoir override si nécessaire
      C’est préférable à tomber sur des erreurs d’incompatibilité de version à l’exécution, difficiles à diagnostiquer
    • Le vrai problème, ce n’est pas vraiment l’absence de borne supérieure dans pyproject.toml
      Le vrai problème, c’est que uv lock —-upgrade met tout à niveau d’un coup dès qu’il n’y a pas de borne supérieure
      S’il existait un moyen de mettre à jour des packages sans faire monter leur version majeure, cette commande serait bien plus sûre
    • uv a beaucoup amélioré les choses, mais il y a probablement, au fond, pas mal d’éléments que les outils seuls ne peuvent pas résoudre
      C’est infiniment mieux qu’avant uv, mais il semble difficile d’aller jusqu’au bout sans changements cassants à l’échelle de tout l’écosystème. Quand on repense à la transition de Python 2 vers 3, il ne semble pas y avoir beaucoup d’appétit pour ce genre de changement avant un moment
    • Pour les auteurs de bibliothèques, c’est vrai, mais quand on construit un site web et qu’on dépend de nombreux packages, on veut des bornes supérieures pour se sentir en sécurité lors des mises à jour
      Le flag —-bound aide, mais c’est encore une chose de plus à saisir et à retenir
      Si uv pouvait savoir que le projet concerné n’est pas une bibliothèque, peut-être pourrait-il mettre des bornes supérieures par défaut
    • En réalité, que ce soit avec uv ou npm, la bonne manière de faire consiste à tout passer en = et à ne pas croire que les mises à jour non majeures ne casseront rien, puis à mettre à jour uniquement à la main
  • J’ai 257 dépendances Python dans une application en production, dont plus de la moitié sont des dépendances directes
    Je ne mets pas de bornes supérieures dans pyproject.toml, et toutes les deux semaines j’exécute uv lock --upgrade via GitHub Actions
    J’ai une bonne couverture de tests, donc si quelque chose casse, les tests échouent, et il y a aussi un flux de revue assisté par IA. Quand une PR de mise à jour est créée, un workflow IA liste les mises à jour de versions majeures et mineures via un script Python, va chercher les changelogs, les lie et les résume, puis analyse le niveau de risque de chaque package en fonction de la manière dont il est utilisé dans la base de code
    En général, c’est presque indolore, et il n’est pas nécessaire de mettre à jour les packages un par un, de vérifier les vieux packages ou de gérer des dépendances négligées. Il est très rare qu’un changement nécessite une correction par l’auteur de la dépendance et ne puisse pas être résolu dans le code, environ une fois par an. Sur les trois derniers mois, il y a eu 18 hausses de version majeure, et une seule a nécessité une modification du code
    J’aimerais faire pareil côté front-end, mais je n’ai pas assez de tests pour le faire en toute sécurité. Les tests backend sont plus faciles à écrire et plus importants, donc je pense que toutes les bases de code devraient en avoir. Avec des tests, on peut simplement tout mettre à jour automatiquement

    • Écrire des tests est aussi quelque chose que les agents IA font bien
      Au moins pour transformer des consignes en langage naturel en tests précis
      Cela fait un moment que je n’écris plus les tests moi-même à la main, alors qu’avant c’était quelque chose dont je me plaignais toujours, et ce n’est plus le cas
  • UV a beaucoup apporté à Python, mais aujourd’hui j’ai quand même bien galéré
    J’essayais de centraliser la gestion de scripts dispersés dans plusieurs dépôts et implémentés différemment au fil du temps
    L’idée était de faire uv run --with $package main --help, avec le comportement automatique suivant : 1) installer puis exécuter si absent, 2) ne pas installer si déjà à jour, 3) mettre à jour si ce n’est pas la dernière version
    Mais les trois points se sont révélés plus compliqués que prévu. En pratique, uv run réinstallait à chaque fois, avec 6 secondes pour l’environnement virtuel et l’installation
    uvx ou uv tool n’étaient pas vraiment mieux, car ils introduisaient un nouveau problème : les utilisateurs ne recevaient pas les mises à jour
    J’ai fini par faire un script qui interroge CodeArtifact avec un GET paginé, met à jour si une version non-développement plus récente existe, puis relance. Ça fonctionne, et 200 ms de latence valent mieux que 6 secondes, mais ce n’était pas l’expérience que je voulais

    • Je suis un peu confus, car uv run --with $package main --help devrait avoir exactement ce comportement avec très peu de surcoût
      Il ne devrait pas réinstaller à chaque fois ; l’environnement --with est conservé dans le cache. Et même si l’environnement n’était pas mis en cache, les dépendances le sont, et les installer depuis le cache est très rapide. On devrait clairement être en dessous de 200 ms
      Si vous ouvrez un exemple de reproduction détaillé, je peux regarder
    • Pour cet usage, uv tool install et uv tool upgrade semblent appropriés
      Cela dit, ce genre de petite friction devrait être relativement facile à corriger, donc ce serait bien d’ouvrir un ticket
    • On peut aussi définir les dépendances requises dans le bloc de documentation en haut du fichier, puis exécuter simplement uv run main
      Cela installera, mettra en cache et exécutera automatiquement les dépendances nécessaires : https://docs.astral.sh/uv/guides/scripts/
    • Je me dis que les utilisateurs pourraient simplement exécuter uv tool upgrade
    • https://copier.readthedocs.io/en/stable/ mérite aussi un coup d’œil
      Je ne sais pas si c’est exactement le même cas d’usage, mais c’était excellent pour synchroniser un écosystème de microservices en polyrepo
  • J’ai été assez surpris de voir "uv tree --outdated --depth 1" recommandé pour afficher la liste des dépendances obsolètes
    Personnellement, depuis que cette commande existe, j’utilise "uv pip list --outdated"
    Cela dit, je suis d’accord pour dire qu’une commande aussi importante mériterait une sous-commande de premier niveau distincte

    • Du point de vue de l’auteur, ce n’était pas vraiment une recommandation, mais la seule méthode qu’il connaissait
      Il est vrai que "uv pip list --outdated" fournit une sortie bien meilleure, merci
      Mais le simple fait qu’il y ait deux façons de voir les packages obsolètes, avec des sorties très différentes, renforce aussi l’impression d’une UX bancale
    • "uv tree -od1" devrait probablement fonctionner aussi
      Mais la critique adressée à des gestionnaires de paquets comme pacman, comme à apt, était aussi qu’il faut proposer des commandes faciles à comprendre pour les usages fréquents
  • Vu le titre qui parle de quelque chose d’« affreux », les exemples montrent surtout qu’il faut ajouter quelques arguments
    Un meilleur titre serait plutôt améliorations de qualité de vie souhaitées dans UV

    • Cette formule, comme des phrases du genre « qui a conçu cette interface en ligne de commande », donne l’impression de chercher l’attention et les clics
      Le fond du retour est utile et je suis d’accord avec la plupart des points, mais ce genre de formulation diminue la valeur du feedback et appelle des réactions défensives
      L’interface en ligne de commande de uv me semble aussi pénible par moments, mais je comprends pourquoi elle a été écrite ainsi
  • uv est excellent, mais aujourd’hui le plus gros problème du packaging Python reste de bien gérer le packaging scientifique / machine learning
    Si on veut installer PyTorch, il faut déjà déterminer quelle version, puis si c’est CUDA. Et si c’est CUDA, il y a encore six variantes selon la version de CUDA, sans compter que les wheels sont trop gros pour être mis sur PyPI et doivent être récupérés directement
    Conda ne résout ce problème qu’en partie. Spack est extrêmement configurable et permet d’avoir les dépendances C/C++/Fortran et la toolchain de compilation nécessaires pour obtenir les meilleures performances, mais il s’intègre mal avec uv et les autres. Résultat : il est difficile d’emmener en production un projet expérimental de machine learning créé par des chercheurs

    • Avant, je contournais ça avec Anaconda, mais il y avait trop de choses annexes embarquées, et l’environnement de développement ne ressemblait en rien à l’environnement de production, donc ce n’était pas idéal non plus
      Au final, on retombe sur la situation évoquée plus haut
  • Il y a beaucoup de retours utiles, mais aussi une part de formulation clickbait
    Concernant pnpm outdated, ce n’est pas quelque chose qui revenait très souvent jusqu’ici, mais cela semble être une demande raisonnable. Cela vient probablement de différences culturelles entre Python et JavaScript. En Python, je me suis rarement soucié du fait qu’une dépendance soit obsolète tant qu’elle n’était pas vulnérable ou cassée, alors que dans l’écosystème JavaScript, il semble assez courant de mettre à niveau quand l’occasion se présente. Ce n’est pas pour dire que c’est mauvais, mais cela montre à quel point les intuitions sur ce qu’une interface en ligne de commande doit exposer peuvent différer entre grandes communautés de programmation
    Comme l’a dit Armin, le comportement de uv sur les bornes supérieures est intentionnel, et fonctionnellement nécessaire compte tenu de la manière dont Python résout les dépendances. C’est un compromis propre à Python par rapport à d’autres langages, mais je pense que c’est un bon compromis, dans la mesure où il n’y a qu’une seule version de chaque dépendance dans l’arbre et que toutes les exigences entremêlées sont résolues en fonction de cela
    Si uv lock --upgrade fonctionne ainsi, c’est parce qu’il met à jour le fichier de verrouillage, pas les exigences de l’utilisateur. À l’inverse, pnpm update semble mettre à jour les exigences utilisateur dans package.json. Cela peut prêter à confusion, mais le fait de le placer sous uv lock est plus exact. Sinon, des utilisateurs risqueraient d’être déroutés parce que uv upgrade ne ferait pas la mise à niveau qu’ils imaginent. Il y a sans doute encore de la place pour une présentation plus propre, et il y a manifestement une demande pour une sous-commande uv qui mette aussi directement à jour les exigences elles-mêmes
    https://news.ycombinator.com/item?id=48230048

    • Je suis d’accord sur les packages obsolètes et les bornes supérieures, mais si les utilisateurs se plaignent que l’interface est difficile, c’est bien qu’il y a quelque chose
      La commande uv lock qui ne touche qu’au fichier de verrouillage a du sens, mais les utilisateurs ont aussi un vrai besoin de mettre à niveau les dépendances directes et transitives. Pour les transitives, uv lock --upgrade-package le permet, mais c’est un peu verbeux. Cela fonctionne aussi pour les dépendances directes, mais sans modifier pyproject.toml, alors que ce fichier est bien plus visible pour les développeurs
      Si les versions de packages dans uv.lock prennent de l’avance sur pyproject.toml, alors pyproject.toml devient moins fiable comme guide pour comprendre la surface de dépendances. Une commande uv upgrade conviviale serait bienvenue
      Le plus gros piège UX que j’ai vu jusqu’ici avec uv, c’est uv pip. Beaucoup de projets utilisent correctement uv avec pyproject.toml et uv.lock pour le développement, mais dans les Dockerfiles de déploiement ou les outils CI, ils font uv pip install -r pyproject.toml, ce qui contourne uv.lock
      Le fait que les agents de code recommandent de mauvais schémas uv pip parce qu’il y a trop de pip dans leurs données d’entraînement pose problème, mais uv devrait aussi offrir des garde-fous pour protéger les utilisateurs
      uv est un excellent outil et devrait être plus largement adopté : https://aleyan.com/blog/2026-why-arent-we-uv-yet
    • Si cela a paru « clickbait » du point de vue de l’auteur, c’est regrettable, mais en réalité ce n’est que de la franchise et de la directité néerlandaises
      poetry update met aussi à jour le fichier de verrouillage. Je trouve que la manière dont l’interface CLI de uv est structurée est assez pénible au quotidien. On a l’impression qu’elle est conçue pour l’exactitude et pour les machines, pas pour être conviviale
    • En venant de pip vers uv, je reviens à uv pip list --outdated quand j’ai besoin de cette information
    • uv upgrade est sur la feuille de route
      Si cela n’a pas encore été fait, c’est parce qu’il est difficile d’en faire une excellente expérience, qu’il y a bien plus de nuances que ce que les gens imaginent, et que l’équipe est petite avec beaucoup de priorités
    • L’un des usages d’une fonctionnalité du type pnpm outdated est de voir ce qui serait mis à jour si l’on exécutait "uv sync --update" ou "uv lock --update"
      Cela dit, il vaudrait peut-être mieux ajouter une demande de confirmation à ces commandes
  • Pixi utilise uv comme backend, et j’ai apprécié son interface parce qu’il est facile d’ajouter un alias de tâche qui liste joliment les packages obsolètes
    En particulier, Pixi-diff-to-markdown rend les mises à jour automatiques de packages en CI faciles à parcourir
    Si vous voulez voir lesquels des packages obsolètes seraient mis à jour, vous pouvez créer dans votre projet un alias de tâche comme celui-ci
    pixi task add outdated "pixi update --dry-run --json | pixi exec pixi-diff-to-markdown"
    Puis exécuter pixi run outdated dans le projet
    La sortie est un tableau Markdown lisible avec les packages qui seraient mis à jour, leur version actuelle et la nouvelle version qui serait installée par la commande pixi update. Bien sûr, cela dépend aussi des préférences et du contexte

  • Récemment, un script env est apparu dans mon PATH et a perturbé l’usage normal de la commande UNIX env
    J’ai découvert que c’était créé par l’installateur uv quand on exécute la commande ci-dessous
    curl -LsSf [https://astral.sh/uv/install.sh](<https://astral.sh/uv/install.sh>;) | sh
    Il installe dans $HOME/.cargo/bin/, puis crée un script shell dans $HOME/.cargo/env qui préfixe PATH avec $HOME/.cargo/bin/ si ce chemin n’y est pas déjà
    J’ai du mal à comprendre quels programmeurs écrivent du code qui écrase ainsi des commandes UNIX de base