- uv destaca por su velocidad, la gestión de versiones de Python y la integración en un solo binario, pero la UX de gestión de paquetes en la etapa de mantenimiento sigue siendo tosca
- Los paquetes desactualizados se pueden revisar con
uv pip list --outdated, pero al no ser un comando de nivel superior sino estar bajo el namespace de compatibilidad con pip, su descubribilidad es baja
uv add pydantic agrega por defecto restricciones sin límite superior, como pydantic>=2.13.4, lo que permite incluso saltos de versión mayor
- La actualización completa se hace con
uv lock --upgrade, y para varios paquetes específicos hay que repetir --upgrade-package, lo que vuelve el comando verboso
- La configuración
add-bounds = "major" permite restricciones predeterminadas más seguras, pero es una función en vista previa y hace falta una UX de actualización más intuitiva para aplicaciones
Fortalezas de uv y molestias en la etapa de mantenimiento
- uv de Astral tiene como puntos fuertes la velocidad, la gestión de versiones de Python y la capacidad de reemplazar varias herramientas con un solo binario
- El flujo para iniciar un proyecto nuevo de Python y agregar la primera dependencia es sencillo, pero cuando el proyecto entra en la etapa de mantenimiento, la UX para revisar paquetes desactualizados y hacer actualizaciones periódicas se siente más tosca que en pnpm o Poetry
- Los problemas principales están en la descubribilidad del comando para revisar paquetes desactualizados, la ausencia de un límite superior en las restricciones de versión por defecto y lo verboso de los comandos de actualización
Revisar paquetes desactualizados
- En proyectos de JavaScript,
pnpm outdated permite ver de forma concisa los paquetes desactualizados, la versión actual, la última versión y la versión permitida según las restricciones
- En uv no hay un comando de nivel superior
uv outdated, y al principio se usó el siguiente comando como alternativa
$ uv tree --outdated --depth 1
uv tree --outdated --depth 1 no filtra solo los elementos desactualizados, sino que imprime todo el árbol de dependencias de nivel superior y luego agrega una pequeña anotación junto a los elementos que pueden actualizarse
- Aunque tengas 50 dependencias y solo 2 paquetes desactualizados, igual tienes que revisar una lista de 50 líneas
poetry show --outdated también tiene un nombre de comando menos intuitivo, pero en la práctica muestra solo los paquetes desactualizados
El riesgo de las restricciones de versión por defecto
-
Enfoque predeterminado de pnpm y Poetry
pnpm add escribe en package.json un requisito con caret como ^1.23.4
^1.23.4 permite versiones 1.x.x, pero no actualiza a 2.0.0
- Poetry también usa por defecto un formato como
>=1.23.4,<2.0.0; aunque es menos fácil de leer, el efecto es el mismo
- En ambas herramientas, bajo la suposición de que el paquete respeta SemVer, ejecutar
pnpm update o poetry update reduce la posibilidad de que el build se rompa por cambios importantes en la API
-
Enfoque predeterminado de uv
uv add pydantic agrega a pyproject.toml una restricción sin límite superior como esta
dependencies = [
"pydantic>=2.13.4",
]
- Con esta restricción, están permitidas las versiones 2, 3 o incluso 100 de pydantic
- Si ejecutas una actualización masiva, no solo recibes correcciones de errores, sino también cambios incompatibles publicados por todos los mantenedores del grafo de dependencias
- En particular, esto puede convertirse en un riesgo de estabilidad durante el mantenimiento de aplicaciones
La UX de los comandos de actualización
- En pnpm y Poetry, la actualización completa es así de simple
$ pnpm update
$ poetry update
- En uv, para una actualización completa se usa este comando
$ uv lock --upgrade
uv lock --upgrade no funciona como uv update ni uv upgrade, sino como una opción del comando lock, por lo que resulta menos intuitivo como comando de gestión de paquetes de uso humano
- Combinado con restricciones sin límite superior,
uv lock --upgrade se convierte en una decisión de llevar todos los paquetes del lockfile a la versión más reciente posible
- Esa actualización puede incluir dependencias profundamente anidadas que ni siquiera conoces de forma directa
- Si quieres actualizar solo paquetes específicos, en pnpm basta con listar los nombres así
$ pnpm update pydantic httpx uvicorn
- En uv hay que repetir la bandera
--upgrade-package para cada paquete
$ uv lock --upgrade-package pydantic --upgrade-package httpx --upgrade-package uvicorn
- Al subir varios paquetes de una sola vez, repetir la bandera se vuelve una gran molestia
La bandera --bounds y la configuración
- Hace poco se agregó a uv la opción
--bounds para uv add
$ uv add pydantic --bounds major
- Este comando genera una restricción más segura:
pydantic>=2.13.4,<3.0.0
--bounds major es actualmente una función en vista previa y es una opción opt-in que hay que escribir manualmente en cada comando
- Después se descubrió que se puede definir un valor predeterminado una vez en
pyproject.toml
[tool.uv]
add-bounds = "major"
- Con esta configuración, ya no hace falta escribir
--bounds major cada vez, y los siguientes uv add obtienen un valor predeterminado más razonable
- Para aplicaciones, sería mejor que este comportamiento fuera el predeterminado, pero en la práctica la facilidad de uso no es tan mala como se describió al principio
La diferencia entre aplicaciones y librerías
- El consejo estándar en el empaquetado de Python es que las librerías distribuidas en PyPI no fijen un límite superior, y ese consejo es válido
- Si todas las librerías fijaran un límite superior, el árbol de dependencias de los consumidores aguas abajo podría no resolverse
- En cambio, las aplicaciones son el nodo final del grafo de dependencias, y otros usuarios no resuelven sus dependencias en función de esas restricciones
- En las aplicaciones no hay costo por establecer un límite superior, y eso protege contra saltos inesperados de versión mayor
- Aquí el alcance es el mantenimiento de aplicaciones como sitios web, servicios o herramientas internas; para distribuir librerías, el valor predeterminado sin límite superior puede ser razonable
Correcciones y problemas que siguen pendientes
- Con
uv pip list --outdated se pueden ver filtrados solo los paquetes desactualizados
$ uv pip list --outdated
- Esto debilita la crítica sobre la salida ruidosa de
uv tree --outdated --depth 1
- El problema que queda es que esta función no está en un comando de nivel superior, sino bajo el namespace de compatibilidad con
pip, así que su descubribilidad es baja
- Con la configuración
add-bounds = "major" se pueden definir bounds predeterminados, así que no es correcto plantearlo como una disyuntiva entre editar manualmente el límite superior cada vez o aceptar el riesgo
- Aun así, la función sigue en vista previa, y para la gestión de paquetes en aplicaciones hacen falta restricciones predeterminadas más seguras y comandos de actualización más intuitivos
Mejoras deseadas
- Hace falta un comando dedicado
uv outdated que muestre claramente solo los paquetes desactualizados
- Hace falta un comando
update más ergonómico para actualizar varios paquetes sin repetir banderas
- Las restricciones de versión predeterminadas deberían reflejar mejor la expectativa de estabilidad de la gestión semántica de versiones (SemVer)
- En el estado actual, sigue existiendo la carga de revisar con sospecha cada línea modificada del lockfile
1 comentarios
Comentarios de Hacker News
El rango de versiones predeterminado de
uv addpuede configurarse como una configuración persistente, así que no hace falta pasarlo cada vezReferencia: https://docs.astral.sh/uv/reference/settings/#add-bounds
La razón por la que no incluye un límite superior por defecto es que eso genera muchos conflictos innecesarios en el ecosistema, y de la época en que usaba Poetry incluso había reunido material relacionado: https://github.com/zanieb/poetry-relax#references
Al usar dependencias en un proyecto web, quiero que haya límites superiores para evitar cambios incompatibles, asumiendo que las dependencias cumplen con SemVer
Se desincentivaba poner límites superiores defensivos, se compilaba continuamente una gran parte activa del ecosistema en cada release para encontrar problemas reales de compatibilidad, se enviaban notificaciones automáticas a los responsables y se daba un calendario claro para permanecer en el siguiente release "LTS"
Ahora parece que solo con el solver de Cabal ya es bastante estable, pero probablemente una amplia batería de builds nocturnos y fallas/bloqueos visibles ayudó a mantener el ecosistema resoluble
add-bounds, y aunque fijar dependencias con precisión es importante, esto resulta útil para proyectos que desarrolladores con menos experiencia pueden pasar por alto, especialmente cuando no son librerías sino productos finales--native-tlsPor la configuración de Zscaler en mi trabajo, UV siempre falla sin esa bandera
También me pregunto si planean soportar una función para especificar una versión de Python compatible con cierta arquitectura. Un paquete que mantenemos en la empresa requiere Python de 32 bits, así que siempre tengo que pasar
--python /path/to/32bitexclude-newerenpyproject.tomlCuando ejecuto
uv run, se eliminaexclude-newerdepyproject.tomlPodría ejecutar
uv run —-frozenouv run --exclude-newercada vez, pero no parece el flujo correcto, y me pregunto si hay una manera idiomática que me estoy perdiendouvestá diseñado intencionalmente para no tener límites superiores, porque necesita un único resultado de resoluciónnpm puede instalar distintos resultados de resolución en diferentes partes del árbol, pero en Python eso no es una opción. En Rye también hubo que tomar la misma decisión, y no hay una solución mejor
Si agregas límites superiores, en la práctica puedes terminar con árboles que ya no se pueden resolver. Algunas partes del ecosistema de paquetes de Python en el pasado incluso distribuyeron overrides para paquetes viejos publicados con límites superiores incorrectos
A día de hoy no se puede saber si tu paquete será compatible o no con un paquete que todavía ni siquiera ha sido publicado
uvme dé un error al actualizar si los paquetes no son compatibles, y poder hacer override si hace faltaEs mejor que encontrarte en runtime con errores de incompatibilidad de versiones difíciles de rastrear
pyproject.tomlno es realmente el problemaEl problema real es que
uv lock —-upgradeactualiza en bloque todo lo que no tiene límite superiorSi hubiera una forma de actualizar paquetes sin subir la versión major, este comando sería mucho más seguro
Comparado con antes de uv, está muchísimo mejor, pero parece difícil que llegue a estar completamente bien sin hacer ciertos cambios incompatibles a nivel de todo el ecosistema. Pensando en lo que fue el paso de Python 2 a 3, no parece que haya mucho apetito por ese tipo de cambios en el corto plazo
La bandera
—-boundayuda, pero es una cosa más que hay que escribir y recordarSi uv pudiera detectar que el proyecto no es una librería, quizá podría poner límites superiores por defecto en ese caso
=y actualizar solo de forma manual, sin confiar en que las actualizaciones no major no vayan a romper nadaTengo 257 dependencias de Python en una app en producción, y más de la mitad son dependencias directas
No pongo límites superiores en
pyproject.toml, y cada dos semanas ejecutouv lock --upgradecon GitHub ActionsTenemos buena cobertura de tests, así que si algo se rompe los tests fallan, y además hay un flujo de revisión asistido por IA. Cuando se crea un PR de actualización, un workflow de IA enumera con un script de Python las actualizaciones major y minor, busca los changelogs, los enlaza y los resume, y analiza el riesgo de cada paquete según cómo se usa ese paquete en el codebase
En general casi no duele, y no hace falta subir paquetes uno por uno, ni revisar paquetes viejos, ni lidiar con dependencias abandonadas. Es muy raro que haga falta un arreglo del autor de una dependencia y que no se pueda resolver en el código; pasa más o menos una vez al año. En los últimos 3 meses hubo 18 subidas de versión major, y solo una requirió cambios en el código
Me gustaría poder hacer lo mismo en frontend, pero no hay suficientes tests como para ejecutarlo con seguridad. Los tests de backend son más fáciles de escribir y más importantes, así que creo que cualquier codebase debería tenerlos. Si tienes tests, simplemente puedes actualizar todo automáticamente
Al menos sobresalen convirtiendo instrucciones en lenguaje natural en tests precisos
Hace tiempo que no escribo tests manualmente, y antes era algo de lo que siempre me quejaba, pero ya no
UV ha hecho mucho por Python, pero hoy tuve que pelear bastante con él
Quería gestionar centralmente scripts dispersos en varios repositorios y que con el tiempo se habían implementado de formas distintas
La idea era
uv run --with $package main --help, y quería que automáticamente 1) instalara y ejecutara si no estaba, 2) no instalara si ya estaba actualizado, 3) actualizara si no estaba al díaPero las tres cosas fueron más complicadas de lo esperado. Básicamente
uv runreinstalaba cada vez, y el entorno virtual más la instalación tardaban 6 segundosuvxouv tooltampoco fueron mucho mejores, porque apareció otro problema nuevo: los usuarios no recibían actualizacionesAl final hice que un script hiciera un GET paginado a CodeArtifact para ver si había una versión no de desarrollo más nueva; si la había, actualizaba y volvía a ejecutar. Funciona, y 200 ms de retraso son mejores que 6 segundos, pero no era la experiencia que quería
uv run --with $package main --helpdebería hacer justo eso con muy poco overheadNo reinstala cada vez, y el entorno de
--withse guarda en caché y se conserva. Incluso si el entorno no estuviera cacheado, las dependencias sí lo están, y la instalación desde caché es muy rápida. Definitivamente debería estar por debajo de 200 msSi abres un ejemplo reproducible con más detalle, puedo revisarlo
uv tool installyuv tool upgradeparecen lo correctoAun así, este tipo de pequeñas fricciones probablemente se puedan resolver con relativa facilidad, así que estaría bien que abras un issue
uv run mainEntonces instalará, cacheará y ejecutará automáticamente las dependencias necesarias: https://docs.astral.sh/uv/guides/scripts/
uv tool upgradeNo sé si es exactamente el mismo caso de uso, pero me ha ido muy bien para sincronizar un ecosistema de microservicios en polyrepo
Me sorprendió bastante que recomendaran
"uv tree --outdated --depth 1"para ver la lista de dependencias viejasPersonalmente, desde que se introdujo he estado usando
"uv pip list --outdated"Aun así, estoy de acuerdo en que un comando tan importante merecería su propio subcomando de nivel superior
Es cierto que
"uv pip list --outdated"da una salida mucho mejor, graciasPero también me hace pensar que la UX está mal, porque hay dos maneras de ver paquetes desactualizados y la salida es muy distinta
"uv tree -od1"probablemente también funcionePero la crítica a gestores de paquetes como pacman también era que, como en apt, para comandos de uso frecuente debería haber comandos fáciles de entender para humanos
Comparado con lo de “desastre” en el título, los ejemplos en realidad son más bien cosas donde hay que escribir unos cuantos argumentos extra
Un mejor título sería algo como mejoras de calidad de vida que me gustaría ver en UV
El feedback en sí es útil y en su mayoría estoy de acuerdo, pero ese tipo de frases reduce el valor del feedback y provoca respuestas defensivas
A mí también me parece engorrosa la interfaz de línea de comandos de uv, pero entiendo por qué está hecha así
uves excelente, pero actualmente el problema más grande del empaquetado en Python sigue siendo manejar bien el empaquetado científico y de machine learningSi quieres instalar PyTorch, primero tienes que ver qué versión quieres y si es CUDA. Si es CUDA, además hay como 6 variantes según la versión de CUDA, y los wheels son demasiado grandes para subirlos a PyPI, así que hay que descargarlos directamente
Conda solo resuelve este problema de forma parcial. Spack es extremadamente configurable y puede darte las dependencias necesarias en C/C++/Fortran y el toolchain del compilador para sacar el máximo rendimiento, pero no se integra bien con uv y otros. Por eso es difícil llevar a producción proyectos experimentales de machine learning creados por investigadores
Al final uno vuelve a la situación que mencioné antes
Hay mucho feedback útil, pero mezclado con expresiones tipo clickbait
Sobre
pnpm outdated, no había salido muy seguido hasta ahora, pero parece una petición razonable. Da la impresión de venir de una diferencia cultural entre Python y JavaScript. En Python casi nunca me importó si una dependencia estaba desactualizada salvo que fuera vulnerable o estuviera rota, pero en el ecosistema de JavaScript parece bastante común actualizar cuando se presenta la oportunidad. No digo que eso sea malo; más bien muestra cuánto pueden diferir las intuiciones sobre qué exponer en una interfaz de línea de comandos entre grandes comunidades de programaciónComo dijo Armin, el comportamiento de los límites superiores en uv es intencional y funcionalmente necesario por la forma en que Python resuelve dependencias. Es un trade-off que Python eligió frente a otros lenguajes, pero me parece un buen trade-off en el sentido de que solo hay una instancia de cada dependencia en el árbol y todos los requisitos entrelazados se resuelven en torno a eso
La razón por la que
uv lock --upgradees así es que está actualizando el archivo de bloqueo, no los requisitos declarados por el usuario. En cambio,pnpm updateparece actualizar los requisitos del usuario enpackage.json. Puede ser confuso, pero ponerlo debajo deuv lockes más preciso. De lo contrario, aparecerían usuarios confundidos porqueuv upgradeno hace la actualización que cada quien imagina. Aun así, hay margen para presentarlo de forma más limpia, y claramente existe demanda de los usuarios por un subcomando de uv que actualice directamente los requisitos mismoshttps://news.ycombinator.com/item?id=48230048
Tiene sentido que el comando
uv locksolo maneje el lockfile, pero los usuarios tienen la necesidad real de actualizar dependencias directas y transitivas. Las transitivas se pueden actualizar conuv lock --upgrade-package, pero es un poco verboso. También funciona con dependencias directas, pero no tocapyproject.toml, y ese archivo es mucho más visible para los desarrolladoresSi las versiones de paquetes en
uv.lockse adelantan respecto apyproject.toml, entoncespyproject.tomlse vuelve menos confiable como guía para entender la superficie de dependencias. Estaría bien tener un comando amable comouv upgradeLa trampa de UX más grande que he visto hasta ahora en uv es
uv pip. Muchos proyectos usan correctamente uv conpyproject.tomlyuv.lockdurante el desarrollo, pero en el Dockerfile de despliegue o en herramientas de CI hacenuv pip install -r pyproject.tomly así se saltanuv.lockTambién es un problema que los agentes de código, por tener demasiados ejemplos de
pipen sus datos de entrenamiento, recomienden malos patrones conuv pip, pero uv también debería ofrecer protecciones para los usuariosuv es una gran herramienta y creo que debería usarse más: https://aleyan.com/blog/2026-why-arent-we-uv-yet
poetry updatetambién actualiza el archivo de bloqueo. Me parece que la composición del CLI de uv es bastante molesta para trabajar. Da la impresión de estar diseñada para la exactitud y para las máquinas, no para ser amigable con el usuariouv pip list --outdateduv upgradeestá en el roadmapLa razón por la que todavía no lo han hecho es que es difícil convertirlo en una gran experiencia, tiene muchos más matices de los que la gente espera, el equipo es pequeño y hay muchas prioridades
pnpm outdatedes ver qué se va a actualizar cuando ejecutas"uv sync --update"o"uv lock --update"Aunque quizá sería mejor que esos comandos tuvieran un prompt de confirmación
Pixi usa uv como backend, y me gustó su UI porque permite agregar fácilmente aliases de tareas para listar de forma agradable los paquetes desactualizados
En particular, Pixi-diff-to-markdown ayuda a revisar fácilmente actualizaciones automáticas de paquetes en CI
Si quieres ver qué paquetes desactualizados se actualizarían, puedes crear este alias de tarea en tu proyecto
pixi task add outdated "pixi update --dry-run --json | pixi exec pixi-diff-to-markdown"Y luego ejecutar
pixi run outdateden el proyectoLa salida es una tabla Markdown fácil de leer con los paquetes que se actualizarían, la versión actual y la nueva versión que instalaría el comando
pixi update. Claro, eso puede variar según el gusto y la situación de cada quienHace poco apareció un script
enven mi ruta y eso interfirió con el uso normal del comando UNIXenvResulta que el instalador de uv crea esto al ejecutar el siguiente comando
curl -LsSf [https://astral.sh/uv/install.sh](<https://astral.sh/uv/install.sh>) | shLo instala en
$HOME/.cargo/bin/y crea un script de shell en$HOME/.cargo/envque antepone$HOME/.cargo/bin/alPATHsi no está presenteMe cuesta entender qué clase de programadores escriben código que sobrescribe comandos básicos de UNIX de esta manera