uv en macOS: gestión de versiones, proyectos y herramientas de Python

Traducción automática Este artículo se tradujo automáticamente a partir de la versión original en inglés.

He cambiado mi flujo de trabajo de Python a uv. Ha sustituido el cambio de herramientas que antes hacía entre pip, entornos virtuales, pip-tools, pipx y los gestores de proyectos. Ahora un único ejecutable cubre la mayor parte de ese trabajo.

Los flujos siguen siendo diferentes. Para los desarrolladores de Python que pasan de pip, los entornos virtuales, pip-tools o pipx a uv en macOS, un proyecto, un script con metadatos inline, una CLI puntual y una CLI instalada pertenecen a entornos y ciclos de vida distintos. Los comandos siguientes muestran cómo elegir entre ellos.

Inicio rápido

# Install uv with Homebrew
brew install uv

# Start a project
uv init example-app
cd example-app
uv add httpx
uv run python main.py

# Run an isolated CLI without installing it permanently
uvx ruff check .

TL;DR. Dentro de los proyectos uso uv add, uv lock, uv sync y uv run. Para scripts autocontenidos uso metadatos PEP 723; para herramientas puntuales, uvx; y para comandos que deben permanecer en PATH, uv tool install. En CI, --locked verifica que los metadatos del proyecto coinciden con uv.lock. --frozen confía en el lock existente sin comprobar si está actualizado.

Instala uv con un único responsable

Homebrew es una opción cómoda para instalar en macOS. Consulta la guía de instalación de uv para ver los métodos compatibles:

brew install uv
uv --version

Si Homebrew ha instalado uv, Homebrew debería actualizarlo:

brew upgrade uv

uv self update se utiliza con el método de instalación independiente de uv y está desactivado para las instalaciones mediante gestores de paquetes. No permitas que dos instaladores compitan por el mismo ejecutable.

Comandos útiles para identificar la instalación:

command -v uv
uv python dir
uv tool dir
uv cache dir

Las instalaciones de Python gestionadas, las herramientas persistentes y las entradas desechables de la caché utilizan directorios distintos. La caché se puede eliminar y reconstruir. No es una fuente de verdad.

Los cuatro límites del flujo de trabajo de uvLos cuatro límites del flujo de trabajo de uv

Proyectos: declaraciones, resolución y entorno

Un proyecto de uv normalmente tiene tres artefactos:

  • pyproject.toml declara los metadatos del proyecto y sus requisitos directos.
  • uv.lock almacena la resolución multiplataforma de uv.
  • .venv es el entorno instalado localmente y debería ser desechable.

Entradas y estado derivado en un proyecto de uvEntradas y estado derivado en un proyecto de uv

Crea una aplicación y añade los requisitos de ejecución y de pruebas:

uv init forecast-app
cd forecast-app

uv add httpx
uv add --group test pytest
uv run python main.py
uv run --group test pytest

Las plantillas actuales de aplicaciones de uv crean main.py, pyproject.toml, README.md y .python-version. De forma predeterminada, no definen un sistema de build. Usa uv init --lib para una biblioteca empaquetada con una estructura src y un backend de build.

uv run comprueba el proyecto, actualiza el lock cuando es necesario, sincroniza las dependencias requeridas y ejecuta el comando. Consulta la documentación de uv sobre la estructura de proyectos y la sincronización para conocer este ciclo de vida. La primera operación sobre el proyecto crea .venv y uv.lock cuando es necesario. uv init solo crea los archivos del proyecto.

Haz commit de las entradas, no del entorno

Haz commit de pyproject.toml, uv.lock, el código fuente y un .python-version intencionado. Ignora .venv y las cachés de uv.

El lock registra una resolución para los markers y las plataformas compatibles. No hace que las wheels nativas sean idénticas en macOS, Linux, Intel y Apple Silicon. Prueba cada plataforma de despliegue.

Actualización del lock: --locked no es --frozen

!!! byte «Byte dice»

Usaba `--frozen` en CI para omitir la resolución. También omitía la comprobación de divergencias. El lock quedó desactualizado respecto a `pyproject.toml` durante un mes y no se encendió ninguna alerta.

