hexcore 2.1.1__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. {hexcore-2.1.1 → hexcore-2.2.0}/LICENSE +1 -5
  2. hexcore-2.2.0/MANIFEST.in +1 -0
  3. hexcore-2.2.0/PKG-INFO +463 -0
  4. hexcore-2.2.0/README.md +437 -0
  5. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/__init__.py +6 -10
  6. hexcore-2.2.0/hexcore/__main__.py +4 -0
  7. hexcore-2.2.0/hexcore/application/cqrs/__init__.py +24 -0
  8. hexcore-2.2.0/hexcore/application/cqrs/adapters.py +33 -0
  9. hexcore-2.2.0/hexcore/application/cqrs/config.py +69 -0
  10. hexcore-2.2.0/hexcore/application/cqrs/factory.py +131 -0
  11. hexcore-2.2.0/hexcore/application/cqrs/in_memory_buses.py +99 -0
  12. hexcore-2.2.0/hexcore/application/cqrs/pipeline.py +58 -0
  13. hexcore-2.2.0/hexcore/application/cqrs/registry.py +115 -0
  14. hexcore-2.2.0/hexcore/application/dtos/__init__.py +23 -0
  15. hexcore-2.2.0/hexcore/application/dtos/query.py +66 -0
  16. hexcore-2.2.0/hexcore/application/use_cases/__init__.py +13 -0
  17. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/application/use_cases/base.py +4 -4
  18. hexcore-2.2.0/hexcore/application/use_cases/query.py +37 -0
  19. hexcore-2.2.0/hexcore/config.py +185 -0
  20. hexcore-2.2.0/hexcore/domain/auth/__init__.py +13 -0
  21. hexcore-2.2.0/hexcore/domain/auth/permissions.py +71 -0
  22. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/domain/auth/value_objects.py +2 -3
  23. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/domain/base.py +3 -3
  24. hexcore-2.2.0/hexcore/domain/cqrs/__init__.py +50 -0
  25. hexcore-2.2.0/hexcore/domain/cqrs/buses.py +80 -0
  26. hexcore-2.2.0/hexcore/domain/cqrs/commands.py +30 -0
  27. hexcore-2.2.0/hexcore/domain/cqrs/exceptions.py +34 -0
  28. hexcore-2.2.0/hexcore/domain/cqrs/handlers.py +52 -0
  29. hexcore-2.2.0/hexcore/domain/cqrs/middleware.py +51 -0
  30. hexcore-2.2.0/hexcore/domain/cqrs/queries.py +27 -0
  31. hexcore-2.2.0/hexcore/domain/cqrs/serializer.py +39 -0
  32. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/domain/events.py +36 -4
  33. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/domain/repositories.py +6 -11
  34. hexcore-2.2.0/hexcore/domain/services.py +232 -0
  35. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/domain/uow.py +7 -10
  36. hexcore-2.2.0/hexcore/infrastructure/api/__init__.py +3 -0
  37. hexcore-2.2.0/hexcore/infrastructure/api/utils.py +174 -0
  38. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/infrastructure/cache/cache_backends/memory.py +3 -3
  39. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/infrastructure/cache/cache_backends/redis.py +2 -2
  40. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/infrastructure/cli.py +184 -145
  41. hexcore-2.2.0/hexcore/infrastructure/cqrs/__init__.py +24 -0
  42. hexcore-2.2.0/hexcore/infrastructure/cqrs/middlewares.py +136 -0
  43. hexcore-2.2.0/hexcore/infrastructure/cqrs/procrastinate.py +99 -0
  44. hexcore-2.2.0/hexcore/infrastructure/cqrs/pydantic_serializer.py +56 -0
  45. hexcore-2.2.0/hexcore/infrastructure/cqrs/rabbitmq.py +182 -0
  46. hexcore-2.2.0/hexcore/infrastructure/events/events_backends/memory.py +32 -0
  47. hexcore-2.2.0/hexcore/infrastructure/repositories/base.py +28 -0
  48. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/infrastructure/repositories/implementations.py +55 -35
  49. hexcore-2.2.0/hexcore/infrastructure/repositories/orms/beanie/utils.py +283 -0
  50. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/alchemy.py → hexcore-2.2.0/hexcore/infrastructure/repositories/orms/sqlalchemy/__init__.py +1 -1
  51. hexcore-2.2.0/hexcore/infrastructure/repositories/orms/sqlalchemy/session.py +66 -0
  52. {hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql → hexcore-2.2.0/hexcore/infrastructure/repositories/orms/sqlalchemy}/utils.py +132 -12
  53. hexcore-2.2.0/hexcore/infrastructure/repositories/utils.py +352 -0
  54. hexcore-2.2.0/hexcore/infrastructure/uow/__init__.py +181 -0
  55. hexcore-2.2.0/hexcore/infrastructure/uow/helpers.py +21 -0
  56. hexcore-2.2.0/hexcore/infrastructure/workers/__init__.py +1 -0
  57. hexcore-2.2.0/hexcore/infrastructure/workers/rabbitmq_worker.py +93 -0
  58. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/types.py +5 -1
  59. hexcore-2.2.0/hexcore.egg-info/PKG-INFO +463 -0
  60. hexcore-2.2.0/hexcore.egg-info/SOURCES.txt +93 -0
  61. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore.egg-info/requires.txt +7 -3
  62. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore.egg-info/top_level.txt +2 -1
  63. {hexcore-2.1.1 → hexcore-2.2.0}/pyproject.toml +18 -28
  64. hexcore-2.2.0/scripts/main.py +10 -0
  65. hexcore-2.2.0/tests/conftest.py +21 -0
  66. hexcore-2.2.0/tests/test_beanie_query_utils.py +138 -0
  67. hexcore-2.2.0/tests/test_config_loading.py +187 -0
  68. hexcore-2.2.0/tests/test_cqrs.py +602 -0
  69. hexcore-2.2.0/tests/test_domain_service_query.py +153 -0
  70. hexcore-2.2.0/tests/test_infrastructure_query_path.py +150 -0
  71. hexcore-2.2.0/tests/test_query_field_validation.py +218 -0
  72. hexcore-2.2.0/tests/test_rabbitmq_bus.py +95 -0
  73. hexcore-2.2.0/tests/test_repositories_utils.py +335 -0
  74. hexcore-2.2.0/tests/test_uow_session_regression.py +187 -0
  75. hexcore-2.2.0/tests/test_use_cases_query.py +224 -0
  76. hexcore-2.1.1/PKG-INFO +0 -38
  77. hexcore-2.1.1/README.md +0 -14
  78. hexcore-2.1.1/hexcore/__init__.pyi +0 -10
  79. hexcore-2.1.1/hexcore/application/__init__.pyi +0 -4
  80. hexcore-2.1.1/hexcore/application/dtos/__init__.py +0 -7
  81. hexcore-2.1.1/hexcore/application/dtos/__init__.pyi +0 -3
  82. hexcore-2.1.1/hexcore/application/dtos/base.pyi +0 -4
  83. hexcore-2.1.1/hexcore/application/use_cases/__init__.py +0 -7
  84. hexcore-2.1.1/hexcore/application/use_cases/__init__.pyi +0 -3
  85. hexcore-2.1.1/hexcore/application/use_cases/base.pyi +0 -11
  86. hexcore-2.1.1/hexcore/config.py +0 -99
  87. hexcore-2.1.1/hexcore/config.pyi +0 -35
  88. hexcore-2.1.1/hexcore/domain/auth/__init__.py +0 -17
  89. hexcore-2.1.1/hexcore/domain/auth/__init__.pyi +0 -4
  90. hexcore-2.1.1/hexcore/domain/auth/permissions.py +0 -73
  91. hexcore-2.1.1/hexcore/domain/auth/permissions.pyi +0 -25
  92. hexcore-2.1.1/hexcore/domain/auth/value_objects.pyi +0 -12
  93. hexcore-2.1.1/hexcore/domain/base.pyi +0 -19
  94. hexcore-2.1.1/hexcore/domain/events.pyi +0 -35
  95. hexcore-2.1.1/hexcore/domain/exceptions.pyi +0 -2
  96. hexcore-2.1.1/hexcore/domain/repositories.pyi +0 -25
  97. hexcore-2.1.1/hexcore/domain/services.py +0 -11
  98. hexcore-2.1.1/hexcore/domain/services.pyi +0 -8
  99. hexcore-2.1.1/hexcore/domain/uow.pyi +0 -18
  100. hexcore-2.1.1/hexcore/infrastructure/__init__.pyi +0 -0
  101. hexcore-2.1.1/hexcore/infrastructure/api/__init__.pyi +0 -0
  102. hexcore-2.1.1/hexcore/infrastructure/api/utils.py +0 -20
  103. hexcore-2.1.1/hexcore/infrastructure/api/utils.pyi +0 -9
  104. hexcore-2.1.1/hexcore/infrastructure/cache/__init__.pyi +0 -10
  105. hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/__init__.pyi +0 -0
  106. hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/memory.pyi +0 -10
  107. hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends/redis.pyi +0 -12
  108. hexcore-2.1.1/hexcore/infrastructure/cli.pyi +0 -21
  109. hexcore-2.1.1/hexcore/infrastructure/events/__init__.pyi +0 -0
  110. hexcore-2.1.1/hexcore/infrastructure/events/events_backends/__init__.pyi +0 -0
  111. hexcore-2.1.1/hexcore/infrastructure/events/events_backends/memory.py +0 -20
  112. hexcore-2.1.1/hexcore/infrastructure/events/events_backends/memory.pyi +0 -8
  113. hexcore-2.1.1/hexcore/infrastructure/repositories/__init__.pyi +0 -0
  114. hexcore-2.1.1/hexcore/infrastructure/repositories/base.py +0 -24
  115. hexcore-2.1.1/hexcore/infrastructure/repositories/base.pyi +0 -13
  116. hexcore-2.1.1/hexcore/infrastructure/repositories/decorators.pyi +0 -5
  117. hexcore-2.1.1/hexcore/infrastructure/repositories/implementations.pyi +0 -39
  118. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/__init__.pyi +0 -0
  119. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/__init__.py +0 -0
  120. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/__init__.pyi +0 -0
  121. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/beanie.pyi +0 -14
  122. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/utils.py +0 -134
  123. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/utils.pyi +0 -18
  124. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/__init__.py +0 -0
  125. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/__init__.pyi +0 -0
  126. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/alchemy.pyi +0 -19
  127. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/session.py +0 -31
  128. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/session.pyi +0 -9
  129. hexcore-2.1.1/hexcore/infrastructure/repositories/orms/sql/utils.pyi +0 -22
  130. hexcore-2.1.1/hexcore/infrastructure/repositories/utils.py +0 -119
  131. hexcore-2.1.1/hexcore/infrastructure/repositories/utils.pyi +0 -13
  132. hexcore-2.1.1/hexcore/infrastructure/uow/__init__.py +0 -121
  133. hexcore-2.1.1/hexcore/infrastructure/uow/__init__.pyi +0 -22
  134. hexcore-2.1.1/hexcore/infrastructure/uow/decorators.pyi +0 -4
  135. hexcore-2.1.1/hexcore/infrastructure/uow/helpers.py +0 -9
  136. hexcore-2.1.1/hexcore/infrastructure/uow/helpers.pyi +0 -7
  137. hexcore-2.1.1/hexcore/types.pyi +0 -21
  138. hexcore-2.1.1/hexcore.egg-info/PKG-INFO +0 -38
  139. hexcore-2.1.1/hexcore.egg-info/SOURCES.txt +0 -100
  140. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/application/__init__.py +0 -0
  141. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/application/dtos/base.py +0 -0
  142. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/domain/__init__.py +0 -0
  143. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/domain/exceptions.py +0 -0
  144. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/infrastructure/__init__.py +0 -0
  145. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/infrastructure/cache/__init__.py +0 -0
  146. {hexcore-2.1.1/hexcore/infrastructure/api → hexcore-2.2.0/hexcore/infrastructure/cache/cache_backends}/__init__.py +0 -0
  147. {hexcore-2.1.1/hexcore/infrastructure/cache/cache_backends → hexcore-2.2.0/hexcore/infrastructure/events}/__init__.py +0 -0
  148. {hexcore-2.1.1/hexcore/infrastructure/events → hexcore-2.2.0/hexcore/infrastructure/events/events_backends}/__init__.py +0 -0
  149. {hexcore-2.1.1/hexcore/infrastructure/events/events_backends → hexcore-2.2.0/hexcore/infrastructure/repositories}/__init__.py +0 -0
  150. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/infrastructure/repositories/decorators.py +0 -0
  151. {hexcore-2.1.1/hexcore/infrastructure/repositories → hexcore-2.2.0/hexcore/infrastructure/repositories/orms}/__init__.py +0 -0
  152. /hexcore-2.1.1/hexcore/infrastructure/repositories/orms/nosql/beanie.py → /hexcore-2.2.0/hexcore/infrastructure/repositories/orms/beanie/__init__.py +0 -0
  153. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore/infrastructure/uow/decorators.py +0 -0
  154. /hexcore-2.1.1/hexcore/domain/__init__.pyi → /hexcore-2.2.0/hexcore/py.typed +0 -0
  155. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore.egg-info/dependency_links.txt +0 -0
  156. {hexcore-2.1.1 → hexcore-2.2.0}/hexcore.egg-info/entry_points.txt +0 -0
  157. {hexcore-2.1.1/hexcore/infrastructure/repositories/orms → hexcore-2.2.0/scripts}/__init__.py +0 -0
  158. {hexcore-2.1.1 → hexcore-2.2.0}/setup.cfg +0 -0
  159. {hexcore-2.1.1 → hexcore-2.2.0}/tests/test_basic.py +0 -0
