7 puntos por GN⁺ 2025-06-25 | 2 comentarios | Compartir por WhatsApp
  • Al cambiar a uv, la velocidad de instalación de dependencias de Python es aproximadamente 10 veces más rápida que con pip, y también permite ejecutarlas como usuario no root sin usar un venv aparte
  • Con base en pyproject.toml, basta con declarar solo las dependencias de nivel superior y uv administra automáticamente el archivo lock, ofreciendo un árbol de dependencias y un control exacto de versiones superior a pip freeze
  • En el Dockerfile se requieren cambios paso a paso, como copiar los binarios de uv y uvx, usar los archivos pyproject.toml/uv.lock y configurar variables de entorno
  • Con comandos como uv sync/add/remove, uv:outdated es posible gestionar fácilmente la adición, eliminación y actualización de dependencias, además de revisar versiones más recientes de los paquetes
  • Facilita la administración periódica del archivo lock y la actualización de dependencias, lo que aporta consistencia en entornos de colaboración y despliegue

Instalación de dependencias 10 veces más rápida, sin venv y en un entorno no root

  • uv es una herramienta que mejora de forma importante la velocidad de instalación de dependencias en proyectos Python frente a pip
  • Con la adopción de uv, en proyectos como Flask o Django se puede experimentar una velocidad de instalación cerca de 10 veces mayor que con pip
  • Incluso sin un entorno virtual (venv) separado, es posible ejecutar de forma segura dentro del contenedor como usuario no root

pyproject.toml vs requirements.txt

  • En lugar del tradicional requirements.txt, si se declaran solo las dependencias de nivel superior en el archivo pyproject.toml, uv genera automáticamente el archivo uv.lock
    • Agregar la entrada [project] dependencies en pyproject.toml
    • Eliminar el requirements.txt existente
  • El archivo lock de uv es similar al resultado de pip freeze, pero incluye un árbol de dependencias y datos de versión más precisos

