Cuando tenemos un proyecto un poco más grande de un script, enseguida nos vamos a ver con la necesidad de usar un gestor de paquetes, ya que vamos a necesitar alguna librería externa.

Debido a que cada librería depende o puede depender de otras librerías, al final es probable que, si instalamos a mano cada una de ellas, nos encontremos conflictos entre ellas por requerir distintas versiones de una misma librería.

Para solucionar este problema, existen los gestores de paquetes. En Python, pip (package installer for Python) ha sido el usado por defecto históricamente.

Del mismo modo, cuando trabajamos en el entorno local de desarrollo, necesitamos poder aislar un proyecto de otro. Para ello tenemos venv, que nos permite crear entornos virtuales, ya que no queremos que una de nuestras aplicaciones rompa otra al instalar las dependencias.

Por último, cuando vamos a hacer una release, necesitamos fijar la versiones de las librerías, ya que no queremos que, unos días después de nuestra release, un cambio en una librería externa rompa nuestra aplicación y crear una “build” de nuestro desarrollo para poder distribuirlo.

¿Qué es UV?

Al igual que poetry, UV es una herramienta que nos va a permitir solucionar todos los problemas que hemos descrito anteriormente.

Pero UV pretende ser algo más, un “Cargo para Python”. Esto quiere decir que es un único binario que contiene múltiples herramientas. UV está escrito en Rust, un lenguaje de programación compilado que permite que la velocidad de ejecución sea muy superior a otras.

Compatibilidad y diferencias entre herramientas

Para las operaciones habituales de pip y pip-tools, en la mayoría de los casos, simplemente cambiar pip install por uv pip install funcionará sin problemas. Esto facilita enormemente la adopción en proyectos existentes. Sin embargo, UV no es un clon exacto de pip, y algunas de sus diferencias son intencionadas para ofrecer mejoras significativas, por ejemplo:

  • Configuración: UV no lee archivos de configuración específicos de pip (como pip.conf o PIP_INDEX_URL) para evitar heredar comportamientos o dependencias de formato que puedan cambiar. En su lugar, utiliza sus propias variables de entorno (como UV_INDEX_URL) y soporta la configuración en archivos uv.toml o en la sección [tool.uv.pip] de pyproject.toml.
  • Priorización de índices: para prevenir ataques de "confusión de dependencias" (donde un atacante podría publicar un paquete malicioso con el mismo nombre en PyPI que uno interno), UV prioriza por defecto los paquetes de índices internos sobre los de PyPI. UV ofrece la opción --index-strategy para ajustar esta prioridad si es necesario, aunque advierte sobre los riesgos de seguridad.
  • Entornos virtuales por defecto: a diferencia de pip, UV está diseñado para trabajar con entornos virtuales por defecto. uv pip install y uv pip sync siempre instalarán paquetes en el entorno virtual activo o buscarán uno llamado .venv en el directorio actual o en un directorio padre.
  • Compilación de bytecode: por defecto, UV no compila los archivos .py a .pyc durante la instalación (lo hace pip). La compilación se realiza de forma perezosa al importar un módulo. Sin embargo, puedes habilitarla con la bandera --compile-bytecode para optimizar los tiempos de inicio en aplicaciones CLI o contenedores Docker.

Os dejamos una comparativa entre distintas herramientas:

Característica pip + virtualenv pipenv Poetry pdm UV
Función principal Instalador de paquetes + Gestor de entornos (manual) Gestor de dependencias y entornos integrado Gestor de proyectos completo (dependencias, build, publish) Gestor de proyectos moderno Gestor unificado de proyectos y herramientas de alto rendimiento
Archivo de configuración requirements.txt Pipfile pyproject.toml pyproject.toml pyproject.toml
Archivo de bloqueo No por defecto (requiere pip freeze) Pipfile.lock (JSON) poetry.lock (TOML) pdm.lock (TOML) uv.lock (TOML)
Gestión de entornos Manual con virtualenv o venv Automática y centralizada Automática y por proyecto (configurable) Automática (venv o PEP 582 opcional) Automática y por proyecto
Resolución de dependencias Básica, secuencial Avanzada pero históricamente lenta Avanzada y robusta Avanzada y rápida Avanzada y ultrarrápida
Soporte para publicación No (requiere herramientas como twine) No Sí, integrado (poetry publish) Sí, integrado (pdm publish) Sí, integrado (UV publish)
Rendimiento Base Lento Moderado Rápido Excepcionalmente rápido
Madurez / soporte Máxima Alta pero estancada Muy alta Alta, en crecimiento Emergente, en rápido crecimiento
Adhesión a PEP 621 N/A No Si

¿Dónde destaca UV? No todo es la velocidad

