Este documento recoge las reglas del
Google Python Style Guide
que ruff no puede imponer automáticamente, además de las
invariantes arquitectónicas específicas de esta plantilla.
Lo que sí se automatiza está en pyproject.toml (ruff) y en tach.toml
(boundaries). Lo que sigue debe respetarse manualmente y revisarse en
code review.
- Line length: 88 (Google = 80). Adoptamos el default de Black/ruff por alineamiento con el ecosistema moderno.
- Indent: 4 espacios (Google permite 2 o 4). PEP 8 y default de toda la herramienta Python.
Estas reglas las fuerza tach check. No deben relajarse:
app.domainnunca importa deapp.adaptersniapp.entrypoint.app.domain.modelno importa de ninguna otra capa (ni siquiera del kernelapp.domain).app.domain.usecasedepende solo deapp.domain.modely del kernelapp.domain; nunca al reves (modelno conoceusecase).app.adaptersno importa deapp.entrypoint.- No se permiten dependencias circulares entre módulos.
- Una capa nueva de primer nivel bajo
src/app/debe declararse entach.tomlcon susdepends_onexplícitos. Las features nuevas dentro demodel/ousecase/no requieren tocartach.toml.
Reglas adicionales no automatizadas:
- El gateway (puerto) vive junto a su modelo en
domain/model/<feature>/gateways.py, no en un módulo de puertos aparte. - Los use cases son puros: reciben la solicitud y retornan un
resultado de dominio. No entregan IO (no llaman al puerto). La
entrega ocurre en
entrypoint/. - Los errores específicos de una feature viven junto a su use case
(
domain/usecase/<feature>/errors.py) y heredan deapp.domain.errors.DomainError(kernel compartido). - El composition root es único:
wiring.build_dependencieses el único lugar donde se instancian adapters; se invoca una sola vez en el callback raíz deentrypoint/cli.pyy se inyecta víactx.obj. Los comandos no construyen dependencias. - Las dependencias externas (typer, requests, sqlalchemy, etc.) viven
solo en
adapters/oentrypoint/, nunca endomain/. - Los fakes/mocks de test viven en
tests/conftest.pyo en archivos bajotests/, nunca ensrc/. - El mapeo de excepciones de dominio al canal de salida (códigos de
salida, colores, formatos) vive solo en
entrypoint/cli.py. El dominio no conocetyper.Exit.
- La conformidad estructural de los adapters con el
Protocolde su gateway la verificaty check(noruffnitach). Es parte del toolchain obligatorio (pre-commit + CI). - El ancla de conformidad es
wiring.py: cada adapter se devuelve tipado como su puerto, de modo quetydetecta cualquier divergencia de firma antes de runtime.
- No usar metaclases salvo justificación arquitectónica explícita.
- No usar reflexión dinámica (
getattr/setattrcon strings derivados de input externo) sin comentario que la justifique. - No monkey-patchear módulos de terceros.
- No usar import hooks ni manipular
sys.modules.
- No introducir
threading/multiprocessingsin justificación documentada en el PR. - Preferir
concurrent.futuresoasynciosobrethreading.Threadpelado. - Estructuras compartidas: usar
queue.Queueo equivalentes thread-safe.
ruff con convention = "google" cubre formato y secciones, pero estas
reglas son cualitativas:
- Módulos: docstring de una línea como mínimo.
- Clases públicas: docstring con sección
Attributes:cuando aplique. - Funciones públicas: secciones
Args:,Returns:,Raises:cuando apliquen. - Funciones privadas (
_foo): pueden omitir secciones pero conservan un resumen. - No documentar tipos en docstring si ya están en la anotación.
- TODOs siempre con autor:
# TODO(usuario): descripción de qué falta. - Comentarios en frases completas, con mayúscula inicial y punto final.
- Esta plantilla usa español por preferencia del autor; mantener consistencia dentro del proyecto.
- Toda excepción propia hereda de
app.domain.errors.DomainError. except:desnudo está prohibido (ruff BLE001lo bloquea, pero también aplica aexcept Exception:sin justificación).- No usar excepciones para flujo de control normal.
- El mapeo
DomainError → typer.Exitvive solo enentrypoint/cli.py.
- Prohibido. Toda dependencia llega por inyección (puertos + wiring).
- Constantes en
UPPER_CASEson aceptables si son inmutables.
- Evitar clases anidadas salvo factory patterns claros.
- Closures solo cuando capturan estado relevante; si no, sacar a top-level.
@propertysolo para acceso O(1) sin side effects observables.- Si la propiedad puede fallar o es costosa, usar un método
get_*()explícito.
ruff B006lo bloquea, pero recordatorio: usarNonecomo sentinela y construir el default dentro del cuerpo.
- Solo para callbacks de una línea pasados a
sorted,map, etc. - Nunca asignar una lambda a un nombre (usar
def). Lo cubreE731.
- Una sola expresión ternaria por línea. Anidadas → usar
if/else.
- Siempre absolutos (
ruff TID252configurado). - Orden: stdlib, third-party, local (lo gestiona
I). - Un import por línea.
- Obligatorias en toda función pública y atributos públicos de clases
(
ruff ANN). Anysolo en interfaces dinámicas justificadas (ANN401enignore).- Preferir
typing.Protocolsobre ABCs para puertos.
- Si una función excede ~40 líneas, considerar partirla.
ruff PLR0915da una señal pero no cubre todos los casos; revisar en code review.
print()está prohibido en código de producción (ruff T201).- Toda salida al usuario pasa por un adapter (p. ej.
ConsoleGreeter). - Para logs internos:
logging.getLogger(__name__)con lazy formatting:logger.info("user %s", user_id). f-strings dentro de log calls los bloquearuff G004.
ruff N cubre la mayoría. Convenciones:
module_name,package_nameClassName,ExceptionNamefunction_name,method_nameGLOBAL_CONSTANT_NAMEglobal_var_name,instance_var_name,function_parameter_name,local_var_name- "Interno": prefijo
_. Módulos internos: prefijo_en el nombre del archivo.
Cuando se añada un nuevo caso de uso o adapter:
- Seguir los pasos de "Cómo añadir un nuevo comando" del README.
- Las features nuevas dentro de
domain/model/odomain/usecase/no requieren tocartach.toml(pertenecen a la capa ya declarada). Solo si se introduce una capa nueva de primer nivel bajosrc/app/hay que registrarla con susdepends_onexplícitos. - Nunca invertir el flujo de capas:
modelno depende deusecase, ydomainno depende deadaptersnientrypoint. Esa es la invariante que protege la arquitectura.