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
- (a) Cada servicio define su propio estándar
- Pro: cero coordinación, máxima flexibilidad por proyecto.
-
Contra: estado actual; produce déficit medido. Cuesta repetir el mismo bootstrap en cada repo nuevo. Bugs detectables se escapan.
-
(b) Template canónico en
core/core-python-base/+ adopción gradual ← propuesta - 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).
-
Contra: requiere disciplina de mantener el template actualizado. Sin enforcement por CI puede quedar como sugerencia.
-
(c) Mega-pyproject monorepo único
- Pro: garantiza consistencia.
- 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 cadapyproject.tomlproyecto..pre-commit-config.yaml— hooksruff,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.lockse 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 = falsepara servicios existentes (ruido manejable);strict = truepor 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 installparte del onboarding del repo. CI correpre-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 detectaruff TRY004/TRY203en 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 --fixen 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.templatedesactualiza, los repos nuevos divergen. Mitigación: hookregenerate-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-libycore-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.tomlexistentes sin clobberar[project]ni[project.dependencies]. Servicios pueden override secciones si justifican (ej.cis-isometricorequiere line-length 100 por meshes; queda como excepción documentada en su CLAUDE.md). - No es lib instalable:
core-python-basees solo templates. La separación de responsabilidades respecto acore/py-common(lib instalable, ADR-016) es deliberada — código compartido vs convenciones compartidas.