02 - Gestión de Entornos y Dependencias
De npm a uv: entornos virtuales, pyproject.toml, lockfiles deterministas y el tooling moderno de Python.
1. El Problema que Node.js Resolvió (y Python Tardó en Resolver)
En Node.js, el aislamiento de dependencias es automático: node_modules vive en tu proyecto y package-lock.json garantiza builds reproducibles. En Python, históricamente teníamos:
- Dependencias globales que colisionaban entre proyectos
requirements.txtsin lockfile real (versiones flotantes)- Múltiples herramientas compitiendo (pip, pipenv, poetry, conda)
En 2024-2025, esto cambió radicalmente con uv.
2. uv: El npm de Python (Escrito en Rust)
uv es un gestor de paquetes y entornos virtuales creado por Astral (los mismos de Ruff). Es 10-100x más rápido que pip y unifica:
- Creación de entornos virtuales
- Instalación de dependencias
- Lockfiles deterministas
- Gestión de versiones de Python
Instalación
# macOS/Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# O con pipx (si ya tienes Python)
pipx install uv
Workflow Básico
# Node.js
npm init -y
npm install fastapi uvicorn
npm install -D pytest ruff
# Ejecutar
npx uvicorn main:app # Python con uv
uv init
uv add fastapi uvicorn
uv add --dev pytest ruff
# Ejecutar
uv run uvicorn main:app Estructura de Proyecto Generada
my-project/
├── .venv/ # Entorno virtual (equivalente a node_modules)
├── .python-version # Versión de Python pinneada
├── pyproject.toml # Equivalente a package.json
├── uv.lock # Lockfile determinista (como package-lock.json)
└── src/
└── my_project/
└── __init__.py
3. pyproject.toml: El package.json de Python
Este archivo centraliza toda la configuración del proyecto (PEP 621):
[project]
name = "mi-api"
version = "1.0.0"
description = "API Enterprise con FastAPI"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.109.0",
"uvicorn[standard]>=0.27.0",
"pydantic>=2.6.0",
"sqlalchemy>=2.0.25",
"asyncpg>=0.29.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.23.0",
"ruff>=0.2.0",
"mypy>=1.8.0",
]
[project.scripts]
# Equivalente a "scripts" en package.json
start = "uvicorn src.main:app --reload"
test = "pytest"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
# Configuración de Ruff (linter + formatter)
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "A", "C4", "PT"]
# Configuración de Pytest
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
# Configuración de Mypy
[tool.mypy]
python_version = "3.12"
strict = true
Comparativa de Campos
| package.json | pyproject.toml | Notas |
|---|---|---|
name | [project] name | Idéntico |
version | [project] version | Idéntico |
dependencies | [project] dependencies | Lista en vez de objeto |
devDependencies | [project.optional-dependencies] dev | Grupo “dev” |
scripts | [project.scripts] | Entry points ejecutables |
main | N/A | Python usa __init__.py |
type: "module" | N/A | Python siempre es “module” |
4. Entornos Virtuales: El Aislamiento Real
A diferencia de node_modules, los entornos virtuales de Python son directorios con binarios y symlinks:
.venv/
├── bin/
│ ├── python -> /usr/local/bin/python3.12 # Symlink al intérprete
│ ├── pip # pip aislado
│ ├── uvicorn # Binarios instalados
│ └── activate # Script de activación
├── lib/
│ └── python3.12/
│ └── site-packages/ # Dependencias instaladas
└── pyvenv.cfg # Configuración del venv
Activación (Opcional con uv)
# Método tradicional (sin uv)
source .venv/bin/activate
python -m uvicorn main:app
# Con uv (no necesitas activar)
uv run uvicorn main:app
uv run automáticamente:
- Detecta si existe
.venv - Lo crea si no existe
- Sincroniza dependencias con
uv.lock - Ejecuta el comando en el entorno correcto
5. Lockfiles: Reproducibilidad Garantizada
El Problema de requirements.txt
# requirements.txt tradicional - ¡PELIGROSO!
fastapi>=0.100.0 # ¿Qué versión exacta?
pydantic # ¿2.0? ¿2.6? ¿2.9?
Cada pip install puede instalar versiones diferentes, causando “works on my machine”.
La Solución: uv.lock
uv.lock es un lockfile completo que incluye:
- Versiones exactas de todas las dependencias (directas y transitivas)
- Hashes SHA256 para verificación de integridad
- Metadatos de resolución
# uv.lock (generado automáticamente)
[[package]]
name = "fastapi"
version = "0.109.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "starlette" },
]
[[package]]
name = "pydantic"
version = "2.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "...", hash = "sha256:..." }
Workflow de Lockfile
# Desarrollo: añadir dependencia y actualizar lock
uv add httpx
# → Modifica pyproject.toml
# → Actualiza uv.lock
# → Instala en .venv
# CI/CD: instalación reproducible
uv sync --frozen
# → Lee uv.lock exactamente
# → Falla si el lock está desactualizado
# Actualizar dependencias
uv lock --upgrade-package fastapi
# → Solo actualiza fastapi y sus dependencias
6. Gestión de Versiones de Python
uv también gestiona las versiones de Python (como nvm para Node):
# Listar versiones disponibles
uv python list
# Instalar una versión específica
uv python install 3.12
# Pinear versión para el proyecto
uv python pin 3.12
# → Crea .python-version con "3.12"
# uv usará automáticamente la versión correcta
uv run python --version # Python 3.12.x
.python-version
3.12
Este archivo (como .nvmrc) asegura que todos los developers usen la misma versión.
7. Workspaces (Monorepos)
Para proyectos enterprise con múltiples packages:
# pyproject.toml (raíz)
[tool.uv.workspace]
members = ["packages/*"]
[tool.uv.sources]
# Dependencias locales
shared-lib = { workspace = true }
monorepo/
├── pyproject.toml # Workspace root
├── uv.lock # Lock compartido
├── packages/
│ ├── api/
│ │ └── pyproject.toml # depends on shared-lib
│ ├── worker/
│ │ └── pyproject.toml # depends on shared-lib
│ └── shared-lib/
│ └── pyproject.toml
8. Migración desde pip/poetry
Desde requirements.txt
# Crear pyproject.toml desde requirements
uv init
uv add $(cat requirements.txt)
Desde Poetry
# uv puede leer poetry.lock
uv sync # Genera uv.lock desde poetry.lock
Desde Pipenv
# Exportar primero
pipenv requirements > requirements.txt
uv init
uv add -r requirements.txt
9. Integración con Docker
El workflow de Docker cambia significativamente con uv:
# Dockerfile optimizado con uv
FROM python:3.12-slim AS builder
# Instalar uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Copiar solo archivos de dependencias (layer caching)
WORKDIR /app
COPY pyproject.toml uv.lock ./
# Instalar dependencias (sin el código fuente)
RUN uv sync --frozen --no-dev --no-install-project
# Copiar código fuente
COPY src ./src
# Instalar el proyecto
RUN uv sync --frozen --no-dev
# Runtime stage
FROM python:3.12-slim
WORKDIR /app
# Copiar el venv completo
COPY --from=builder /app/.venv ./.venv
ENV PATH="/app/.venv/bin:$PATH"
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
Optimizaciones aplicadas:
- Layer caching: Dependencias se reinstalan solo si
pyproject.tomlouv.lockcambian - Multi-stage: Builder tiene uv, runtime solo tiene Python
- —frozen: Falla si el lockfile está desactualizado (seguridad en CI)
10. Tabla Comparativa: Ecosistema de Gestión
| Herramienta | Velocidad | Lockfile | Python Version Mgmt | Recomendación |
|---|---|---|---|---|
| uv | ⚡ 10-100x pip | ✅ uv.lock | ✅ Integrado | Usar siempre |
| pip | 🐌 Lento | ❌ | ❌ | Legacy |
| poetry | 🐢 Moderado | ✅ poetry.lock | ❌ | Migrar a uv |
| pipenv | 🐢 Lento | ✅ Pipfile.lock | ❌ | Obsoleto |
| conda | 🐢 Lento | ✅ | ✅ | Solo para Data Science |
11. Scripts y Comandos Comunes
# Inicializar proyecto
uv init my-project
cd my-project
# Añadir dependencias
uv add fastapi uvicorn[standard]
uv add --dev pytest ruff mypy
# Ejecutar comandos
uv run pytest
uv run ruff check .
uv run uvicorn main:app --reload
# Actualizar dependencias
uv lock --upgrade # Todas
uv lock --upgrade-package X # Solo X
# Limpiar y reinstalar
rm -rf .venv
uv sync
# Exportar para sistemas legacy
uv export > requirements.txt
Conclusión
El tooling de Python ha madurado dramáticamente. Con uv:
- Velocidad: Instalaciones 10-100x más rápidas que pip
- Reproducibilidad: Lockfiles deterministas como en Node.js
- Simplicidad: Una herramienta para todo (venv, deps, Python versions)
- Compatibilidad: Lee
requirements.txt,poetry.lock, etc.
Tu workflow diario será:
uv add <package> # Añadir dependencia
uv run <command> # Ejecutar en el venv
uv sync --frozen # CI/CD reproducible
En el siguiente capítulo, profundizaremos en el sistema de tipos de Python: Type Hints, Pydantic V2, Generics y Protocols.