De forma predeterminada, los comandos del proyecto pueden actualizar uv.lock cuando cambian las declaraciones. Consulta la documentación de uv sobre locking y sincronización para conocer los flags de actualización y exactitud.

Usa --locked para exigir que el lock esté actualizado respecto a los metadatos del proyecto:

uv lock --check
uv sync --locked --group test
uv run --locked --group test pytest

Si pyproject.toml y uv.lock no coinciden, estos comandos fallan en lugar de resolver un lock nuevo. Ese fallo suele ser el gate de CI que necesitas.

Usa --frozen únicamente cuando quieras deliberadamente que uv utilice el lock existente sin comprobar si está actualizado:

uv sync --frozen

Puede ser útil en una fase de build controlada en la que el lock ya se ha validado, pero no sirve para detectar locks obsoletos.

La exactitud es un ajuste independiente de la actualización. uv sync es exacto de forma predeterminada y elimina los paquetes adicionales. uv run utiliza por defecto una sincronización inexacta, salvo que se solicite --exact.

Python gestionado: una solicitud de versión, no un binario universal

uv puede descargar y gestionar distribuciones de Python:

uv python install 3.12 3.13
uv python list --only-installed
uv python pin 3.12

Las builds de CPython gestionadas por uv proceden del proyecto python-build-standalone. uv también puede descubrir intérpretes del sistema, de Homebrew, pyenv, Conda y otros. Consulta la documentación de uv sobre versiones de Python para conocer las fuentes compatibles y el comportamiento de descubrimiento.

.python-version es una solicitud de versión que uv y otras herramientas compatibles pueden descubrir. requires-python en pyproject.toml es el contrato de compatibilidad del proyecto. Mantén ambos de forma intencionada:

[project]
requires-python = ">=3.12,<3.14"

Fijar 3.12 no garantiza para siempre la misma build de patch ni el mismo artefacto en todos los sistemas operativos. Solicita un patch exacto cuando lo necesites y haz que CI seleccione e informe explícitamente del intérprete.

Usa otro gestor de Python cuando un proyecto necesite una distribución o configuración de build que uv no proporcione. uv puede seguir utilizando ese intérprete mediante --python o mediante el descubrimiento normal.

Elige entre uv run, uvx y las herramientas instaladas

Cómo elegir entre un entorno de proyecto, una herramienta efímera o una persistenteCómo elegir entre un entorno de proyecto, una herramienta efímera o una persistente

Herramientas ligadas al proyecto: uv run

Si pytest, mypy, un generador de código u otra herramienta debe importar el proyecto o utilizar sus plugins bloqueados, declárala en un grupo de dependencias:

uv add --group lint ruff
uv run --group lint ruff check .

Ejecutar esa herramienta mediante uvx la aislaría del proyecto y podría ocultar el paquete instalado o los plugins que necesita.

Herramientas ad hoc: uvx

uvx es un alias de uv tool run. Crea un entorno aislado almacenado en la caché desechable de uv. La documentación de uv sobre tools describe esta caché y el ciclo de vida de las herramientas persistentes:

uvx ruff@0.12.0 check .
uvx --from jupyterlab jupyter lab
uvx --with mkdocs-material mkdocs --help

Fija la versión de la herramienta en CI o en la documentación. La primera invocación sin versión selecciona una release actual y las invocaciones posteriores pueden reutilizar el estado de la caché.

Ejecutables persistentes: uv tool install

Instala una herramienta cuando los scripts que no controlas necesiten su comando en PATH, o cuando el manifiesto de configuración de tu máquina deba ser su responsable:

uv tool install ruff
uv tool list
uv tool upgrade ruff
uv tool uninstall ruff

Las herramientas persistentes siguen utilizando entornos aislados. No modifiques manualmente esos entornos con pip.

Scripts: convierte un único archivo en la unidad de release

PEP 723 define metadatos inline para scripts. Un runner compatible puede leer el bloque de comentarios y crear un entorno aislado. Consulta la guía de uv sobre scripts y PEP 723 para conocer el formato de los metadatos y el comportamiento del runner.