Cambios en la configuración del Dockerfile

  • Se usan los binarios de uv y uvx copiándolos al contenedor (binarios de Rust compilados estáticamente)
  • En lugar de requirements*.txt, se copian los archivos pyproject.toml, uv.lock\*
  • Agregar variables de entorno:
    • UV_COMPILE_BYTECODE=1: precompila a bytecode en la etapa de build
    • UV_PROJECT_ENVIRONMENT="/home/python/.local": instala los paquetes en una ruta específica sin crear un venv aparte
  • El comando de instalación de dependencias también cambia de pip3-install a uv-install
    • Ejemplo: RUN chmod 0755 bin/* && bin/uv-install

Gestión de dependencias: agregar, quitar, actualizar, etc.

  • Se pueden ejecutar comandos de uv dentro del contenedor mediante un script run independiente
    • ./run deps:install: instala después de construir la imagen y exporta el archivo lock al host
    • ./run deps:install --no-build: actualiza solo el archivo lock sin hacer build
    • ./run uv add mypackage --no-sync: actualiza solo pyproject.toml y el archivo lock; la instalación real se ejecuta por separado
    • ./run uv remove mypackage --no-sync: elimina un paquete
    • ./run uv:outdated: revisa la versión más reciente de las dependencias actuales

Video y guía práctica disponibles

  • Incluye demo real y ejemplos de git diff sobre la adopción de uv, redacción de pyproject.toml, cambios en el Dockerfile, comandos de lock/sync, agregar o quitar dependencias y revisar versiones más recientes
  • También se pueden consultar los diff de migración de dos proyectos: Flask y Django

2 comentarios

 
yangeok 2025-06-26

Justo estaba pensando en migrar lo que desplegaba con Poetry, y esto se ve estable y sencillo ^^

 
GN⁺ 2025-06-25
Opiniones en Hacker News
  • Vale la pena notar que uv soporta un flujo de trabajo que reemplaza directamente a pyenv, virtualenv y pip. No es un enfoque forzado por un lockfile o por pyproject.toml. Con el comando uv python pin <version> se crea un archivo .python-version en el directorio actual, con uv virtualenv se descarga esa versión de Python como en pyenv y se crea el entorno virtual .venv, con uv pip install -r requirements.txt se instalan los paquetes de requirements.txt, y con uv run <command> se puede ejecutar un comando incluyendo las variables de entorno del archivo .env. Eso sí, hay que tener cuidado con el orden de prioridad de las variables de entorno (issue relacionado)

    • La flexibilidad de uv está realmente a otro nivel. La experiencia de hacer en 20 a 30 segundos con uv algo que con pip toma 10 minutos
    • Justo por eso empecé a usar uv. Es súper conveniente. Aunque a veces uv pip se pone lento y no tengo claro por qué; quizá tenga que ver con la red de la empresa
    • Tenía la duda de si el archivo .python-version realmente es necesario, porque entiendo que la información de la versión de Python también se guarda en pyproject.toml
  • # Script para garantizar siempre el lock file más reciente
    if ! test -f uv.lock || ! uv lock --check 2>/dev/null; then
      uv lock
    fi
    

    Este enfoque le quita sentido a la existencia misma del lock file. Si el archivo no existe o es inválido, eso significa que hay un problema serio con el lock file y lo ideal es que alguien familiarizado con el proyecto lo resuelva manualmente. Si no, entonces no tiene mucho caso tener lock file. En CI podría reemplazarse automáticamente el lock file y generar confusión

    • (Respuesta del autor) Si el lock file es inválido, no se ignora silenciosamente para crear uno nuevo. uv lock falla con un mensaje claro, y por errexit en el script de shell todo se detiene de inmediato. La redirección de errores en uv lock --check es para evitar que el mismo error se imprima dos veces. Si se rompe el lock file a propósito y se ejecuta el script, el build se detiene con un mensaje de error específico. Reescribí el script con if-else para que quede más claro. Si no hay lock file, sí corresponde crearlo. En ese caso se genera y luego se hace commit
    • Esto queda cubierto por la opción uv sync --locked. Si no hay lock file o está desactualizado, falla de forma clara. Recomiendo hacer siempre los builds con la opción --locked
    • En el mundo de Python muchas veces no se suben los lock files al control de versiones, y se tratan como una “etapa rara” del proceso de instalación
    • Este enfoque tiene un bug grave. Si usas la bandera --frozen, se supone que el lock file no debe actualizarse, pero en la práctica pasa lo contrario. Coincido en que si falta el lock file o no coincide, tiene que intervenir una persona
    • Aun así, si no hay lock file, o es la primera ejecución o de todos modos será sobrescrito desde el upstream de git. Si está roto, alguien se equivocó durante la instalación, y volver a generarlo me parece prácticamente la única opción razonable. Es una excepción poco común, pero suficiente para un manejo simple
  • Estoy totalmente en contra de que las herramientas de Python se desarrollen en lenguajes que no sean Python. Ya existe C y CPython está estandarizado, así que no hace falta un lenguaje nuevo como Rust. El paquete Pendulum tardó más de 7 meses en soportar 3.13, y creo que fue porque al tener código nativo en Rust faltaba gente capaz de arreglar ese problema. Si hubiera sido C, yo mismo lo habría corregido. (issue relacionado) En un mundo ideal, si quieres hacer un datetime rápido con un lenguaje externo como Rust, lo correcto sería hacerlo vía FFI en un formato reutilizable por varios lenguajes. Todavía no me convence mucho el enfoque basado en Rust, y ya entiendo por qué a la comunidad Linux no le gusta

    • Respeto ese punto de vista, pero creo que hacer una herramienta como uv en Rust es una buena idea. Si una herramienta para administrar Python está hecha en Python, aparece el problema de “qué fue primero, el huevo o la gallina”. Para usar una herramienta de Python, primero tienes que tener Python instalado; además se complica qué versión de Python se usa, los posibles conflictos entre las librerías de la herramienta y las de la app real, el manejo de variables de entorno y el debugging. En cambio, una herramienta binaria hecha en Rust o similar simplemente la descargas y funciona de inmediato, sin preocuparte por nada de eso. Al usuario en general no debería importarle mucho en qué lenguaje está hecha la herramienta
    • Me gusta Python, pero la simplicidad y velocidad de uv no tienen comparación. Cuando necesitas un Python reciente en un servidor EOL, o solo quieres instalar rápido dependencias para un script pequeño, uv es lo mejor. También entiendo parte del argumento: antes hacía todo en pure Python, luego empecé a usar extensiones en C, y cuando notas los límites terminas queriendo escribir casi todo en C. Como C es difícil, últimamente estoy refactorizando a Rust. Cuando el código externo ya supera al interno, conviene cambiarlo todo de lenguaje
    • Si de verdad crees que las herramientas deben hacerse solo en Python, yo voy a estar dando un paseo mientras espero a que termine el lento Pylint
    • El soporte de múltiples lenguajes no representa una gran carga para el usuario. Basta con que la herramienta sea rápida y resuelva bien el problema. En la práctica, sí es mucho más rápida. Una herramienta de administración está hecha para los desarrolladores del objetivo de uso
    • A mí me da igual en qué lenguaje esté hecha, siempre que haga bien su trabajo. Es cierto que los usuarios de Python pueden contribuir más fácil a la herramienta, pero si cumple bien su propósito, el lenguaje da lo mismo. De hecho, cuando te topas con problemas del entorno, una herramienta hecha en Python también puede verse afectada por esos mismos problemas
  • Hay que tener cuidado al usar uv en lugar de pip. Por defecto no genera archivos .pyc, así que el inicio del servicio podría volverse más lento (referencia)

  • Si pruebas uv en un contenedor de Flask, no solo la diferencia en tiempo de build es lo bastante grande como para hacerse tediosa con pip, sino que además el proceso de instalación se vuelve muy predecible. Se acabó esa frustración de que cambien las versiones de dependencias con pip. Usas pyproject.toml, ejecutas uv lock y listo. En Docker, si copias solo pyproject.toml y uv.lock (HOT COPY) y ejecutas uv sync --frozen --no-install-project, puedes cachear la capa de instalación omitiendo el código de la app. Si sabes lo doloroso que es reconstruir toda la capa por cambiar un solo paquete, entiendes por qué esto importa. Si usas la variable UV_PROJECT_ENVIRONMENT=/home/python/.local, puedes pre-warm la imagen base sin venv, compartir builds y reducir costos de infraestructura. Con la opción UV_COMPILE_BYTECODE=1 se generan archivos .pyc durante el build. Se eliminan los entornos mutables y se fuerza la reproducibilidad; si ahora un build falla, queda claro que el problema es del lockfile

  • Incluso en 2025, el empaquetado y la gestión de dependencias en Python siguen siendo un caos

    • Creo que sigue siendo confuso porque no todo el mundo usa uv
    • Esto deja una lección importante: esas cosas hay que dejarlas bien definidas desde el diseño inicial del lenguaje. No patearlas para después de la v2.0, pensar varias veces antes de meter metadata en scripts ejecutables, y entender que algo puede funcionar en ciertos lenguajes pero no ser una buena idea para Python
    • Yo nunca he tenido problemas de dependencias. Con requirements.txt y venv me basta
    • La gestión de dependencias sigue siendo un desastre, y ahora además le agregaron Rust
  • Me da curiosidad comparar la seguridad de gestores de paquetes de Python como uv, pip y conda. La velocidad está bien, pero la seguridad del package manager me parece mucho más importante

    • uv es más seguro que pip. Analiza dependencias sin ejecutar código arbitrario, verifica hashes de paquetes por defecto y evita varios riesgos como el typosquatting. También destaca en velocidad y reproducibilidad (presentación técnica, documentación de compatibilidad)
    • Pero en esencia todos los gestores de paquetes terminan descargando y ejecutando código de terceros no verificado. Más que las diferencias de seguridad entre las implementaciones de uv y pip, el verdadero riesgo importante es no tener una política definida frente al código externo
  • Como alguien que publica paquetes en PyPI, personalmente me gustaría usar uv por su rapidez, pero no puedo cambiar tan fácilmente si no hay garantía de que se comporte exactamente igual que pip. Si un usuario tiene un error con pip install xxx, yo también necesito reproducirlo y depurarlo en el mismo entorno

    • No funciona 100% igual que pip. Las diferencias importantes están documentadas en la documentación de compatibilidad. Algunas vienen del proceso de alinearse con el estándar, y otras son decisiones de diseño propias de uv
  • Creo que uv es uno de los cambios más positivos que ha habido recientemente en el empaquetado de Python: simplemente lo ejecutas y te da un buen resultado

  • También se comparte una excelente guía para usar uv en la construcción de contenedores de producción (ver guía)