Saltar a contenido

ADR-018 · Python project standard (uv lockfile + pyproject + pre-commit + mypy gradual)

Fecha: 2026-04-29 Source: /srv/projects/cis/cis-plan/DECISIONS.md (do not edit here — re-split desde la fuente)


Contexto

El audit de estándares (agent-c, scan acdf956053dd737dd, 2026-04-29) reportó 60% de déficit en disciplina Python en el ecosistema CIS. Solo periodismo2-www tiene .pre-commit-config.yaml operativo. Cero servicios tienen requirements.lock reproducible. cis-core y cis-auth (T0 críticos) no tienen CI ni tests automáticos. El bug detectado en P0.2 (except ImportError: pass silencioso en cis-core/app/integrations/__init__.py que hizo que MCP tools se cayeran sin alertar durante 8 días) habría sido capturado por un linter con la regla tryceratops/TRY activa.

El plan maestro (Bloque P0.4) impone llevar a verde 8 servicios live (cis-admin, cis-core, cis-platform, cis-inbox, cis-mailer, cis-pagos, cis-monitoreo, cis-sii-gateway) con un estándar único. Sin un canon escrito, cada servicio reinventa su pyproject.toml, su .pre-commit-config.yaml y su política de lockfile (o no la tiene).

Alternativas consideradas

  1. (a) Cada servicio define su propio estándar
  2. Pro: cero coordinación, máxima flexibilidad por proyecto.
  3. Contra: estado actual; produce déficit medido. Cuesta repetir el mismo bootstrap en cada repo nuevo. Bugs detectables se escapan.

  4. (b) Template canónico en core/core-python-base/ + adopción gradualpropuesta

  5. Pro: un solo lugar fuente de verdad. Servicios nuevos arrancan ya verdes; existentes migran cuando tocan el archivo relevante. Permite parametrizar por servicio (coverage threshold, mypy strict on/off).
  6. Contra: requiere disciplina de mantener el template actualizado. Sin enforcement por CI puede quedar como sugerencia.

  7. (c) Mega-pyproject monorepo único

  8. Pro: garantiza consistencia.
  9. Contra: contradice ADR-007 (repos separados). Imposible con la realidad de 24 repos distribuidos. El template logra el mismo objetivo sin romper la división.

Decisión

El estándar Python canónico vive en /srv/projects/core/core-python-base/ con dos artefactos:

  • pyproject.toml.template — secciones [tool.ruff], [tool.black], [tool.pytest.ini_options], [tool.coverage.run], [tool.coverage.report], [tool.mypy] listas para mergear en cada pyproject.toml proyecto.
  • .pre-commit-config.yaml — hooks ruff, ruff-format, trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files, mypy (en proyectos con strict mode).

Reglas vinculantes:

  • Python 3.12 explícito en servicios nuevos. Servicios existentes pueden quedarse en 3.10/3.11 hasta su próximo refactor mayor.
  • Lockfile reproducible: uv pip compile requirements.txt -o requirements.lock. El .lock se commitea; deploy en prod instala desde el lock, no desde el .txt.
  • Line length 88 (black/ruff compatible). isort dentro de ruff (no instalar isort separado).
  • Coverage mínimo 60% target. Servicios que arrancan <60% configuran threshold inicial menor + ticket explícito para subirlo. T0 (cis-core, cis-auth, cis-platform, cis-mailer) bloqueante en CI.
  • Mypy gradual: strict = false para servicios existentes (ruido manejable); strict = true por default en servicios nuevos.
  • Structlog JSON para logs (formaliza ADR-019).
  • Slowapi + prometheus_client para rate limiting + métricas en endpoints HTTP.
  • Pre-commit obligatorio en cada repo: pre-commit install parte del onboarding del repo. CI corre pre-commit run --all-files.

El proyecto-modelo es periodismo2-api/pyproject.toml (único con coverage operativo y ruff+pytest+mypy en CI). El template de core-python-base es la generalización de su configuración.

Consecuencias

  • Positivo: bugs como el de P0.2 (except ImportError: pass) los detecta ruff TRY004/TRY203 en pre-commit. Costo de adoptar nuevo proyecto baja a ~30 min (copiar 2 archivos + uv pip compile). Estándar único reduce ruido de PRs ("formato? lint? coverage?") a una sola fuente.
  • Negativo: 8 servicios live deben adoptar el estándar (P0.4 estima ~3-4h por servicio para cubrirlos todos). El ruido inicial de ruff --fix en repos viejos puede generar diff masivo en un commit (estrategia: 1 commit "auto-format" sin lógica + commits siguientes con cambios reales).
  • Riesgo: si core-python-base/pyproject.toml.template desactualiza, los repos nuevos divergen. Mitigación: hook regenerate-ecosystem.sh (ADR-016) chequea consistencia.
  • Migración: P0.4 entrega los 8 servicios verdes. Servicios T3 (periodismo2, situacion, usaia, isometrico, librospa) migran cuando se toque el repo. core-auth-lib y core-db-base (libs compartidas) son prioritarias porque cualquier cliente las hereda — adoptan en paralelo a P0.4.
  • Compatibilidad: el template está pensado para mergear con pyproject.toml existentes sin clobberar [project] ni [project.dependencies]. Servicios pueden override secciones si justifican (ej. cis-isometrico requiere line-length 100 por meshes; queda como excepción documentada en su CLAUDE.md).
  • No es lib instalable: core-python-base es solo templates. La separación de responsabilidades respecto a core/py-common (lib instalable, ADR-016) es deliberada — código compartido vs convenciones compartidas.