2 puntos por GN⁺ 5 시간 전 | 1 comentarios | Compartir por WhatsApp
  • 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

 
GN⁺ 5 시간 전
Comentarios de Hacker News
  • El rango de versiones predeterminado de uv add puede configurarse como una configuración persistente, así que no hace falta pasarlo cada vez
    Referencia: 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

    • Entiendo que al distribuir librerías es importante eliminar los límites superiores de versión, pero el artículo está escrito desde la perspectiva de alguien que construye un sitio web, no una librería
      Al usar dependencias en un proyecto web, quiero que haya límites superiores para evitar cambios incompatibles, asumiendo que las dependencias cumplen con SemVer
    • La comunidad de Haskell también batalló con este problema durante años, y en algún momento el enfoque más exitoso fue Stackage
      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
    • Apenas ahora me entero de la configuración 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
    • Me pregunto si hay alguna forma de configurar de manera persistente la bandera --native-tls
      Por 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/32bit
    • Tal vez sea una pregunta un poco menos refinada, pero me pregunto si hay forma de hacer que uv respete exclude-newer en pyproject.toml
      Cuando ejecuto uv run, se elimina exclude-newer de pyproject.toml
      Podría ejecutar uv run —-frozen o uv run --exclude-newer cada vez, pero no parece el flujo correcto, y me pregunto si hay una manera idiomática que me estoy perdiendo
  • uv está diseñado intencionalmente para no tener límites superiores, porque necesita un único resultado de resolución
    npm 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

    • Personalmente prefiero que uv me dé un error al actualizar si los paquetes no son compatibles, y poder hacer override si hace falta
      Es mejor que encontrarte en runtime con errores de incompatibilidad de versiones difíciles de rastrear
    • Que no haya límites superiores en pyproject.toml no es realmente el problema
      El problema real es que uv lock —-upgrade actualiza en bloque todo lo que no tiene límite superior
      Si hubiera una forma de actualizar paquetes sin subir la versión major, este comando sería mucho más seguro
    • uv ha mejorado mucho las cosas, pero en el fondo parece que hay bastante que ninguna herramienta puede resolver por sí sola
      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
    • Para autores de librerías eso es correcto, pero cuando construyes un sitio web y dependes de varios paquetes, quieres límites superiores para sentirte seguro al actualizar
      La bandera —-bound ayuda, pero es una cosa más que hay que escribir y recordar
      Si uv pudiera detectar que el proyecto no es una librería, quizá podría poner límites superiores por defecto en ese caso
    • En realidad, tanto con uv como con npm, la forma correcta de usarlo es cambiar todo a = y actualizar solo de forma manual, sin confiar en que las actualizaciones no major no vayan a romper nada
  • Tengo 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 ejecuto uv lock --upgrade con GitHub Actions
    Tenemos 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

    • Escribir tests también es algo en lo que los agentes de IA son buenos
      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ía
    Pero las tres cosas fueron más complicadas de lo esperado. Básicamente uv run reinstalaba cada vez, y el entorno virtual más la instalación tardaban 6 segundos
    uvx o uv tool tampoco fueron mucho mejores, porque apareció otro problema nuevo: los usuarios no recibían actualizaciones
    Al 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

    • Me confunde un poco, porque uv run --with $package main --help debería hacer justo eso con muy poco overhead
      No reinstala cada vez, y el entorno de --with se 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 ms
      Si abres un ejemplo reproducible con más detalle, puedo revisarlo
    • Para ese caso de uso, uv tool install y uv tool upgrade parecen lo correcto
      Aun así, este tipo de pequeñas fricciones probablemente se puedan resolver con relativa facilidad, así que estaría bien que abras un issue
    • También puedes definir las dependencias necesarias en un bloque de documentación al inicio del archivo y luego simplemente ejecutar uv run main
      Entonces instalará, cacheará y ejecutará automáticamente las dependencias necesarias: https://docs.astral.sh/uv/guides/scripts/
    • Supongo que bastaría con que el usuario ejecute uv tool upgrade
    • También vale la pena revisar https://copier.readthedocs.io/en/stable/
      No 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 viejas
    Personalmente, 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

    • Desde la perspectiva del autor, más que una recomendación era la única forma que conocía
      Es cierto que "uv pip list --outdated" da una salida mucho mejor, gracias
      Pero 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 funcione
      Pero 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

    • Esa expresión y frases como “quién diseñó esta interfaz de línea de comandos” parecen puestas para llamar la atención y conseguir clics
      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í
  • uv es excelente, pero actualmente el problema más grande del empaquetado en Python sigue siendo manejar bien el empaquetado científico y de machine learning
    Si 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

    • Antes lo evitaba usando Anaconda, pero trae demasiadas cosas extra y además el entorno de desarrollo queda completamente distinto al de producción, así que tampoco era una gran solución
      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ón
    Como 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 --upgrade es así es que está actualizando el archivo de bloqueo, no los requisitos declarados por el usuario. En cambio, pnpm update parece actualizar los requisitos del usuario en package.json. Puede ser confuso, pero ponerlo debajo de uv lock es más preciso. De lo contrario, aparecerían usuarios confundidos porque uv upgrade no 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 mismos
    https://news.ycombinator.com/item?id=48230048

    • Estoy de acuerdo con lo de los paquetes desactualizados y los límites superiores, pero si los usuarios se quejan de que la interfaz es difícil, claramente hay algo ahí
      Tiene sentido que el comando uv lock solo maneje el lockfile, pero los usuarios tienen la necesidad real de actualizar dependencias directas y transitivas. Las transitivas se pueden actualizar con uv lock --upgrade-package, pero es un poco verboso. También funciona con dependencias directas, pero no toca pyproject.toml, y ese archivo es mucho más visible para los desarrolladores
      Si las versiones de paquetes en uv.lock se adelantan respecto a pyproject.toml, entonces pyproject.toml se vuelve menos confiable como guía para entender la superficie de dependencias. Estaría bien tener un comando amable como uv upgrade
      La trampa de UX más grande que he visto hasta ahora en uv es uv pip. Muchos proyectos usan correctamente uv con pyproject.toml y uv.lock durante el desarrollo, pero en el Dockerfile de despliegue o en herramientas de CI hacen uv pip install -r pyproject.toml y así se saltan uv.lock
      También es un problema que los agentes de código, por tener demasiados ejemplos de pip en sus datos de entrenamiento, recomienden malos patrones con uv pip, pero uv también debería ofrecer protecciones para los usuarios
      uv es una gran herramienta y creo que debería usarse más: https://aleyan.com/blog/2026-why-arent-we-uv-yet
    • Si desde la perspectiva del autor sonó “clickbait”, lo lamento, pero en realidad es solo franqueza y honestidad al estilo neerlandés
      poetry update tambié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 usuario
    • Desde la perspectiva de alguien que migró de pip a uv, cuando necesito esa información termino volviendo a uv pip list --outdated
    • uv upgrade está en el roadmap
      La 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
    • Uno de los usos de funciones tipo pnpm outdated es 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 outdated en el proyecto
    La 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 quien

  • Hace poco apareció un script env en mi ruta y eso interfirió con el uso normal del comando UNIX env
    Resulta 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>;) | sh
    Lo instala en $HOME/.cargo/bin/ y crea un script de shell en $HOME/.cargo/env que antepone $HOME/.cargo/bin/ al PATH si no está presente
    Me cuesta entender qué clase de programadores escriben código que sobrescribe comandos básicos de UNIX de esta manera