uv en macOS: gestión de versiones de Python, proyectos y herramientas
Traducción automática
Este artículo se tradujo automáticamente a partir de la versión original en inglés.
Esta guía está dirigida a desarrolladores de Python en macOS que pasan de pip, venv, pip-tools, pyenv o pipx a uv. uv admite cuatro tipos de trabajo: un proyecto, un script de un solo archivo, una ejecución puntual de una CLI con uvx y una CLI instalada en PATH. Los cuatro pueden utilizar las versiones de Python que instala uv. Aprenderás los comandos de cada uno y cómo elegir entre ellos.
He pasado mi propio flujo de trabajo de Python a uv. Ahora un único ejecutable cubre el trabajo que antes repartía entre esas herramientas.
Inicio rápido
# Install uv with Homebrew
brew install uv
# Start a project
uv init --no-package example-app
cd example-app
uv add httpx
uv run python main.py
# Prints "Hello from example-app!"
# Run an isolated CLI without installing it permanently
uvx ruff check .
La figura muestra dónde guarda cada uno de los cuatro flujos de trabajo su entorno, qué conservas en cada caso y las instalaciones gestionadas de Python que comparten.
Instalar y actualizar uv
Homebrew es una forma cómoda de instalarlo 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 debe actualizarlo:
brew upgrade uv
uv self update es para el método de instalación independiente de uv y está desactivado en las instalaciones hechas con un gestor de paquetes. Actualiza uv con la misma herramienta que lo instaló.
Para ver dónde están uv, sus instalaciones de Python, sus herramientas y su caché:
command -v uv
uv python dir
uv tool dir
uv cache dir
Las instalaciones de Python gestionadas, las herramientas persistentes y las entradas de caché desechables tienen directorios distintos. La caché se puede borrar y reconstruir. No es una fuente de información definitiva. En macOS, uv clona los paquetes de la caché en cada entorno (copia al escribir) en lugar de copiarlos. Al reconstruir .venv, reutiliza los artefactos necesarios que aún estén en la caché.
Proyectos: declaraciones, resolución y entorno
Un proyecto de uv suele tener tres partes:
pyproject.tomldeclara los metadatos del proyecto y los requisitos directos.uv.lockguarda la resolución multiplataforma de uv..venves el entorno instalado localmente y debe ser desechable.
La figura clasifica los archivos del proyecto según quién los escribe y si git los conserva.
Crea una aplicación y añade los requisitos de ejecución y de pruebas:
uv init --no-package forecast-app
cd forecast-app
uv add httpx
uv add --group test pytest
uv run python main.py
uv run --group test pytest --version
La aplicación muestra Hello from forecast-app!; el último comando comprueba que pytest está instalado. Cuando tengas pruebas, ejecútalas con uv run --group test pytest.
La plantilla --no-package crea main.py, pyproject.toml, README.md y .python-version sin un sistema de compilación. Desde uv 0.12, uv init sin opciones crea una aplicación empaquetada con una estructura src y un backend de compilación. Utiliza uv init --lib para una biblioteca empaquetada. Consulta la guía de creación de proyectos de uv.
uv run comprueba el proyecto, actualiza el lock cuando hace falta, sincroniza las dependencias necesarias y ejecuta el comando. Consulta la documentación de uv sobre la estructura y la sincronización de proyectos para ver este ciclo de vida. La primera operación sobre el proyecto crea .venv y uv.lock según haga falta. uv init por sí solo crea los archivos del proyecto.
uv instala el grupo dev por defecto. Otros grupos, como test, necesitan --group en cada comando, o default-groups bajo [tool.uv]. Para instalar un solo grupo, por ejemplo en un job de CI de documentación, usa uv sync --only-group docs.
Los grupos son locales y nunca se publican. Un extra publicado es distinto: uv add --optional cli rich añade un extra que los usuarios instalan como forecast-app[cli].
Para usar el proyecto en notebooks de Jupyter en VS Code, ejecuta uv add --dev ipykernel y selecciona .venv como kernel. Consulta la guía de Jupyter de uv.
Versiona las entradas, no el entorno
Sube al repositorio pyproject.toml, uv.lock, el código fuente y un .python-version intencionado. Ignora .venv y las cachés de uv.
Un compañero que clona el repositorio obtiene el entorno bloqueado con un solo comando:
git clone https://github.com/example/forecast-app.git
cd forecast-app
uv sync --locked
Para ver por qué está instalado un paquete y para mover el lock a versiones más nuevas:
uv tree # show the dependency graph
uv tree --outdated # show newer versions where they exist
uv lock --upgrade-package httpx # move one package, keep the rest
uv lock --upgrade # re-resolve everything
Sube el uv.lock modificado junto con el cambio que lo necesitó.
El lock registra una resolución para todos los marcadores y plataformas compatibles. No hace que los wheels nativos sean idénticos en macOS, Linux, Intel y Apple Silicon. Prueba cada plataforma de despliegue.
uv puede imponer parte de esto. required-environments bajo [tool.uv] hace que la resolución falle cuando un paquete sin distribución de código fuente no tiene wheel para una plataforma de la lista:
[tool.uv]
required-environments = [
"sys_platform == 'darwin' and platform_machine == 'arm64'",
"sys_platform == 'linux' and platform_machine == 'x86_64'",
]
no-build-package prohíbe compilar desde el código fuente los paquetes indicados, así que un wheel que falta hace fallar la instalación en lugar de arrancar un compilador. Si también despliegas PyTorch en Linux con CUDA, sigue la guía de PyTorch de uv para configurar el índice.
Lock actualizado: --locked no es --frozen
Por defecto, los comandos de proyecto pueden actualizar uv.lock cuando cambian las declaraciones. Consulta la documentación de uv sobre bloqueo y sincronización para ver las opciones de actualización y de exactitud.
Usa --locked para exigir que el lock esté al día con 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. Úsalos en CI para que un lock desactualizado haga fallar el build.
Usa --frozen solo cuando quieras a propósito que uv use el lock existente sin comprobar si está al día:
uv sync --frozen
Puede ser útil en una fase de build controlada en la que el lock ya se validó, pero no detecta un lock desactualizado.
La figura sigue un proyecto desactualizado a través de cuatro comandos: rich se añadió a pyproject.toml a mano y uv.lock no se actualizó.
La exactitud es un ajuste distinto de la actualización. uv sync es exacto por defecto y elimina los paquetes sobrantes. uv run usa por defecto una sincronización no exacta salvo que se pida --exact. La figura sigue un paquete instalado a mano a través de uv sync, uv run y uv run --exact.
Instalar y fijar versiones de Python
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 gestionadas de CPython de uv proceden del proyecto python-build-standalone. uv también puede descubrir intérpretes del sistema, de Homebrew, de pyenv, de Conda y otros. Consulta la documentación de uv sobre versiones de Python para ver las fuentes compatibles y el comportamiento de descubrimiento.
.python-version es una petición de versión que descubren uv y otras herramientas compatibles. uv python pin escribe este archivo y uv init crea uno. 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"
uv toma la versión solicitada primero de --python, después de .python-version y por último de requires-python.
Fijar 3.12 no garantiza la misma build de parche para siempre ni el mismo artefacto en todos los sistemas operativos. Pide un parche exacto cuando lo necesites. En CI, selecciona el intérprete e imprímelo, con uv run --python 3.12.7 python -VV o definiendo UV_PYTHON=3.12.7 para el job.
Usa otro gestor de Python cuando un proyecto necesite una distribución o una configuración de build que uv no ofrece. uv puede seguir usando ese intérprete mediante --python o el descubrimiento normal.
Elegir entre uv run, uvx y herramientas instaladas
Elige según si la herramienta necesita los paquetes instalados del proyecto, debe permanecer en PATH o puede ejecutarse de forma independiente. La figura muestra qué dependencias están disponibles y cuánto dura cada entorno.
Herramientas ligadas al proyecto: uv run
Si pytest, mypy, un generador de código u otra herramienta debe importar el proyecto o usar sus plugins bloqueados, declárala en un grupo de dependencias:
uv add --group lint ruff
uv run --group lint ruff check .
uvx no instala automáticamente el paquete ni las dependencias del proyecto. Una herramienta aún puede importar archivos fuente locales si su forma de importar lo permite; utiliza uv run cuando necesite el proyecto instalado o sus dependencias.
Herramientas puntuales: uvx
uvx es un alias de uv tool run. Reutiliza una herramienta compatible instalada con uv tool install; en caso contrario, utiliza un entorno aislado almacenado en la caché desechable de uv. La documentación de herramientas de uv describe esta caché y el ciclo de vida de las herramientas persistentes:
uvx ruff@0.12.0 check .
uvx ruff@latest check .
uvx --from jupyterlab jupyter lab
uvx --from httpie http --version
uvx --with mkdocs-material mkdocs --help
Fija la versión de la herramienta en CI o en la documentación. Si no hay una herramienta instalada, la primera invocación sin versión elige la versión más reciente disponible; las siguientes pueden reutilizar el estado de la caché. @latest ignora una versión instalada y actualiza la caché para la versión más reciente. Utiliza --from cuando el nombre del paquete difiera del nombre del comando, como ocurre con jupyterlab y httpie.
Ejecutables persistentes: uv tool install
Instala una herramienta cuando scripts que no controlas necesitan su comando en PATH, o cuando el manifiesto de configuración de tu máquina debe gestionarla:
uv tool install ruff
uv tool list
uv tool upgrade ruff
uv tool upgrade --all
uv tool uninstall ruff
Si la instalación avisa de que el directorio de ejecutables no está en PATH, ejecuta uv tool update-shell y reinicia tu shell antes de utilizar el comando. Consulta la documentación de ejecutables de herramientas de uv.
Las herramientas persistentes también usan entornos aislados. No modifiques esos entornos a mano con pip.
Scripts de un solo archivo con PEP 723
PEP 723 define los metadatos inline de un script. Un runner compatible puede leer el bloque de comentarios y crear un entorno aislado. Consulta la guía de scripts de uv y la PEP 723 para ver el formato de los metadatos y el comportamiento del runner.
La figura marca el bloque de metadatos en fetch.py y muestra qué hacen uv run y python a secas con el archivo.
uv puede escribir el bloque de metadatos por ti:
uv init --script fetch.py --python 3.12
Un script terminado tiene este aspecto:
# /// 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
Un simple python fetch.py ignora los metadatos del comentario. Por eso el script depende de un runner compatible, aunque la sintaxis de Python siga siendo válida. uv es uno de esos runners. pipx run, Hatch y PDM también leen el bloque.
Para ejecutar el script como ./fetch.py, añade un shebang que llame a uv:
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["httpx"]
# ///
import httpx
chmod +x fetch.py
./fetch.py
La opción -S hace que env divida la línea en argumentos separados, de modo que --script llegue a uv.
Para un experimento rápido, pasa las dependencias en la línea de comandos en lugar de escribir metadatos. La segunda línea abre un REPL de Python con pandas disponible:
uv run --with httpx scratch.py
uv run --with pandas python
Para un script que deba reproducir una resolución más adelante, crea un lock junto a él:
uv lock --script fetch.py
uv run --script fetch.py
Esto escribe fetch.py.lock. Una marca de tiempo exclude-newer en el bloque de metadatos hace que uv ignore las versiones de paquetes publicadas después de esa fecha:
# /// script
# dependencies = ["httpx"]
# [tool.uv]
# exclude-newer = "2025-04-17T00:00:00Z"
# ///
Es más débil que un lock. No fija versiones exactas y no puede recuperar un archivo que el índice elimine más tarde.
Usa un proyecto en su lugar cuando varios archivos comparten dependencias, el código se puede importar como paquete, las pruebas necesitan el estado del proyecto o varios scripts deben evolucionar juntos.
Proyectos existentes con requirements.txt
Un archivo requirements.txt puede contener entradas sin fijar, versiones exactas, hashes, restricciones, índices, URL o una exportación de requisitos generada por un resolvedor. Su reproducibilidad depende de cómo se generó y de cómo se usa. El nombre del archivo por sí solo no dice nada.
Tienes dos opciones: mantener el formato de pip y ejecutarlo con uv pip o, para una aplicación propia, migrar a uv.lock.
Para un archivo completamente resuelto que incluya las dependencias directas y transitivas, utiliza la interfaz de uv compatible con pip sin migrar:
uv venv
uv pip sync requirements.txt
.venv/bin/python app.py
Ejecuta aquí el intérprete instalado directamente. Si existe un pyproject.toml, uv run puede fijar y sincronizar sus requisitos antes de la ejecución y sustituir los paquetes que acabas de instalar. Consulta la guía de scripts de uv.
uv pip sync hace que el entorno coincida con un archivo completamente resuelto. Si requirements.txt contiene solo requisitos directos, utiliza uv pip install -r requirements.txt para resolver e instalar sus dependencias. uv pip install -r añade paquetes. Consulta la guía de fijación de versiones compatible con pip de uv.
Para una aplicación propia, una migración por etapas puede ser útil:
uv init --bare
uv add -r requirements.in
uv add --group test -r requirements-test.in
uv lock
uv sync --locked
Ejecuta todo el proceso de pruebas y despliegue antes de borrar los archivos antiguos. Si un sistema posterior sigue esperando el formato de pip, genéralo a partir del lock de uv validado con el comando export 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 que compiten entre sí.
Caché e índices privados
La caché de uv acelera las instalaciones repetidas, pero sigue siendo desechable:
uv cache prune
uv cache clean
Usa preferentemente prune para la limpieza habitual. clean elimina todas las entradas de la caché y obliga a repetir descargas y compilaciones.
Un lockfile mejora la repetibilidad. No hace que las dependencias sean de confianza.
Para un índice privado, pon su nombre y su URL en pyproject.toml y deja las credenciales fuera del archivo:
[[tool.uv.index]]
name = "internal"
url = "https://pypi.example.com/simple"
Pasa las credenciales mediante UV_INDEX_INTERNAL_USERNAME y UV_INDEX_INTERNAL_PASSWORD, o mediante keyring. uv construye los nombres de las variables a partir del nombre del índice. Consulta la documentación de uv sobre autenticación de índices.
Comandos antiguos y sus equivalentes en uv
Si vienes de las herramientas antiguas, esta tabla relaciona cada comando conocido con el comando de uv que hace lo mismo.
| Tarea | Antes | uv |
|---|---|---|
| Instalar Python | pyenv install 3.12 | uv python install 3.12 |
| Proyecto y entorno nuevos | mkdir proj && cd proj && python -m venv .venv | uv init proj && cd proj && uv sync |
| Añadir un paquete | pip install pandas && pip freeze > requirements.txt | uv add pandas |
| Compilar versiones fijadas | pip-compile requirements.in | uv lock |
| Actualizar un paquete | pip-compile --upgrade-package httpx | uv lock --upgrade-package httpx |
| Ejecutar en el entorno | source .venv/bin/activate && python app.py | uv run app.py |
| Script con dependencias | un venv manual para un solo archivo | bloque PEP 723 + uv run |
| Ejecutar una herramienta una vez | pipx run black | uvx black |
| Instalar una herramienta | pipx install ruff | uv tool install ruff |
| Instalación reproducible en CI | pip install -r requirements.txt | uv sync --locked |
Lista de comprobación operativa
Para cada proyecto:
- Define
requires-pythony las dependencias directas enpyproject.toml. - Separa los extras publicados (
--optional) de los grupos de dependencias locales (--group). - Sube
uv.lockal repositorio. Ignora.venvy el estado de la caché. - Ejecuta las herramientas ligadas al proyecto a través del entorno del proyecto.
- Usa
uv lock --checko--lockeden CI. - Prueba cada sistema operativo y arquitectura de destino que cubre el lock.
- Exporta formatos de compatibilidad solo para consumidores posteriores concretos.
- Deja las credenciales de los índices fuera de los archivos versionados. Pásalas mediante variables de entorno o keyring.
Utilizo uv como herramienta de Python predeterminada en macOS porque un único ejecutable cubre estas tareas y mantiene separadas las dependencias del proyecto y de las herramientas.