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:

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:

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.

Suscríbete