@@ -1,10 +1,6 @@
1
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2
- copies of the Software, and to permit persons to whom the Software is
3
- furnished to do so, subject to the following conditions:
4
-
5
1
  MIT License
6
2
 
7
- Copyright (c) 2025 David Latosefki (Indroic)
3
+ Copyright (c) 2025 David Latosefki
8
4
 
9
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
10
6
  of this software and associated documentation files (the "Software"), to deal
@@ -0,0 +1 @@
1
+ recursive-include hexcore *.pyi py.typed
hexcore-2.2.0/PKG-INFO ADDED
@@ -0,0 +1,463 @@
1
+ Metadata-Version: 2.4
2
+ Name: hexcore
3
+ Version: 2.2.0
4
+ Summary: Núcleo reutilizable para proyectos Python con arquitectura hexagonal y event handling. Provee abstracciones, utilidades y contratos para DDD, eventos y desacoplamiento de infraestructura.
5
+ Author-email: "David Latosefki (Indroic)" <indroic@outlook.com>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: aiosqlite>=0.21.0
11
+ Requires-Dist: alembic>=1.16.5
12
+ Requires-Dist: asyncpg>=0.30.0
13
+ Requires-Dist: beanie>=2.0.0
14
+ Requires-Dist: fastapi>=0.116.1
15
+ Requires-Dist: pika>=1.3.2
16
+ Requires-Dist: pydantic>=2.11.7
17
+ Requires-Dist: redis>=6.4.0
18
+ Requires-Dist: sqlalchemy>=2.0.43
19
+ Requires-Dist: typer>=0.17.3
20
+ Requires-Dist: ruff>=0.12.11
21
+ Provides-Extra: procrastinate
22
+ Requires-Dist: procrastinate>=3.0.0; extra == "procrastinate"
23
+ Provides-Extra: rabbitmq
24
+ Requires-Dist: aio-pika>=9.4.0; extra == "rabbitmq"
25
+ Dynamic: license-file
26
+
27
+ # HexCore [![PyPI Downloads](https://static.pepy.tech/personalized-badge/hexcore?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/hexcore)
28
+ HexCore es un módulo base reutilizable para proyectos Python que implementan arquitectura hexagonal y event handling.
29
+
30
+ ---
31
+
32
+ ## Skills del Proyecto
33
+
34
+ Este repositorio cuenta con un conjunto de skills adicionales para extender y personalizar funcionalidades en VS Code y otros entornos compatibles. Puedes encontrarlas en:
35
+
36
+ - [Repositorio de Skills de HexCore](https://github.com/Indroic/hexcore-skill)
37
+
38
+ ---
39
+
40
+ ## ¿Qué provee HexCore?
41
+
42
+ - **Clases base y abstracciones** para entidades, repositorios, servicios y unidad de trabajo (UoW), siguiendo los principios de DDD y arquitectura hexagonal.
43
+ - **Interfaces y contratos** para caché, eventos y manejo de dependencias, desacoplando la lógica de negocio de la infraestructura.
44
+ - **Utilidades para event sourcing y event dispatching** listas para usar en cualquier proyecto.
45
+ - **Estructura flexible** para que puedas construir microservicios o aplicaciones monolíticas desacopladas y testeables.
46
+
47
+ ---
48
+
49
+ ## Instalación
50
+
51
+ ```sh
52
+ pip install hexcore
53
+ ```
54
+
55
+ ## Templates de Proyecto (CLI)
56
+
57
+ HexCore incluye templates base para bootstrap de proyectos:
58
+
59
+ ```sh
60
+ hexcore init mi_proyecto --template hexagonal
61
+ hexcore init mi_proyecto --template vertical-slice
62
+ ```
63
+
64
+ - `hexagonal`: crea `src/domain`, `src/application`, `src/infrastructure`.
65
+ - `vertical-slice`: crea `src/features`, `src/shared/domain`, `src/shared/application`, `src/shared/infrastructure`.
66
+
67
+ En ambos templates se generan:
68
+ - `config.py` en raíz con `repository_discovery_paths` de ejemplo.
69
+ - estructura de migraciones con Alembic.
70
+
71
+ ---
72
+
73
+ ## Configuración v2 (Folder-Agnostic)
74
+
75
+ Desde v2, HexCore usa configuración explícita y no aplica fallback implícito para descubrir repositorios.
76
+
77
+ ### 1. Configuración visible en raíz
78
+
79
+ Define un archivo `config.py` en la raíz del proyecto:
80
+
81
+ ```python
82
+ from hexcore.config import ServerConfig
83
+
84
+ config = ServerConfig(
85
+ repository_discovery_paths={
86
+ "myapp.features.users.infrastructure.repositories",
87
+ "myapp.features.billing.infrastructure.repositories",
88
+ }
89
+ )
90
+ ```
91
+
92
+ ### 2. Prioridad para cargar configuración
93
+
94
+ `LazyConfig` resuelve módulos en este orden:
95
+
96
+ 1. `HEXCORE_CONFIG_MODULE`
97
+ 2. `HEXCORE_CONFIG_MODULES` (lista separada por comas)
98
+ 3. módulos configurados por `LazyConfig.set_config_modules(...)`
99
+ 4. `config` por defecto (raíz del proyecto)
100
+
101
+ ### 3. Regla de discovery en v2
102
+
103
+ - Si `repository_discovery_paths` está vacío, no se cargan módulos de repositorios.
104
+ - UoW falla con error explícito para evitar comportamiento ambiguo.
105
+
106
+ ---
107
+
108
+ ## Pautas de Colaboración
109
+
110
+ ¡Gracias por tu interés en contribuir a HexCore! Para mantener una colaboración organizada y eficiente, sigue estas pautas:
111
+
112
+ ### 1. Código de Conducta
113
+ Mantén siempre una comunicación respetuosa y profesional. Revisa el [Código de Conducta](CODE_OF_CONDUCT.md) antes de interactuar.
114
+
115
+ ### 2. Cómo Contribuir
116
+ - **Forkea** el repositorio y crea una rama para tu contribución (`feature/nombre`, `fix/nombre`, etc.).
117
+ - Realiza tus cambios en la rama y asegúrate de que el código funcione correctamente.
118
+ - Escribe una descripción clara y detallada en tu pull request (PR).
119
+ - Relaciona los issues relevantes en tu PR si aplica.
120
+
121
+ ### 3. Estilo y Formato de Código
122
+ - Sigue la guía de estilos de Python ([PEP8](https://pep8.org/)).
123
+ - Usa comentarios cuando sea necesario para clarificar el propósito del código.
124
+ - Idealmente, incluye pruebas unitarias para nuevas funciones y arreglos.
125
+
126
+ ### 4. Revisión de Pull Requests
127
+ - Todos los PR serán revisados antes de ser aceptados. Se pueden solicitar cambios o aclaraciones.
128
+ - Responde a los comentarios de los revisores para facilitar el proceso.
129
+
130
+ ### 5. Issues
131
+ - Describe claramente los problemas que encuentres.
132
+ - Proporciona información relevante (logs, versiones, pasos para reproducir, etc.).
133
+
134
+ ### 6. Comunicación
135
+ - Usa los issues y las discusiones para preguntas, sugerencias o propuestas.
136
+ - Si tienes dudas sobre cómo empezar, puedes abrir un issue para orientación.
137
+
138
+ ### 7. Licencia
139
+ Al contribuir, aceptas que tu código será distribuido bajo la licencia del repositorio.
140
+
141
+ ---
142
+
143
+ ## Documentación Básica
144
+
145
+ ### Estructura principal
146
+
147
+ HexCore se organiza con los siguientes submódulos y carpetas:
148
+
149
+ - **src/domain/**: Módulos de dominio, entidades, repositorios, servicios, objetos de valor, eventos, enums y excepciones.
150
+ ```
151
+ src/domain/{modulo}/
152
+ ├─ __init__.py
153
+ ├─ entities.py
154
+ ├─ repositories.py
155
+ ├─ services.py
156
+ ├─ value_objects.py
157
+ ├─ events.py
158
+ ├─ enums.py
159
+ └─ exceptions.py
160
+ ```
161
+ - **src/application/**: Casos de uso (UseCase) y DTOs para orquestar la lógica de negocio.
162
+ - **src/infrastructure/**: Implementaciones técnicas (ORM/ODM, CLI, caché, base de datos, repositorios, unit of work).
163
+ - **src/infrastructure/database/models/**: Modelos SQLAlchemy para base de datos relacional.
164
+ - **src/infrastructure/database/documents/**: Documentos Beanie para MongoDB.
165
+ - **tests/**: Pruebas para módulos de dominio e infraestructura.
166
+
167
+ ---
168
+
169
+ ### Abstracciones de Entidades y Eventos
170
+
171
+ #### BaseEntity
172
+
173
+ Clase base para entidades de dominio. Provee atributos comunes y gestión de eventos.
174
+
175
+ ```python
176
+ from hexcore.domain.base import BaseEntity
177
+
178
+ class User(BaseEntity):
179
+ id: UUID
180
+ name: str
181
+ ```
182
+
183
+ #### DomainEvent y eventos de entidad
184
+
185
+ Abstracciones para eventos de dominio y para ciclo de vida de entidades.
186
+
187
+ ```python
188
+ from hexcore.domain.events import DomainEvent, EntityCreatedEvent
189
+
190
+ class UserCreatedEvent(EntityCreatedEvent[User]):
191
+ pass
192
+
193
+ user = User(...)
194
+ event = UserCreatedEvent(entity_id=user.id, payload={"name": user.name})
195
+ ```
196
+
197
+ ---
198
+
199
+ ### Implementaciones de Repositorios
200
+
201
+ #### SQLAlchemyCommonImplementationsRepo
202
+
203
+ Repositorio genérico para modelos SQLAlchemy con métodos CRUD reutilizables.
204
+
205
+ ```python
206
+ class SQLAlchemyCommonImplementationsRepo(BaseSQLAlchemyRepository[T], HasBasicArgs[T, M], t.Generic[T, M]):
207
+ # Métodos principales: get_by_id, list_all, save, delete
208
+ ...
209
+ ```
210
+
211
+ **Ejemplo:**
212
+
213
+ ```python
214
+ class UserRepository(SQLAlchemyCommonImplementationsRepo[UserEntity, UserModel]):
215
+ def __init__(self, uow):
216
+ super().__init__(
217
+ entity_cls=UserEntity,
218
+ model_cls=UserModel,
219
+ not_found_exception=UserNotFoundException,
220
+ fields_resolvers=None,
221
+ fields_serializers=None,
222
+ uow=uow
223
+ )
224
+ ```
225
+
226
+ #### BeanieODMCommonImplementationsRepo
227
+
228
+ Repositorio genérico para documentos Beanie ODM (MongoDB) con métodos CRUD reutilizables.
229
+
230
+ ```python
231
+ class BeanieODMCommonImplementationsRepo(IBaseRepository[T], HasBasicArgs[T, D], t.Generic[T, D]):
232
+ # Métodos principales: get_by_id, list_all, save, delete
233
+ ...
234
+ ```
235
+
236
+ **Ejemplo:**
237
+
238
+ ```python
239
+ class UserRepository(BeanieODMCommonImplementationsRepo[UserEntity, UserDocument]):
240
+ def __init__(self, uow):
241
+ super().__init__(
242
+ entity_cls=UserEntity,
243
+ document_cls=UserDocument,
244
+ not_found_exception=UserNotFoundException,
245
+ fields_resolvers=None,
246
+ fields_serializers=None,
247
+ uow=uow
248
+ )
249
+ ```
250
+
251
+ ---
252
+
253
+ ### Inicialización y Descubrimiento de Documentos Beanie
254
+
255
+ Para inicializar y registrar automáticamente todos los documentos Beanie:
256
+
257
+ ```python
258
+ from hexcore.infrastructure.repositories.orms.beanie.utils import init_beanie_documents
259
+
260
+ await init_beanie_documents()
261
+ ```
262
+
263
+ ---
264
+
265
+ ### Conversión entre modelos/documentos y entidades
266
+
267
+ Ambos repositorios utilizan `to_entity_from_model_or_document` para convertir modelos ORM/ODM en entidades del dominio, aplicando resolvers para atributos complejos.
268
+
269
+ ---
270
+
271
+ ---
272
+
273
+ ## Arquitectura CQRS en HexCore
274
+
275
+ HexCore v2 integra de forma nativa soporte para el patrón **CQRS (Command Query Responsibility Segregation)**, permitiendo separar conceptual y técnicamente las operaciones de escritura (Commands) de las de lectura (Queries).
276
+
277
+ ### ¿Cómo funciona el CQRS en HexCore?
278
+
279
+ El sistema se basa en 3 buses principales, configurables e independientes:
280
+
281
+ 1. **`AbstractCommandBus`**: Despacha inteniones de mutación (`Command`) a un único `AbstractCommandHandler`. Los commands modifican el estado del sistema y se ejecutan (por defecto) dentro de una transacción de base de datos (Unit of Work).
282
+ 2. **`AbstractQueryBus`**: Despacha intenciones de lectura (`Query`) a un único `AbstractQueryHandler`. Retornan un resultado sin mutar el estado.
283
+ 3. **`EventBus`**: Distribuye eventos de dominio (`DomainEvent`) a múltiples suscriptores asíncronamente (vía `subscribe`/`publish`).
284
+
285
+ La configuración de CQRS se activa mediante el `CQRSConfig` en tu `ServerConfig`:
286
+
287
+ ```python
288
+ from hexcore.config import ServerConfig
289
+ from hexcore.application.cqrs.config import CQRSConfig, BusConfig
290
+
291
+ config = ServerConfig(
292
+ cqrs=CQRSConfig(
293
+ command_bus=BusConfig(
294
+ # Por defecto incluye TransactionMiddleware
295
+ middlewares=["hexcore.infrastructure.cqrs.middlewares.TransactionMiddleware"]
296
+ ),
297
+ # Puedes sustituir el backend en memoria por uno distribuido (Ej: Celery, Procrastinate)
298
+ # backend="mi_app.infrastructure.ProcrastinateCommandBus"
299
+ )
300
+ )
301
+ ```
302
+
303
+ ---
304
+
305
+ ### Guía de Migración: De Casos de Uso Clásicos a CQRS
306
+
307
+ Si ya tienes una aplicación escrita con la abstracción `UseCase` de HexCore, puedes migrar progresivamente a CQRS sin reescribir todo tu código, utilizando los adaptadores incluidos.
308
+
309
+ #### Paso 1: Usar el adaptador `UseCaseCommandHandler`
310
+
311
+ En lugar de instanciar un UseCase directamente en tu endpoint, envuélvelo en un comando:
312
+
313
+ ```python
314
+ from hexcore.application.cqrs.adapters import UseCaseCommandHandler
315
+ from hexcore.application.cqrs.registry import HandlerRegistry
316
+
317
+ # 1. Tienes tu UseCase legado
318
+ class CreateUserUseCase(UseCase[CreateUserCommand, UserDTO]):
319
+ async def execute(self, request: CreateUserCommand) -> UserDTO:
320
+ # logica legacy
321
+ pass
322
+
323
+ # 2. Lo registras en el registry de CQRS utilizando el adaptador
324
+ registry = HandlerRegistry()
325
+ registry.register_command(
326
+ CreateUserCommand,
327
+ UseCaseCommandHandler(CreateUserUseCase())
328
+ )
329
+ ```
330
+
331
+ #### Paso 2: Consumirlo desde el endpoint usando el CommandBus
332
+
333
+ ```python
334
+ @router.post("/users")
335
+ async def create_user(
336
+ cmd: CreateUserCommand,
337
+ # factory inyectado por dependencias
338
+ bus: AbstractCommandBus = Depends(get_command_bus)
339
+ ):
340
+ # El bus despacha el comando al UseCase legacy de forma transparente
341
+ result = await bus.dispatch(cmd)
342
+ return result
343
+ ```
344
+
345
+ #### Paso 3 (Final): Refactor a Handler Puro
346
+
347
+ Cuando estés listo, convierte tu UseCase directamente en un `AbstractCommandHandler`:
348
+
349
+ ```python
350
+ from hexcore.domain.cqrs import AbstractCommandHandler
351
+
352
+ class CreateUserHandler(AbstractCommandHandler[CreateUserCommand, UserDTO]):
353
+ def __init__(self, uow: IUnitOfWork):
354
+ self.uow = uow
355
+
356
+ async def handle(self, command: CreateUserCommand) -> UserDTO:
357
+ # Lógica refactorizada
358
+ return dto
359
+ ```
360
+
361
+ ---
362
+
363
+ ### Guía: Almacenamiento Híbrido para Queries (Mongo, Redis, SQL)
364
+
365
+ La mayor ventaja de CQRS es optimizar las lecturas. HexCore permite que tus **Commands** escriban en una base de datos relacional (SQLAlchemy) fuertemente normalizada, mientras que los **Queries** leen de vistas desnormalizadas súper rápidas en MongoDB o Redis.
366
+
367
+ #### 1. Sincronización a través del EventBus (La Proyección)
368
+
369
+ Cuando un Command modifica SQL, dispara un Evento de Dominio. Un handler de eventos intercepta este evento y actualiza el "Read Model" en MongoDB o Redis.
370
+
371
+ ```python
372
+ from hexcore.domain.events import EventBus, DomainEvent
373
+
374
+ class UserCreatedEvent(DomainEvent):
375
+ user_id: str
376
+ full_name: str
377
+ email: str
378
+
379
+ async def project_user_to_mongodb(event: UserCreatedEvent):
380
+ """Proyecta el evento en la BD de lectura (MongoDB)"""
381
+ doc = UserReadDocument(
382
+ id=event.user_id,
383
+ name=event.full_name,
384
+ email=event.email
385
+ )
386
+ await doc.insert() # usando Beanie (Mongo)
387
+
388
+ # Registrar la proyección
389
+ event_bus.subscribe(UserCreatedEvent, project_user_to_mongodb)
390
+ ```
391
+
392
+ #### 2. Query Handler leyendo del Read Model
393
+
394
+ Tu QueryHandler nunca toca SQL, simplemente ataca directamente a Mongo o Redis para máxima velocidad.
395
+
396
+ ```python
397
+ from hexcore.domain.cqrs import AbstractQueryHandler, Query
398
+
399
+ class GetUserQuery(Query[UserReadDTO]):
400
+ user_id: str
401
+
402
+ class GetUserQueryHandler(AbstractQueryHandler[GetUserQuery, UserReadDTO]):
403
+ async def handle(self, query: GetUserQuery) -> UserReadDTO:
404
+ # Consulta ultra rápida a la colección de lectura en MongoDB
405
+ doc = await UserReadDocument.get(query.user_id)
406
+
407
+ # O desde Redis:
408
+ # data = await redis_client.get(f"user:{query.user_id}")
409
+
410
+ if not doc:
411
+ raise UserNotFoundException()
412
+ return UserReadDTO(**doc.dict())
413
+ ```
414
+
415
+ Con este esquema, alcanzas una alta escalabilidad: tus endpoints GET son despachados por el `QueryBus` respondiendo en milisegundos desde Mongo/Redis, y tus operaciones POST/PUT/DELETE van por el `CommandBus` transaccionando con ACID en SQL.
416
+
417
+ #### 3. Definición de Modelos de Lectura (Proyecciones)
418
+
419
+ Una pregunta frecuente es: **¿HexCore genera automáticamente estos modelos de lectura?**
420
+ La respuesta es **No**. El patrón CQRS sugiere que tus modelos de lectura estén diseñados *específicamente* para lo que tus interfaces visuales (UI) o APIs van a consultar. Por lo tanto, debes definir estos modelos manualmente.
421
+
422
+ **Si usas MongoDB (Beanie) para lecturas:**
423
+ Debes crear un documento manual optimizado. Por ejemplo, en lugar de tener joins, puedes embeber datos:
424
+ ```python
425
+ from beanie import Document
426
+
427
+ # Modelo desnormalizado optimizado para la lectura
428
+ class UserReadDocument(Document):
429
+ id: str # ID referenciado de la tabla SQL
430
+ name: str
431
+ email: str
432
+ total_purchases_cache: int = 0 # Dato pre-calculado por eventos
433
+
434
+ class Settings:
435
+ name = "users_read_projections"
436
+ ```
437
+
438
+ **Si usas PostgreSQL/MySQL (SQLAlchemy) para lecturas:**
439
+ Si prefieres mantenerte 100% en SQL pero aislando lecturas, puedes crear tablas específicas para proyecciones (Materialized Views o tablas planas):
440
+ ```python
441
+ from sqlalchemy.orm import declarative_base
442
+ from sqlalchemy import Column, String, Integer
443
+
444
+ Base = declarative_base()
445
+
446
+ class UserReadProjection(Base):
447
+ __tablename__ = 'users_read_projection'
448
+
449
+ # Modelo totalmente plano sin relaciones ForeignKey complejas
450
+ id = Column(String, primary_key=True)
451
+ full_name = Column(String)
452
+ email = Column(String)
453
+ total_purchases_cache = Column(Integer, default=0)
454
+ ```
455
+ En ambos casos, es tu **EventBus** (o un consumidor como Procrastinate) el encargado de instanciar estos modelos manuales y persistirlos cada vez que se detecte un cambio en los modelos de escritura.
456
+
457
+ ---
458
+
459
+ ## Referencias
460
+
461
+ - [CONTRIBUTING.md](./CONTRIBUTING.md): Pautas de colaboración.
462
+ - [CHANGELOG.md](./CHANGELOG.md): Historial de cambios.
463
+ - [DOCS.md](./DOCS.md): Documentación básica de clases, funciones y ejemplos.