Anatomía de un script PEP 723Anatomía de un script PEP 723

# /// script
# requires-python = ">=3.12"
# dependencies = [
#   "httpx>=0.27,<1",
# ]
# ///
import httpx

response = httpx.get("https://example.com", timeout=10)
response.raise_for_status()
print(response.status_code)

Ejecuta el script y edita sus metadatos con uv:

uv run fetch.py
uv add --script fetch.py rich
uv remove --script fetch.py rich

El python fetch.py normal ignora los metadatos de los comentarios. Por tanto, el script depende de un runner compatible aunque la sintaxis de Python siga siendo válida.

Para un script que deba reproducir una resolución posteriormente, crea un lock adyacente:

uv lock --script fetch.py
uv run --script fetch.py

Esto escribe fetch.py.lock. Una marca temporal exclude-newer puede restringir las fechas de las distribuciones candidatas, pero es más débil que una resolución exacta bloqueada y no garantiza que un artefacto siga disponible.

Usa un proyecto cuando varios archivos compartan dependencias, el código sea importable como paquete, las pruebas necesiten el estado del proyecto o varios scripts deban avanzar juntos.

Archivos de requisitos: compatibilidad, no garantía de fallo

Un archivo requirements.txt puede contener entradas abiertas, pins exactos, hashes, constraints, índices, URL o una exportación de requisitos generada por un resolver. Su reproducibilidad depende de cómo se haya producido y consumido. El nombre del archivo, por sí solo, no dice nada.

Usa la interfaz compatible con pip de uv sin migrar:

uv venv
uv pip sync requirements.txt
uv run python app.py

uv pip sync hace que el entorno coincida con el archivo. uv pip install -r es aditivo.

Para una aplicación bajo tu control, puede ser útil una migración por fases:

uv init --bare
uv add -r requirements.in
uv add --group test -r requirements-test.in
uv lock
uv sync --locked

Ejecuta el flujo completo de pruebas y despliegue antes de eliminar los archivos antiguos. Si un sistema downstream sigue esperando el formato de pip, derívalo del lock de uv validado con el comando de exportación de uv:

uv export --format requirements.txt \
  --output-file requirements.txt

No mantengas uv.lock y una exportación editada a mano como dos resoluciones en competencia.

Caché, índices y límites de la supply chain

La caché de uv acelera las instalaciones repetidas, pero sigue siendo desechable:

uv cache prune
uv cache clean

Prefiere prune para la limpieza habitual. clean elimina todas las entradas de la caché y obliga a volver a descargar y compilar posteriormente.

Un lockfile mejora la repetibilidad. No convierte las dependencias en fiables. Revisa las fuentes de los paquetes, la configuración de los índices, las revisiones de Git, los backends de build, las licencias y las credenciales. Mantén la configuración de índices autenticados fuera de los archivos versionados. La excepción es una referencia no secreta a un mecanismo de credenciales aprobado.

Para los paquetes nativos, registra la arquitectura de despliegue y comprueba la disponibilidad de las wheels. De lo contrario, un resolver podría recurrir a una build desde el código fuente que necesite compiladores y bibliotecas del sistema ausentes en CI o producción.

Una checklist operativa compacta

Para cada proyecto:

  1. Define requires-python y las dependencias directas en pyproject.toml.
  2. Separa los extras publicados de los grupos de dependencias locales.
  3. Haz commit de uv.lock. Ignora .venv y el estado de la caché.
  4. Ejecuta las herramientas ligadas al proyecto mediante el entorno del proyecto.
  5. Usa uv lock --check o --locked en CI.
  6. Prueba cada sistema operativo y arquitectura de destino representados por el lock.
  7. Exporta formatos de compatibilidad únicamente para consumidores downstream identificados.
  8. Revisa la política de fuentes y credenciales de forma independiente de la resolución.

uv resulta especialmente útil cuando estos límites de responsabilidad siguen siendo visibles. Un único binario puede gestionarlos todos sin convertirlos en un único entorno. Menos herramientas, y los cuatro flujos siguen separados. Por eso sigo utilizándolo como mi herramienta de proyectos de Python predeterminada en macOS.

Referencias