Skip to content

Latest commit

 

History

History
195 lines (147 loc) · 7.61 KB

File metadata and controls

195 lines (147 loc) · 7.61 KB

Reglas de estilo y arquitectura (complemento a ruff y tach)

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.

0. Desviaciones conscientes respecto a Google

  • 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.

1. Arquitectura hexagonal (invariantes duras)

Estas reglas las fuerza tach check. No deben relajarse:

  • app.domain nunca importa de app.adapters ni app.entrypoint.
  • app.domain.model no importa de ninguna otra capa (ni siquiera del kernel app.domain).
  • app.domain.usecase depende solo de app.domain.model y del kernel app.domain; nunca al reves (model no conoce usecase).
  • app.adapters no importa de app.entrypoint.
  • No se permiten dependencias circulares entre módulos.
  • Una capa nueva de primer nivel bajo src/app/ debe declararse en tach.toml con sus depends_on explícitos. Las features nuevas dentro de model/ o usecase/ no requieren tocar tach.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 de app.domain.errors.DomainError (kernel compartido).
  • El composition root es único: wiring.build_dependencies es el único lugar donde se instancian adapters; se invoca una sola vez en el callback raíz de entrypoint/cli.py y se inyecta vía ctx.obj. Los comandos no construyen dependencias.
  • Las dependencias externas (typer, requests, sqlalchemy, etc.) viven solo en adapters/ o entrypoint/, nunca en domain/.
  • Los fakes/mocks de test viven en tests/conftest.py o en archivos bajo tests/, nunca en src/.
  • 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 conoce typer.Exit.

Verificación de tipos (ty)

  • La conformidad estructural de los adapters con el Protocol de su gateway la verifica ty check (no ruff ni tach). 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 que ty detecta cualquier divergencia de firma antes de runtime.

2. Power features prohibidas (Google §2.19)

  • No usar metaclases salvo justificación arquitectónica explícita.
  • No usar reflexión dinámica (getattr/setattr con 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.

3. Threading y concurrencia (Google §2.17)

  • No introducir threading/multiprocessing sin justificación documentada en el PR.
  • Preferir concurrent.futures o asyncio sobre threading.Thread pelado.
  • Estructuras compartidas: usar queue.Queue o equivalentes thread-safe.

4. Docstrings (Google §3.8)

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.

5. Comentarios (Google §3.8.5)

  • 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.

6. Excepciones (Google §2.4)

  • Toda excepción propia hereda de app.domain.errors.DomainError.
  • except: desnudo está prohibido (ruff BLE001 lo bloquea, pero también aplica a except Exception: sin justificación).
  • No usar excepciones para flujo de control normal.
  • El mapeo DomainError → typer.Exit vive solo en entrypoint/cli.py.

7. Mutable global state (Google §2.5)

  • Prohibido. Toda dependencia llega por inyección (puertos + wiring).
  • Constantes en UPPER_CASE son aceptables si son inmutables.

8. Nested classes / inner functions (Google §2.16)

  • Evitar clases anidadas salvo factory patterns claros.
  • Closures solo cuando capturan estado relevante; si no, sacar a top-level.

9. Properties (Google §2.15)

  • @property solo para acceso O(1) sin side effects observables.
  • Si la propiedad puede fallar o es costosa, usar un método get_*() explícito.

10. Default mutable arguments (Google §2.12)

  • ruff B006 lo bloquea, pero recordatorio: usar None como sentinela y construir el default dentro del cuerpo.

11. Lambdas (Google §2.10)

  • Solo para callbacks de una línea pasados a sorted, map, etc.
  • Nunca asignar una lambda a un nombre (usar def). Lo cubre E731.

12. Conditional expressions (Google §2.11)

  • Una sola expresión ternaria por línea. Anidadas → usar if/else.

13. Imports (Google §2.13)

  • Siempre absolutos (ruff TID252 configurado).
  • Orden: stdlib, third-party, local (lo gestiona I).
  • Un import por línea.

14. Type annotations (Google §2.21)

  • Obligatorias en toda función pública y atributos públicos de clases (ruff ANN).
  • Any solo en interfaces dinámicas justificadas (ANN401 en ignore).
  • Preferir typing.Protocol sobre ABCs para puertos.

15. Funciones largas (Google §3.18)

  • Si una función excede ~40 líneas, considerar partirla. ruff PLR0915 da una señal pero no cubre todos los casos; revisar en code review.

16. Logging (Google §2.18)

  • 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 bloquea ruff G004.

17. Naming (Google §3.16)

ruff N cubre la mayoría. Convenciones:

  • module_name, package_name
  • ClassName, ExceptionName
  • function_name, method_name
  • GLOBAL_CONSTANT_NAME
  • global_var_name, instance_var_name, function_parameter_name, local_var_name
  • "Interno": prefijo _. Módulos internos: prefijo _ en el nombre del archivo.

18. Política de evolución de la arquitectura

Cuando se añada un nuevo caso de uso o adapter:

  1. Seguir los pasos de "Cómo añadir un nuevo comando" del README.
  2. Las features nuevas dentro de domain/model/ o domain/usecase/ no requieren tocar tach.toml (pertenecen a la capa ya declarada). Solo si se introduce una capa nueva de primer nivel bajo src/app/ hay que registrarla con sus depends_on explícitos.
  3. Nunca invertir el flujo de capas: model no depende de usecase, y domain no depende de adapters ni entrypoint. Esa es la invariante que protege la arquitectura.