IA Ingeniería MLOps · CULTIVA IA

Python Moderno con uv · ruff · ty

Plan completo de migración para DataFlow Analytics — de legacy pip/flake8/mypy al stack Astral moderno.

📦 Proyecto: dataflow-pipeline
🐍 Target: Python 3.11+
👥 Equipo: 5 ingenieros
📋 Tipo: paquete distribuible (PyPI interno)
Estimado: 3-4 horas
Impacto esperado post-migración
4m 30s
~1m 45s
CI Pipeline
−61%
45s (mypy)
~8s
Type Check
−82%
12s (pre-commit)
~2s
Pre-commit Hook
−83%
62 entradas
1 fichero
Config unificada
pyproject.toml
Mapa de reemplazo de herramientas
LEGACY (eliminar)
MODERNO (instalar)
pip + pip-tools
uv  (gestor todo-en-uno)
virtualenv / .venv manual
uv run <cmd>
flake8 + black + isort
ruff (lint + format)
mypy (45s en CI)
ty  (~8s, Astral)
setup.py + setuptools
uv_build backend
requirements.txt
pyproject.toml + uv.lock
pre-commit (Python)
prek  (Rust-native)
[project.optional-dependencies]
[dependency-groups] PEP 735
pyproject.toml final — dataflow-pipeline
TOML · pyproject.toml
# ──────────────────────────────────────────────────────────────────────
# dataflow-pipeline · pyproject.toml (generado con uv init --package)
# ──────────────────────────────────────────────────────────────────────

[project]
name             = "dataflow-pipeline"
version          = "2.0.0"
description      = "SaaS B2B predictive analytics pipeline for e-commerce"
requires-python  = ">=3.11"
readme           = "README.md"
license          = "UNLICENSED"
authors          = [{name = "DataFlow Engineering", email = "eng@dataflow.io"}]

dependencies = [
    "pandas>=2.1",
    "polars>=0.20",
    "duckdb>=0.10",
    "fastapi>=0.110",
    "uvicorn[standard]>=0.28",
    "openai>=1.30",
    "anthropic>=0.25",
]

[build-system]
requires      = ["uv_build"]   # reemplaza hatchling/setuptools
build-backend = "uv_build"

# ── Grupos de dependencias (PEP 735) — NUNCA usar optional-dependencies ──
[dependency-groups]
dev   = [{include-group = "lint"}, {include-group = "test"}, {include-group = "audit"}]
lint  = ["ruff", "ty"]
test  = ["pytest>=8", "pytest-cov", "hypothesis", "httpx"]  # httpx para TestClient
audit = ["pip-audit"]

# ── Ruff: reemplaza flake8 + black + isort ──────────────────────────────
[tool.ruff]
line-length     = 100
target-version  = "py311"

[tool.ruff.lint]
select = ["ALL"]
ignore = [
    "D",      # docstrings opcionales por ahora
    "COM812", # trailing comma (conflicto formatter)
    "ISC001", # implicit string concat (conflicto formatter)
    "ANN",    # anotaciones — ty las gestiona
]

[tool.ruff.lint.isort]
known-first-party = ["dataflow_pipeline"]

# ── ty: reemplaza mypy ──────────────────────────────────────────────────
[tool.ty.environment]
python-version = "3.11"    # CORRECTO: bajo [tool.ty.environment], no [tool.ty]

[tool.ty.terminal]
error-on-warning = true

[tool.ty.rules]
possibly-unresolved-reference = "error"
unused-ignore-comment         = "warn"

# ── pytest ─────────────────────────────────────────────────────────────
[tool.pytest.ini_options]
testpaths      = ["tests"]
addopts        = [
    "--cov=dataflow_pipeline",
    "--cov-report=term-missing",
    "--cov-fail-under=80",
]
filterwarnings = ["error"]   # warnings como errores en tests

# ── Coverage ───────────────────────────────────────────────────────────
[tool.coverage.run]
branch           = true
source           = ["src/dataflow_pipeline"]

[tool.coverage.report]
exclude_lines = ["pragma: no cover", "if TYPE_CHECKING", "\\.\\.\\."]
Comandos de migración (paso a paso)
BASH · terminal
# 1. Instalar uv (una vez por máquina)
$ curl -LsSf https://astral.sh/uv/install.sh | sh

# 2. Inicializar uv en el proyecto existente
$ uv init --bare    # no sobreescribe src/

# 3. Migrar dependencias desde requirements.txt
$ grep -v '^#' requirements.txt | while read pkg; do
    uv add "$pkg" || echo "⚠ Failed: $pkg"
  done
$ grep -v '^#' requirements-dev.txt | while read pkg; do
    uv add --group dev "$pkg"
  done

# 4. Eliminar herramientas legacy
$ uv remove flake8 black isort mypy

# 5. Añadir stack moderno
$ uv add --group lint ruff ty
$ uv add --group audit pip-audit

# 6. Sincronizar todo el entorno
$ uv sync --all-groups