UV brilla en varios aspectos, lo cual está haciendo que destaque dentro del ecosistema de herramientas de desarrollo en Python:

  • Rendimiento extremo: una de las características que primero destaca es su rendimiento, siendo entre 8-10 veces más rápido que pip y pip-tools sin caché, y 80-115 con caché. Esto significa que recrear un entorno virtual o añadir una dependencia a un proyecto existente es casi instantáneo (con tiempos muy por debajo del segundo).
  • Gestión integral y unificada: ofrece una API de proyecto completa que nos permite gestionar herramientas CLI (con uvx o uv tool run para ejecuciones puntuales), instala versiones de Python y hasta maneja scripts de un solo archivo con metadatos de dependencia embebidos (PEP-723).
  • Lockfiles multiplataforma: UV genera un archivo uv.lock, que es una "instantánea" exacta y auditable de las dependencias de tu proyecto. Lo más impresionante es que este lockfile es multiplataforma, lo que significa que el mismo archivo puede usarse para instalar el proyecto de manera consistente en cualquier sistema operativo (Linux, Windows, macOS), garantizando la reproducibilidad del entorno. También nos permite generar un pylock.toml standart.
  • Desarrollo fluido usando uv run: el comando uv run nos permite ejecutar scripts o comandos en el entorno del proyecto sin necesidad de activarlo explícitamente y, además, se asegura de que esté siempre actualizado con el lockfile y pyproject.toml antes de ejecutar el comando. Elimina la necesidad de hacer uv sync o uv lock manuales que, en muchas situaciones, provoca que no estemos ejecutando el entorno con todos los cambios más recientes.

Cómo iniciar un proyecto usando UV

Empezar un nuevo proyecto con UV es sencillo: utilizamos el comando uv init. Esto generará la estructura básica necesaria:

mkdir my-uv-proj
cd my-uv-proj
uv init

También podemos usar uv init my-uv-proj, que creará la carpeta por nosotros. Tras el init, se crearán los archivos esenciales como pyproject.toml, .python-version y un simple main.py.

Ahora vamos a añadir una dependencia usando uv add. En este punto veremos cómo se crean .venv y el fichero uv.lock el entorno virtual, y el fichero pyproject.toml se modifica añadiendo la dependencia.

Por último, para ejecutar el proyecto, usaremos uv run main.py.

Fichero de lock

UV usa un fichero propio de lock uv.lock para controlar las dependencias exactas instaladas con el fin de tener reproducibilidad. A diferencia de pyproject.toml, que declara las necesidades de tu proyecto con rangos de versiones, ej requests>=2.30 en el fichero uv.lock nos encontramos la versión exacta, así como los hashes para validar el contenido.

[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
    { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]

No obstante, el fichero uv.lock es un fichero propio de UV, pero también podemos generar el típico “requirements.txt” o el más moderno pylock.toml usando:

uv export --format requirements.txt
uv export --format pylock.toml

Es recomendable añadir el fichero de lock en el repositorio para garantizar la reproducibilidad en los distintos entornos en los que trabajemos.

Distribuir paquetes

En el caso de crear una librería, también podemos usar UV para crear el paquete a distribuir. Para eso necesitamos que, en el fichero de pyproject.toml, exista la sección [build-system], añadiendo a mano:

[build-system]
requires = ["uv_build>=0.7.13,<0.8"]
build-backend = "uv_build"

También podemos hacerlo ejecutando uv init --build-backend uv al iniciar el proyecto para que nos añada esos campos desde un inicio. Cuando ejecutamos uv build se creará una carpeta dist donde se alojarán los ficheros a distribuir.

Pero eso no publicará el empaquetado en ninguna parte. Para ello, debemos ejecutar uv publish, añadiendo un “index” en el pyproject.toml con el campo publish-url:

[[tool.uv.index]]
name = "pypi"
url = "https://pypi.org/simple/"
publish-url = "https://upload.pypi.org/legacy/"

Quería destacar en este punto el modo de inclusión de dependencias desde distintos repositorios, y comentar que tenemos la opción de descargar las dependencias desde diferentes orígenes según nos interese del siguiente modo:

[[tool.uv.index]]
name = "pytorch"
url = "https://download.pytorch.org/whl/cu121"
explicit = true

[tool.uv.sources]
torch = { index = "pytorch" }
httpx = { git = "https://github.com/encode/httpx", tag = "0.27.0" }
pytest = { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl" }
pydantic = { path = "/path/to/pydantic", editable = true }

Conclusiones

UV es un proyecto que pretende ser algo más que una herramienta de gestión de paquetes. Es un diseño unificado que permite tener todas las herramientas de desarrollo en una sola.

Si actualmente usas poetry, probablemente puedas sustituirlo por UV obteniendo una mejora en los tiempos. No obstante, ten en cuenta que UV aún es un proyecto en desarrollo y que habrá casos de uso que no estén aún cubiertos.

Te animo a que compruebes si puede ser útil en tu CI/CD mejorando los tiempos de ejecución.

Referencias

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.