# 7. Primer lint (con autofix)
$ uv run ruff check --fix .
$ uv run ruff format .

# 8. Type check inicial
$ uv run ty check src/

# 9. Tests con cobertura
$ uv run pytest

# 10. Auditoría de seguridad
$ uv run pip-audit

# 11. Limpiar archivos legacy
$ rm -f requirements.txt requirements-dev.txt setup.py
$ rm -rf .flake8 mypy.ini .pre-commit-config.yaml
$ git add uv.lock pyproject.toml
$ git commit -m "chore: migrate to uv+ruff+ty stack"
Makefile actualizado
MAKEFILE
.PHONY: dev lint format test build audit

dev:  ## Instalar todas las deps
	uv sync --all-groups

lint: ## Lint + format check + tipos
	uv run ruff format --check .
	uv run ruff check .
	uv run ty check src/

format: ## Auto-formatear código
	uv run ruff format .
	uv run ruff check --fix .

test: ## Tests + cobertura
	uv run pytest

audit: ## Seguridad de dependencias
	uv run pip-audit

build: ## Build distributable
	uv build

ci: lint test audit ## Todo junto (CI)
⚠ Anti-patterns críticos
🚫
[tool.ty] python-version está MAL.
Usar: [tool.ty.environment] python-version
🚫
NUNCA uv pip install. Usar uv add para que registre en pyproject.toml y uv.lock.
🚫
NUNCA source .venv/bin/activate. Usar uv run <cmd> siempre.
uv.lock siempre a version control. Garantiza reproducibilidad exacta en CI y entre devs.
✅ Checklist de migración — DataFlow
Instalar uv en todas las máquinas del equipo
Ejecutar uv init --bare en el repo
Migrar deps de requirements.txt → uv add
Eliminar flake8, black, isort, mypy
Añadir ruff + ty como grupo lint
Configurar pyproject.toml con ruff ALL + ty strict
Activar pytest-cov con threshold 80%
Resolver errores de ruff check (estimado: ~2h primera vez)
Resolver errores de ty check (módulos críticos primero)
Instalar prek como reemplazo de pre-commit
Configurar Dependabot para uv.lock
Actualizar CI/CD (GitHub Actions) con uv sync
Borrar .flake8, mypy.ini, requirements*.txt, setup.py
Actualizar documentación interna del equipo
GitHub Actions CI actualizado
YAML · .github/workflows/ci.yml
name: CI
on: [push, pull_request]

jobs:
  lint-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v3
        with:
          enable-cache: true   # uv.lock caching
      - name: Sync dependencies
        run: uv sync --all-groups
      - name: Lint
        run: |
          uv run ruff format --check .
          uv run ruff check .
          uv run ty check src/
      - name: Test
        run: uv run pytest
      - name: Audit
        run: uv run pip-audit
📄 PEP 723 — scripts standalone
Para scripts de migración de datos ad-hoc (sin pyproject.toml):
PYTHON · migrate_events.py
# /// script
# requires-python = ">=3.11"
# dependencies = [
#   "polars>=0.20",
#   "duckdb>=0.10",
#   "rich>=13",
# ]
# ///
import polars as pl
from rich.progress import track

# Ejecutar con: uv run migrate_events.py
# uv resuelve las deps automáticamente
Referencia rápida — comandos uv más usados
Comando Descripción Equivalente legacy
uv add pandas Añadir dependencia de producción pip install pandas + editar requirements.txt
uv add --group dev ruff Añadir a grupo de desarrollo pip install ruff + requirements-dev.txt
uv remove mypy Eliminar paquete pip uninstall + editar requirements
uv sync --all-groups Instalar todas las dependencias pip install -r requirements.txt -r requirements-dev.txt
uv run pytest Ejecutar en el entorno gestionado source .venv/bin/activate && pytest
uv run --with httpx python -m ... Dep temporal sin instalar pip install httpx && python -m ... && pip uninstall httpx
uv build Construir wheel/sdist python setup.py bdist_wheel
uv publish Publicar a PyPI (o registro interno) twine upload dist/*
Herramientas del stack — qué hace cada una
📦
uv
Astral · gestor universal
Reemplaza: pip, virtualenv, pip-tools, pyenv, pipx

Escrito en Rust, 10-100× más rápido que pip. Gestiona versiones de Python, entornos virtuales, dependencias y publicación en un solo binario.
Python mgmt Deps lock Scripts PEP 723
ruff
Astral · linter + formatter
Reemplaza: flake8, black, isort, pyupgrade, pydocstyle

100× más rápido que flake8. +800 reglas de lint, formatter compatible con Black, ordenación de imports. Una sola herramienta, cero conflictos de configuración.
Linting Formatting 800+ reglas
🔬
ty
Astral · type checker
Reemplaza: mypy, pyright

Nuevo type checker de Astral (mismo equipo que ruff/uv). Drásticamente más rápido que mypy (45s → 8s en DataFlow). API compatible, migración simple con uv remove mypy && uv add ty.
Type safety −82% tiempo Strict mode