package-events-bus 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 FK2.0
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,278 @@
1
+ Metadata-Version: 2.3
2
+ Name: package-events-bus
3
+ Version: 0.1.0
4
+ Summary: Una librería para construir microservicios desacoplados en Python usando AWS EventBridge y SQS, donde cada caso de uso maneja su propia cola y se despacha automáticamente mediante handlers tipados.
5
+ License: MIT
6
+ Author: Jose Luis Rosales Meza
7
+ Author-email: jose.rosales@finkargo.com
8
+ Requires-Python: >=3.10,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Dist: boto3 (>=1.38.13,<2.0.0)
16
+ Requires-Dist: python-dotenv (>=1.1.0,<2.0.0)
17
+ Requires-Dist: redis (>=5.0,<6.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+ # 📦 Package Events Bus
21
+
22
+ Una librería para construir servicios desacoplados en Python usando AWS EventBridge y SQS, donde cada caso de uso maneja su propia cola.
23
+
24
+ ---
25
+
26
+ ## � Tabla de Contenidos
27
+
28
+ - [📦 Package Events Bus](#-package-events-bus)
29
+ - [� Tabla de Contenidos](#-tabla-de-contenidos)
30
+ - [📝 Descripción](#-descripción)
31
+ - [⚙️ Instalación](#️-instalación)
32
+ - [🗂️ Estructura del Proyecto](#️-estructura-del-proyecto)
33
+ - [💡 ¿Cómo usarlo?](#-cómo-usarlo)
34
+ - [📤 Publicar eventos](#-publicar-eventos)
35
+ - [📥 Consumir eventos](#-consumir-eventos)
36
+ - [⚡ ¿Cómo implementar el dispatcher en FastAPI?](#-cómo-implementar-el-dispatcher-en-fastapi)
37
+ - [🛡️ Opcional: Configuración de Redis para deduplicación](#️-opcional-configuración-de-redis-para-deduplicación)
38
+ - [⚙️ Variables por Defecto](#️-variables-por-defecto)
39
+ - [🏷️ Definición de Topics](#️-definición-de-topics)
40
+ - [🛠️ TODO](#️-todo)
41
+ - [📝 Buenas Prácticas](#-buenas-prácticas)
42
+ - [📄 Licencia](#-licencia)
43
+
44
+ ---
45
+
46
+ ## 📝 Descripción
47
+
48
+ Esta librería permite construir microservicios desacoplados en Python, facilitando la publicación y consumo de eventos a través de AWS EventBridge y SQS. Cada caso de uso maneja su propia cola, permitiendo escalabilidad y mantenibilidad.
49
+
50
+ ---
51
+
52
+ ## ⚙️ Instalación
53
+
54
+ Instala la librería usando **pip**:
55
+
56
+ ```bash
57
+ pip install package-events-bus
58
+ ```
59
+
60
+ O usando **poetry**:
61
+
62
+ ```bash
63
+ poetry add package-events-bus
64
+ ```
65
+
66
+ ---
67
+
68
+ ## 🗂️ Estructura del Proyecto
69
+
70
+ ```
71
+ package_events_bus/
72
+ ├── __init__.py
73
+ ├── config.py
74
+ ├── exceptions.py
75
+ ├── .vscode/settings.json
76
+ ├── aws/
77
+ │ ├── event_bridge_publisher.py
78
+ │ └── sqs_dispatcher.py
79
+ ├── core/
80
+ │ ├── __init__.py
81
+ │ ├── contracts/
82
+ │ │ ├── base_event.py
83
+ │ │ ├── deduplication.py
84
+ │ │ └── handler.py
85
+ │ ├── infrastructure/
86
+ │ │ ├── event_bus_publisher.py
87
+ │ │ ├── event_serializer.py
88
+ │ │ └── redis_deduplication.py
89
+ │ └── runtime/
90
+ │ └── event_register.py
91
+ ```
92
+
93
+ - **/aws:** Implementaciones específicas para servicios de AWS.
94
+ - **event_bridge_publisher.py:** Publica eventos a AWS EventBridge.
95
+ - **sqs_dispatcher.py:** Despacha mensajes de colas SQS y procesa eventos.
96
+
97
+ ---
98
+
99
+ ## 💡 ¿Cómo usarlo?
100
+
101
+ ### 📤 Publicar eventos
102
+
103
+ 1. Crea una instancia de `EventBridgePublisher` y llama a `publish` con el evento deseado.
104
+
105
+ ```python
106
+ from events_bus.aws.event_bridge_publisher import EventBridgePublisher
107
+
108
+ event_bridge_publisher = EventBridgePublisher(
109
+ bus_name="finkargo-events",
110
+ source="my.service",
111
+ )
112
+ ```
113
+
114
+ 2. Define tu evento heredando de `BaseEvent`:
115
+ Reemplaza `DisbursementCreatedEvent` con el nombre de tu evento, recuerda sobreescribir el método `to_dict` para serializar los atributos del evento que deseas enviar.
116
+
117
+ ```python
118
+ from events_bus.core.contracts.base_event import BaseEvent
119
+
120
+ @dataclass
121
+ class DisbursementCreatedEvent(BaseEvent):
122
+ event_name = 'finkargo.portfolio.1.event.disbursement.created'
123
+
124
+ def __init__(self, disbursement):
125
+ super().__init__(event_name=self.event_name)
126
+ self.id_disbursement = disbursement.id_disbursement
127
+
128
+ def to_dict(self) -> dict:
129
+ return {
130
+ 'id_disbursement': self.id_disbursement
131
+ }
132
+
133
+ @classmethod
134
+ def from_dict(cls, event_id: str, occurred_on, attributes: dict) -> "DisbursementCreatedEvent":
135
+ return cls(disbursement_id=attributes.get('id_disbursement'))
136
+
137
+ event_bridge_publisher.publish(event=DisbursementCreatedEvent(instance))
138
+ ```
139
+
140
+ ---
141
+
142
+ ### 📥 Consumir eventos
143
+
144
+ 1. Hereda de `AsyncHandler` o `SyncHandler` y sobrescribe el método `handle`.
145
+ El método `handle` recibe el evento como parámetro, recuerda que el evento debe heredar de `BaseEvent`,
146
+ y el método `from_dict` debe estar implementado para poder deserializar el evento.
147
+
148
+ Usa `AsyncHandler` para manejar eventos de forma asíncrona y `SyncHandler` para eventos sincrónicos.
149
+
150
+ ```python
151
+ from events_bus.core import AsyncHandler
152
+
153
+ class CustomAsyncHandler(AsyncHandler):
154
+ async def handle(self, data: DisbursementCreatedEvent):
155
+ print(f"Handling event: {data.event_name}")
156
+ print(f"Finished handling event: {data.event_name}")
157
+ ```
158
+
159
+ 2. Registra el handler en el bus de eventos:
160
+ Usa `EventHandlerRegister` para registrar el handler y la cola SQS asociada, recuerda que el target debe ser configurado previamente en AWS.
161
+ Para mas información sobre la configuración de AWS, consulta la [documentación oficial](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-targets.html).
162
+
163
+ ```python
164
+ from events_bus.core import EventHandlerRegister
165
+ from events_bus.aws.sqs_dispatcher import SQSDispatcher
166
+
167
+ dispatcher = SQSDispatcher()
168
+ EventHandlerRegister.register(
169
+ queue_url='https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
170
+ handler=CustomAsyncHandler()
171
+ )
172
+ ```
173
+
174
+ ---
175
+
176
+ ### ⚡ ¿Cómo implementar el dispatcher en FastAPI?
177
+
178
+ ```python
179
+ @asynccontextmanager
180
+ async def lifespan(app: FastAPI):
181
+ await dispatcher.start()
182
+ yield
183
+ dispatcher.stop()
184
+
185
+ app = FastAPI(lifespan=lifespan)
186
+ ```
187
+
188
+ ---
189
+
190
+ ### 🛡️ Opcional: Configuración de Redis para deduplicación
191
+
192
+ ```python
193
+ from events_bus.core import RedisDeduplication
194
+ from events_bus.aws.sqs_dispatcher import SQSDispatcher
195
+
196
+ deduplication = RedisDeduplication(
197
+ url='redis://localhost:6379/0',
198
+ ttl=3600,
199
+ )
200
+
201
+ dispatcher = SQSDispatcher(deduplication=deduplication)
202
+ ```
203
+
204
+ > Si no deseas usar Redis, puedes heredar de `BaseDeduplication` e implementar tu propia lógica.
205
+
206
+ ---
207
+
208
+ ## ⚙️ Variables por Defecto
209
+
210
+ ```python
211
+ from events_bus import CONFIG
212
+ CONFIG.set_envs(os.environ, env_file='.env')
213
+ ```
214
+
215
+ Puedes modificar las variables por defecto de la librería usando un archivo `.env` o configurando las variables de entorno directamente. Las variables configurables son:
216
+
217
+ | Variable | Tipo | Por defecto |Descripción |
218
+ |-------------------------------|---------|----------------|-------------------------------------------------------------------------------|
219
+ | MAX_NUMBER_OF_MESSAGES | int | 5 | Número máximo de mensajes a recibir por llamada. |
220
+ | WAIT_TIME_SECONDS | int | 10 | Tiempo de espera para recibir mensajes. |
221
+ | VISIBILITY_TIMEOUT | int | 30 | Tiempo de visibilidad del mensaje en la cola. |
222
+ | AWS_CLIENT_URL | str | None | URL del cliente de AWS (útil para LocalStack u otros entornos locales). |
223
+ | AWS_REGION_NAME | str | us-east-1 | Región de AWS. |
224
+ | SLEEP_BETWEEN_MESSAGES_SECONDS | float | 0.1 | Tiempo de espera entre mensajes. |
225
+ | ERROR_SLEEP_SECONDS | int | 5 | Tiempo de espera en caso de error. |
226
+
227
+ > ℹ️ **Tip:** Usa `.env` para mantener tu configuración fuera del código fuente.
228
+
229
+ ---
230
+ ## 🏷️ Definición de Topics
231
+
232
+ Los topics deben seguir la convención:
233
+
234
+ ```
235
+ COMPANY.SERVICE.VERSION.MESSAGE_TYPE.RESOURCE_NAME.(EVENT_COMMAND_NAME)
236
+ ```
237
+
238
+ - **COMPANY:** Nombre de la empresa (`Finkargo` para internos).
239
+ - **SERVICE:** Servicio generador del evento.
240
+ - **VERSION:** Versión del topic.
241
+ - **MESSAGE_TYPE:** `command` o `event`.
242
+ - **RESOURCE_NAME:** Entidad relacionada.
243
+ - **EVENT_COMMAND_NAME:** Verbo en pasado para eventos (`created`), infinitivo para comandos (`create`).
244
+
245
+ **Regex de validación:**
246
+
247
+ ```
248
+ TOPIC_REGEX = "^[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.\d+\.(command|event)\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+$"
249
+ ```
250
+
251
+ Basado en: [Topics Definition | AsyncApi](https://github.com/fmvilas/topic-definition)
252
+
253
+ ---
254
+
255
+ ## 🛠️ TODO
256
+
257
+ - [ ] Mejorar el control de excepciones en la publicación de eventos.
258
+ - [ ] Agregar más ejemplos de integración con otros frameworks.
259
+ - [ ] Documentar casos de uso avanzados.
260
+ - [ ] Implementar métricas y logging para los eventos publicados y consumidos.
261
+ - [ ] Añadir pruebas unitarias y de integración.
262
+ - [ ] Mejorar la documentación en inglés y español.
263
+
264
+ ---
265
+
266
+ ## 📝 Buenas Prácticas
267
+
268
+ - Usa nombres de eventos claros y consistentes.
269
+ - Implementa deduplicación para evitar procesar eventos repetidos.
270
+ - Mantén tus handlers simples y enfocados en una sola responsabilidad.
271
+ - Documenta tus eventos y handlers.
272
+
273
+ ---
274
+ ## 📄 Licencia
275
+
276
+ Este proyecto está bajo la licencia MIT.
277
+
278
+
@@ -0,0 +1,258 @@
1
+ # 📦 Package Events Bus
2
+
3
+ Una librería para construir servicios desacoplados en Python usando AWS EventBridge y SQS, donde cada caso de uso maneja su propia cola.
4
+
5
+ ---
6
+
7
+ ## � Tabla de Contenidos
8
+
9
+ - [📦 Package Events Bus](#-package-events-bus)
10
+ - [� Tabla de Contenidos](#-tabla-de-contenidos)
11
+ - [📝 Descripción](#-descripción)
12
+ - [⚙️ Instalación](#️-instalación)
13
+ - [🗂️ Estructura del Proyecto](#️-estructura-del-proyecto)
14
+ - [💡 ¿Cómo usarlo?](#-cómo-usarlo)
15
+ - [📤 Publicar eventos](#-publicar-eventos)
16
+ - [📥 Consumir eventos](#-consumir-eventos)
17
+ - [⚡ ¿Cómo implementar el dispatcher en FastAPI?](#-cómo-implementar-el-dispatcher-en-fastapi)
18
+ - [🛡️ Opcional: Configuración de Redis para deduplicación](#️-opcional-configuración-de-redis-para-deduplicación)
19
+ - [⚙️ Variables por Defecto](#️-variables-por-defecto)
20
+ - [🏷️ Definición de Topics](#️-definición-de-topics)
21
+ - [🛠️ TODO](#️-todo)
22
+ - [📝 Buenas Prácticas](#-buenas-prácticas)
23
+ - [📄 Licencia](#-licencia)
24
+
25
+ ---
26
+
27
+ ## 📝 Descripción
28
+
29
+ Esta librería permite construir microservicios desacoplados en Python, facilitando la publicación y consumo de eventos a través de AWS EventBridge y SQS. Cada caso de uso maneja su propia cola, permitiendo escalabilidad y mantenibilidad.
30
+
31
+ ---
32
+
33
+ ## ⚙️ Instalación
34
+
35
+ Instala la librería usando **pip**:
36
+
37
+ ```bash
38
+ pip install package-events-bus
39
+ ```
40
+
41
+ O usando **poetry**:
42
+
43
+ ```bash
44
+ poetry add package-events-bus
45
+ ```
46
+
47
+ ---
48
+
49
+ ## 🗂️ Estructura del Proyecto
50
+
51
+ ```
52
+ package_events_bus/
53
+ ├── __init__.py
54
+ ├── config.py
55
+ ├── exceptions.py
56
+ ├── .vscode/settings.json
57
+ ├── aws/
58
+ │ ├── event_bridge_publisher.py
59
+ │ └── sqs_dispatcher.py
60
+ ├── core/
61
+ │ ├── __init__.py
62
+ │ ├── contracts/
63
+ │ │ ├── base_event.py
64
+ │ │ ├── deduplication.py
65
+ │ │ └── handler.py
66
+ │ ├── infrastructure/
67
+ │ │ ├── event_bus_publisher.py
68
+ │ │ ├── event_serializer.py
69
+ │ │ └── redis_deduplication.py
70
+ │ └── runtime/
71
+ │ └── event_register.py
72
+ ```
73
+
74
+ - **/aws:** Implementaciones específicas para servicios de AWS.
75
+ - **event_bridge_publisher.py:** Publica eventos a AWS EventBridge.
76
+ - **sqs_dispatcher.py:** Despacha mensajes de colas SQS y procesa eventos.
77
+
78
+ ---
79
+
80
+ ## 💡 ¿Cómo usarlo?
81
+
82
+ ### 📤 Publicar eventos
83
+
84
+ 1. Crea una instancia de `EventBridgePublisher` y llama a `publish` con el evento deseado.
85
+
86
+ ```python
87
+ from events_bus.aws.event_bridge_publisher import EventBridgePublisher
88
+
89
+ event_bridge_publisher = EventBridgePublisher(
90
+ bus_name="finkargo-events",
91
+ source="my.service",
92
+ )
93
+ ```
94
+
95
+ 2. Define tu evento heredando de `BaseEvent`:
96
+ Reemplaza `DisbursementCreatedEvent` con el nombre de tu evento, recuerda sobreescribir el método `to_dict` para serializar los atributos del evento que deseas enviar.
97
+
98
+ ```python
99
+ from events_bus.core.contracts.base_event import BaseEvent
100
+
101
+ @dataclass
102
+ class DisbursementCreatedEvent(BaseEvent):
103
+ event_name = 'finkargo.portfolio.1.event.disbursement.created'
104
+
105
+ def __init__(self, disbursement):
106
+ super().__init__(event_name=self.event_name)
107
+ self.id_disbursement = disbursement.id_disbursement
108
+
109
+ def to_dict(self) -> dict:
110
+ return {
111
+ 'id_disbursement': self.id_disbursement
112
+ }
113
+
114
+ @classmethod
115
+ def from_dict(cls, event_id: str, occurred_on, attributes: dict) -> "DisbursementCreatedEvent":
116
+ return cls(disbursement_id=attributes.get('id_disbursement'))
117
+
118
+ event_bridge_publisher.publish(event=DisbursementCreatedEvent(instance))
119
+ ```
120
+
121
+ ---
122
+
123
+ ### 📥 Consumir eventos
124
+
125
+ 1. Hereda de `AsyncHandler` o `SyncHandler` y sobrescribe el método `handle`.
126
+ El método `handle` recibe el evento como parámetro, recuerda que el evento debe heredar de `BaseEvent`,
127
+ y el método `from_dict` debe estar implementado para poder deserializar el evento.
128
+
129
+ Usa `AsyncHandler` para manejar eventos de forma asíncrona y `SyncHandler` para eventos sincrónicos.
130
+
131
+ ```python
132
+ from events_bus.core import AsyncHandler
133
+
134
+ class CustomAsyncHandler(AsyncHandler):
135
+ async def handle(self, data: DisbursementCreatedEvent):
136
+ print(f"Handling event: {data.event_name}")
137
+ print(f"Finished handling event: {data.event_name}")
138
+ ```
139
+
140
+ 2. Registra el handler en el bus de eventos:
141
+ Usa `EventHandlerRegister` para registrar el handler y la cola SQS asociada, recuerda que el target debe ser configurado previamente en AWS.
142
+ Para mas información sobre la configuración de AWS, consulta la [documentación oficial](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-targets.html).
143
+
144
+ ```python
145
+ from events_bus.core import EventHandlerRegister
146
+ from events_bus.aws.sqs_dispatcher import SQSDispatcher
147
+
148
+ dispatcher = SQSDispatcher()
149
+ EventHandlerRegister.register(
150
+ queue_url='https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
151
+ handler=CustomAsyncHandler()
152
+ )
153
+ ```
154
+
155
+ ---
156
+
157
+ ### ⚡ ¿Cómo implementar el dispatcher en FastAPI?
158
+
159
+ ```python
160
+ @asynccontextmanager
161
+ async def lifespan(app: FastAPI):
162
+ await dispatcher.start()
163
+ yield
164
+ dispatcher.stop()
165
+
166
+ app = FastAPI(lifespan=lifespan)
167
+ ```
168
+
169
+ ---
170
+
171
+ ### 🛡️ Opcional: Configuración de Redis para deduplicación
172
+
173
+ ```python
174
+ from events_bus.core import RedisDeduplication
175
+ from events_bus.aws.sqs_dispatcher import SQSDispatcher
176
+
177
+ deduplication = RedisDeduplication(
178
+ url='redis://localhost:6379/0',
179
+ ttl=3600,
180
+ )
181
+
182
+ dispatcher = SQSDispatcher(deduplication=deduplication)
183
+ ```
184
+
185
+ > Si no deseas usar Redis, puedes heredar de `BaseDeduplication` e implementar tu propia lógica.
186
+
187
+ ---
188
+
189
+ ## ⚙️ Variables por Defecto
190
+
191
+ ```python
192
+ from events_bus import CONFIG
193
+ CONFIG.set_envs(os.environ, env_file='.env')
194
+ ```
195
+
196
+ Puedes modificar las variables por defecto de la librería usando un archivo `.env` o configurando las variables de entorno directamente. Las variables configurables son:
197
+
198
+ | Variable | Tipo | Por defecto |Descripción |
199
+ |-------------------------------|---------|----------------|-------------------------------------------------------------------------------|
200
+ | MAX_NUMBER_OF_MESSAGES | int | 5 | Número máximo de mensajes a recibir por llamada. |
201
+ | WAIT_TIME_SECONDS | int | 10 | Tiempo de espera para recibir mensajes. |
202
+ | VISIBILITY_TIMEOUT | int | 30 | Tiempo de visibilidad del mensaje en la cola. |
203
+ | AWS_CLIENT_URL | str | None | URL del cliente de AWS (útil para LocalStack u otros entornos locales). |
204
+ | AWS_REGION_NAME | str | us-east-1 | Región de AWS. |
205
+ | SLEEP_BETWEEN_MESSAGES_SECONDS | float | 0.1 | Tiempo de espera entre mensajes. |
206
+ | ERROR_SLEEP_SECONDS | int | 5 | Tiempo de espera en caso de error. |
207
+
208
+ > ℹ️ **Tip:** Usa `.env` para mantener tu configuración fuera del código fuente.
209
+
210
+ ---
211
+ ## 🏷️ Definición de Topics
212
+
213
+ Los topics deben seguir la convención:
214
+
215
+ ```
216
+ COMPANY.SERVICE.VERSION.MESSAGE_TYPE.RESOURCE_NAME.(EVENT_COMMAND_NAME)
217
+ ```
218
+
219
+ - **COMPANY:** Nombre de la empresa (`Finkargo` para internos).
220
+ - **SERVICE:** Servicio generador del evento.
221
+ - **VERSION:** Versión del topic.
222
+ - **MESSAGE_TYPE:** `command` o `event`.
223
+ - **RESOURCE_NAME:** Entidad relacionada.
224
+ - **EVENT_COMMAND_NAME:** Verbo en pasado para eventos (`created`), infinitivo para comandos (`create`).
225
+
226
+ **Regex de validación:**
227
+
228
+ ```
229
+ TOPIC_REGEX = "^[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.\d+\.(command|event)\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+$"
230
+ ```
231
+
232
+ Basado en: [Topics Definition | AsyncApi](https://github.com/fmvilas/topic-definition)
233
+
234
+ ---
235
+
236
+ ## 🛠️ TODO
237
+
238
+ - [ ] Mejorar el control de excepciones en la publicación de eventos.
239
+ - [ ] Agregar más ejemplos de integración con otros frameworks.
240
+ - [ ] Documentar casos de uso avanzados.
241
+ - [ ] Implementar métricas y logging para los eventos publicados y consumidos.
242
+ - [ ] Añadir pruebas unitarias y de integración.
243
+ - [ ] Mejorar la documentación en inglés y español.
244
+
245
+ ---
246
+
247
+ ## 📝 Buenas Prácticas
248
+
249
+ - Usa nombres de eventos claros y consistentes.
250
+ - Implementa deduplicación para evitar procesar eventos repetidos.
251
+ - Mantén tus handlers simples y enfocados en una sola responsabilidad.
252
+ - Documenta tus eventos y handlers.
253
+
254
+ ---
255
+ ## 📄 Licencia
256
+
257
+ Este proyecto está bajo la licencia MIT.
258
+
@@ -0,0 +1,4 @@
1
+ from .config import Config
2
+
3
+
4
+ CONFIG = Config()
@@ -0,0 +1,43 @@
1
+ import boto3
2
+
3
+ from .. import CONFIG
4
+ from ..core import (EventBusPublisher, BaseEvent,
5
+ EventJsonSerializerDeserializer)
6
+
7
+
8
+ class EventBridgePublisher(EventBusPublisher):
9
+ def __init__(self, bus_name: str, source: str):
10
+ self.client = boto3.client('events',
11
+ region_name=CONFIG.AWS_REGION_NAME,
12
+ endpoint_url=CONFIG.AWS_CLIENT_URL)
13
+ self.bus_name = bus_name
14
+ self.source = source
15
+
16
+ def publish(self, event: BaseEvent):
17
+ """Publish a single event to the EventBridge bus."""
18
+ # TODO: Add try/except for publish
19
+ self.client.put_events(
20
+ Entries=[
21
+ {
22
+ 'Source': self.source,
23
+ 'DetailType': event.event_name,
24
+ 'Detail': EventJsonSerializerDeserializer.serialize(event),
25
+ 'EventBusName': self.bus_name,
26
+ }
27
+ ]
28
+ )
29
+
30
+ def publish_batch(self, events: list[BaseEvent]):
31
+ """Publish a batch of events to the EventBridge bus."""
32
+ # TODO: Add try/except for publish_batch
33
+ entries = []
34
+ for event in events:
35
+ entries.append(
36
+ {
37
+ 'Source': self.source,
38
+ 'DetailType': event.event_name,
39
+ 'Detail': EventJsonSerializerDeserializer.serialize(event),
40
+ 'EventBusName': self.bus_name,
41
+ }
42
+ )
43
+ self.client.put_events(Entries=entries)
@@ -0,0 +1,136 @@
1
+ import boto3
2
+ import asyncio
3
+ import logging
4
+ import traceback
5
+
6
+ from .. import CONFIG
7
+ from ..core import (EventHandlerRegistry, BaseHandler,
8
+ EventJsonSerializerDeserializer, AsyncHandler,
9
+ SyncHandler, BaseDeduplication)
10
+ from ..exceptions import (HandlerExecutionError, DeserializationEventError)
11
+
12
+
13
+ class SQSDispatcher:
14
+ """Dispatcher for handling events from AWS SQS queues.
15
+ This class is responsible for polling SQS queues, deserializing events,
16
+ executing handlers, and managing deduplication of events.
17
+ """ # noqa: E501
18
+
19
+ def __init__(self, deduplication: BaseDeduplication = None):
20
+ """
21
+ Initialize the SQSDispatcher.
22
+ Args:
23
+ deduplication (BaseDeduplication, optional): An instance of a deduplication strategy.
24
+ If provided, it will be used to check for duplicate events.
25
+ Defaults to None. Can be an instance of `RedisDeduplication`
26
+ or any other class that implements the `BaseDeduplication` interface.. Example:
27
+ >>> from events_bus.core.infrastructure.redis_deduplication import RedisDeduplication
28
+ >>> deduplication = RedisDeduplication(url='redis://localhost:6379/0')
29
+ >>> dispatcher = SQSDispatcher(deduplication=deduplication)
30
+ """ # noqa: E501
31
+ self.client = boto3.client('sqs',
32
+ region_name=CONFIG.AWS_REGION_NAME,
33
+ endpoint_url=CONFIG.AWS_CLIENT_URL)
34
+ self._stop_event = asyncio.Event()
35
+ self.deduplication = deduplication
36
+
37
+ def stop(self):
38
+ self._stop_event.set()
39
+
40
+ async def start(self):
41
+ """Start the SQS dispatcher.
42
+ This method initializes the SQS client and starts polling the queues
43
+ registered in the `EventHandlerRegistry`. It creates a separate task
44
+ for each queue to handle events concurrently.
45
+ """ # noqa: E501
46
+ for queue_url, handler in EventHandlerRegistry.get_handlers().items():
47
+ task = asyncio.create_task(self._poll_queue(queue_url, handler)) # noqa: E501
48
+ task.add_done_callback(self._handle_task_exception)
49
+
50
+ def consume(self):
51
+ """Consume messages from the SQS queues.
52
+ """
53
+ for queue_url, handler in EventHandlerRegistry.get_handlers().items():
54
+ task = asyncio.create_task(self._execute(queue_url, handler))
55
+ task.add_done_callback(self._handle_task_exception)
56
+
57
+ def _handle_task_exception(self, task: asyncio.Task):
58
+ try:
59
+ exc = task.exception()
60
+ if exc:
61
+ logging.error(
62
+ f"An error occurred in task {task.get_name()}: {exc}")
63
+ except asyncio.CancelledError:
64
+ logging.info(f"The task {task.get_name()} was cancelled.")
65
+
66
+ async def _poll_queue(self, queue_url, handler: BaseHandler): # noqa: E501
67
+ while not self._stop_event.is_set():
68
+ await self._execute(
69
+ queue_url=queue_url,
70
+ handler=handler
71
+ )
72
+
73
+ async def _execute(self, queue_url, handler: BaseHandler):
74
+ event_name = None
75
+ try:
76
+ response = await asyncio.to_thread(
77
+ self.client.receive_message,
78
+ QueueUrl=queue_url,
79
+ MaxNumberOfMessages=CONFIG.MAX_NUMBER_OF_MESSAGES,
80
+ WaitTimeSeconds=CONFIG.WAIT_TIME_SECONDS,
81
+ VisibilityTimeout=CONFIG.VISIBILITY_TIMEOUT,
82
+ )
83
+
84
+ for msg in response.get('Messages', []):
85
+ event_name = await self._process_message(queue_url, msg, handler) # noqa: E50
86
+ await asyncio.sleep(CONFIG.SLEEP_BETWEEN_MESSAGES_SECONDS)
87
+ except (Exception, HandlerExecutionError, DeserializationEventError): # noqa: E501
88
+ logging.error(traceback.format_exc())
89
+ logging.error(
90
+ f"""Error processing message from queue {queue_url}\n 'EventName': {event_name}""" # noqa: E501
91
+ )
92
+ await asyncio.sleep(CONFIG.ERROR_SLEEP_SECONDS) # noqa: E501
93
+
94
+ async def _execute_handler(self, handler: BaseHandler, event, event_name: str): # noqa: E501
95
+ try:
96
+ if isinstance(handler, AsyncHandler):
97
+ await handler.handle(event)
98
+ elif isinstance(handler, SyncHandler):
99
+ await asyncio.to_thread(handler.handle, event)
100
+ else:
101
+ raise TypeError(
102
+ f"Handler {handler.__class__.__name__} is not a valid handler type." # noqa: E501
103
+ )
104
+ except Exception as e:
105
+ raise HandlerExecutionError(
106
+ handler=handler,
107
+ event_name=event_name
108
+ ) from e
109
+
110
+ async def _process_message(self,
111
+ queue_url: str,
112
+ msg: dict,
113
+ handler: BaseHandler) -> str:
114
+ """Process a single message from the SQS queue."""
115
+ event = EventJsonSerializerDeserializer.deserialize(
116
+ event_json=msg['Body'],
117
+ handler=handler
118
+ )
119
+ event_name = event.event_name
120
+
121
+ if self.deduplication and self.deduplication.is_duplicate(event.event_id): # noqa: E501
122
+ logging.info(
123
+ f"Duplicate event detected: {event.event_id} for handler {handler.__class__.__name__}" # noqa: E501
124
+ )
125
+ return
126
+ await self._execute_handler(handler, event, event_name)
127
+
128
+ if self.deduplication:
129
+ self.deduplication.mark_as_processed(event.event_id)
130
+
131
+ await asyncio.to_thread(
132
+ self.client.delete_message,
133
+ QueueUrl=queue_url,
134
+ ReceiptHandle=msg['ReceiptHandle']
135
+ )
136
+ return event_name
@@ -0,0 +1,36 @@
1
+ import os
2
+ from os import _Environ
3
+ import dotenv
4
+
5
+
6
+ class Config:
7
+ def __init__(self,
8
+ envs: _Environ[str] = None,
9
+ env_file: str = 'localhost.env',):
10
+ self.__envs = envs
11
+ self.__env_file = env_file
12
+ self.__load()
13
+
14
+ def set_envs(self, envs: _Environ[str], env_file: str = None):
15
+ """Set the environment variables."""
16
+ self.__envs = envs
17
+ if env_file is not None:
18
+ self.__env_file = env_file
19
+ self.__load()
20
+
21
+ def __load(self):
22
+ if self.__envs is None:
23
+ dotenv.load_dotenv(self.__env_file)
24
+ self.__envs = os.environ
25
+ self.MAX_NUMBER_OF_MESSAGES = int(
26
+ self.__envs.get('MAX_NUMBER_OF_MESSAGES', 5))
27
+ self.WAIT_TIME_SECONDS = int(
28
+ self.__envs.get('WAIT_TIME_SECONDS', 10))
29
+ self.VISIBILITY_TIMEOUT = int(
30
+ self.__envs.get('VISIBILITY_TIMEOUT', 30))
31
+ self.AWS_CLIENT_URL = self.__envs.get('AWS_CLIENT_URL', None)
32
+ self.AWS_REGION_NAME = self.__envs.get('AWS_REGION_NAME', 'us-east-1')
33
+ self.SLEEP_BETWEEN_MESSAGES_SECONDS = int(
34
+ self.__envs.get('SLEEP_BETWEEN_MESSAGES_SECONDS', 0.1))
35
+ self.ERROR_SLEEP_SECONDS = int(
36
+ self.__envs.get('ERROR_SLEEP_SECONDS', 5))
@@ -0,0 +1,19 @@
1
+ from .contracts.base_event import BaseEvent
2
+ from .contracts.deduplication import BaseDeduplication
3
+ from .contracts.handler import (BaseHandler, SyncHandler, AsyncHandler)
4
+ from .infrastructure.event_bus_publisher import EventBusPublisher
5
+ from .infrastructure.event_serializer import EventJsonSerializerDeserializer
6
+ from .runtime.event_register import EventHandlerRegistry
7
+
8
+
9
+ __all__ = [
10
+ "BaseEvent",
11
+ "EventHandlerRegistry",
12
+ "EventBusPublisher",
13
+ "EventJsonSerializerDeserializer",
14
+ "EventRegistry",
15
+ "BaseHandler",
16
+ "SyncHandler",
17
+ "AsyncHandler",
18
+ "BaseDeduplication"
19
+ ]
@@ -0,0 +1,52 @@
1
+ import uuid
2
+ from datetime import (datetime, timezone)
3
+ from abc import ABC, abstractmethod
4
+
5
+
6
+ __TOPIC_REGEX = r'^[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.\d+\.(command|event)\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+$' # noqa: E501
7
+
8
+
9
+ class BaseEvent(ABC):
10
+ """
11
+ Base class for all events.
12
+
13
+ This class defines the interface for all events in the system.
14
+ It provides methods to convert events to and from dictionaries,
15
+ and to get the event type.
16
+
17
+ Attributes:
18
+ event_name (str): The fully qualified name of the event topic.
19
+ Must follow the format:
20
+ [company].[service].[version].[message_type].[resource_name].[event_command_name]
21
+ Where:
22
+ - company (str): Name of the company (e.g., 'finkargo').
23
+ - service (str): Name of the service emitting the event (e.g., 'portfolio', 'documents').
24
+ - version (int): Numeric version of the event (e.g., 1, 2).
25
+ - message_type (str): Either 'command' or 'event'.
26
+ - resource_name (str): The resource/entity the event is related to (e.g., 'disbursement').
27
+ - event_command_name (str):
28
+ - If message_type is 'event': must be a verb in past tense (e.g., 'created', 'updated', 'deleted').
29
+ - If message_type is 'command': must be a verb in infinitive form (e.g., 'create', 'send').
30
+ event_id (str): A unique identifier for the event.
31
+
32
+ occurred_on (datetime): The timestamp indicating when the event occurred.
33
+
34
+ """ # noqa: E501
35
+
36
+ def __init__(self,
37
+ event_name: str,
38
+ event_id: str | None = None,
39
+ occurred_on: datetime | None = None):
40
+ self.event_name = event_name
41
+ self.event_id = uuid.uuid4().hex if event_id is None else event_id
42
+ self.occurred_on = datetime.now(
43
+ tz=timezone.utc) if occurred_on is None else occurred_on
44
+
45
+ @abstractmethod
46
+ def to_dict(self) -> dict:
47
+ pass
48
+
49
+ @classmethod
50
+ @abstractmethod
51
+ def from_dict(cls, event_id: str, occurred_on: datetime, attributes: dict) -> "BaseEvent": # noqa: E501
52
+ pass
@@ -0,0 +1,42 @@
1
+ from abc import ABC
2
+
3
+
4
+ class BaseDeduplication(ABC):
5
+ """
6
+ Abstract base class for deduplication strategies.
7
+ This class defines the interface for deduplication strategies.
8
+ Subclasses should implement the `is_duplicate` method to check for duplicates
9
+ and the `mark_as_processed` method to mark events as processed.
10
+ Attributes:
11
+ event_id (str): The unique identifier for the event.
12
+ This is used to identify events and check for duplicates.
13
+ """ # noqa: E501
14
+
15
+ def __init__(self):
16
+ """
17
+ Initialize the BaseDeduplication instance.
18
+ This method is called when an instance of a subclass is created.
19
+ It can be used to set up any necessary resources or configurations.
20
+ """
21
+ pass
22
+
23
+ def is_duplicate(self, event_id: str) -> bool:
24
+ """
25
+ Check if the given event ID is a duplicate.
26
+
27
+ Args:
28
+ event_id (str): The event ID to check for duplication.
29
+
30
+ Returns:
31
+ bool: True if the event ID is a duplicate, False otherwise.
32
+ """
33
+ pass
34
+
35
+ def mark_as_processed(self, event_id: str):
36
+ """
37
+ Mark the given event ID as processed.
38
+
39
+ Args:
40
+ event_id (str): The event ID to mark as processed.
41
+ """
42
+ pass
@@ -0,0 +1,52 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from .. import BaseEvent
4
+
5
+
6
+ class BaseHandler(ABC):
7
+ """
8
+ Abstract base class for event handlers.
9
+ This class defines the interface for handling events.
10
+ Subclasses should implement the `handle` method to process the event.
11
+ Attributes:
12
+ event_type (type[BaseEvent]):
13
+ The type of event that this handler can process.
14
+ """
15
+ @abstractmethod
16
+ def handle(self, data: BaseEvent) -> None:
17
+ """Handle the event.
18
+ Args:
19
+ data (BaseEvent): The event data to handle.
20
+ """
21
+ pass
22
+
23
+
24
+ class SyncHandler(BaseHandler):
25
+ """
26
+ Synchronous event handler.
27
+ This class implements the `handle` method to process events synchronously.
28
+ """
29
+
30
+ def handle(self, data: BaseEvent) -> None:
31
+ """Handle the event synchronously.
32
+ Args:
33
+ data (BaseEvent): The event data to handle.
34
+ """
35
+ pass
36
+
37
+
38
+ class AsyncHandler(BaseHandler):
39
+ """
40
+ Asynchronous event handler.
41
+ This class implements the `handle` method to process events asynchronously.
42
+ """
43
+
44
+ async def handle(self, data: BaseEvent) -> None:
45
+ """Handle the event asynchronously.
46
+ Args:
47
+ data (BaseEvent): The event data to handle.
48
+ """
49
+ pass
50
+ # Implement asynchronous event handling logic here
51
+ # For example, you can process the event data and perform actions
52
+ # based on the event type and data.
@@ -0,0 +1,30 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from .. import BaseEvent
4
+
5
+
6
+ class EventBusPublisher(ABC):
7
+ """Abstract base class for event bus publishers.
8
+ This class defines the interface for publishing events to an event bus.
9
+ Subclasses should implement the `publish` and `publish_batch` methods
10
+ to publish events to the appropriate event bus
11
+ (e.g., RabbitMQ, Kafka, EventBridge, etc.).
12
+ """
13
+
14
+ @abstractmethod
15
+ def publish(self, event: BaseEvent) -> None:
16
+ """Publish an event to the event bus.
17
+ This method should be implemented by subclasses to publish the event
18
+ to the appropriate event bus
19
+ (e.g., RabbitMQ, Kafka, EventBridge, etc.).
20
+ """
21
+ pass
22
+
23
+ @abstractmethod
24
+ def publish_batch(self, events: list[BaseEvent]) -> None:
25
+ """Publish a batch of events to the event bus.
26
+ This method should be implemented by subclasses to publish the events
27
+ to the appropriate event bus
28
+ (e.g., RabbitMQ, Kafka, EventBridge, etc.).
29
+ """
30
+ pass
@@ -0,0 +1,112 @@
1
+ import json
2
+ from datetime import datetime
3
+ from typing import get_type_hints
4
+
5
+ from .. import (BaseEvent, BaseHandler)
6
+ from ...exceptions import DeserializationEventError
7
+
8
+
9
+ class EventJsonSerializerDeserializer:
10
+ """A class for serializing and deserializing events to and from JSON
11
+ format. This class provides methods to convert event objects to JSON
12
+ strings and to create event objects from JSON strings.
13
+ """
14
+
15
+ @staticmethod
16
+ def serialize(event: type[BaseEvent]) -> str:
17
+ """Convert an event object to a JSON string.
18
+ Args:
19
+ event (BaseEvent): The event object to serialize.
20
+ Returns:
21
+ str: The JSON string representation of the event.
22
+ """
23
+ if not isinstance(event, BaseEvent):
24
+ raise TypeError(
25
+ f"Expected an instance of BaseEvent, got {type(event)}"
26
+ )
27
+ return json.dumps(
28
+ {
29
+ 'data': {
30
+ 'id': event.event_id,
31
+ 'type': event.event_name,
32
+ 'occurred_on': event.occurred_on.isoformat(),
33
+ 'attributes': event.to_dict(),
34
+ }
35
+ },
36
+ default=str,
37
+ )
38
+
39
+ @staticmethod
40
+ def deserialize(event_json: str, handler: BaseHandler) -> BaseEvent:
41
+ """Convert a JSON string to an event object.
42
+ Args:
43
+ event_json (str): The JSON string to deserialize.
44
+ handler (BaseHandler): The handler to determine the event type.
45
+ Returns:
46
+ BaseEvent: The event object created from the JSON string.
47
+ """
48
+ if not isinstance(event_json, str):
49
+ raise TypeError(
50
+ f"Expected a JSON string, got {type(event_json)}"
51
+ )
52
+ try:
53
+ json_obj = json.loads(event_json)
54
+ except json.JSONDecodeError as e:
55
+ raise ValueError(
56
+ f"Invalid JSON format: {e.msg}"
57
+ ) from e
58
+ if 'detail' not in json_obj or 'data' not in json_obj['detail']:
59
+ raise ValueError(
60
+ "Invalid JSON format: 'detail' or 'data' key not found."
61
+ )
62
+ data = EventJsonSerializerDeserializer._validate_and_extract_data(json_obj) # noqa: E501
63
+ attributes = data['attributes']
64
+ event_id = data['id']
65
+ occurred_on = datetime.fromisoformat(data['occurred_on'])
66
+ event_class = EventJsonSerializerDeserializer._get_event_type_from_handler(handler) # noqa: E501
67
+ try:
68
+ return event_class.from_dict(
69
+ attributes=attributes,
70
+ event_id=event_id,
71
+ occurred_on=occurred_on,
72
+ )
73
+ except Exception as e:
74
+ raise DeserializationEventError(handler) from e
75
+
76
+ @staticmethod
77
+ def _get_event_type_from_handler(handler: BaseHandler) -> type[BaseEvent]: # noqa: E501
78
+ """Get the event type from the handler."""
79
+ if not isinstance(handler, BaseHandler):
80
+ raise TypeError(
81
+ f"Expected an instance of BaseHandler, got {type(handler)}"
82
+ )
83
+ cls = handler.__class__
84
+
85
+ method = getattr(cls, 'handle', None)
86
+ if method is None:
87
+ raise ValueError(
88
+ f"Handler {cls.__name__} does not have a 'handle' method.")
89
+
90
+ type_hints = get_type_hints(method)
91
+ event = type_hints.get('data', None)
92
+ if not issubclass(event, BaseEvent):
93
+ raise ValueError(
94
+ f"Handler {cls.__name__} does not handle a valid BaseEvent type." # noqa: E501
95
+ )
96
+ return event
97
+
98
+ @staticmethod
99
+ def _validate_and_extract_data(json_obj: dict) -> dict:
100
+ """Validate and extract data from the detail dictionary."""
101
+ if 'detail' not in json_obj or 'data' not in json_obj['detail']:
102
+ raise ValueError(
103
+ 'Invalid JSON format: "detail" or "data" key not found.' # noqa: E501
104
+ )
105
+ data = json_obj['detail']['data']
106
+ missing_keys = [key for key in ['attributes',
107
+ 'id', 'occurred_on'] if key not in data] # noqa: E501
108
+ if missing_keys:
109
+ raise ValueError(
110
+ 'Invalid JSON format: Missing keys in "data": ' + ', '.join(missing_keys) # noqa: E501
111
+ )
112
+ return data
@@ -0,0 +1,30 @@
1
+ import redis
2
+ from ..contracts.deduplication import BaseDeduplication
3
+
4
+
5
+ class RedisDeduplication(BaseDeduplication):
6
+ """
7
+ Deduplication strategy using Redis.
8
+ This class implements the deduplication strategy using Redis as the backend.
9
+ It checks for duplicates and marks events as processed using Redis.
10
+ """ # noqa: E501
11
+
12
+ def __init__(self, url: str, ttl: int = 3600):
13
+ """
14
+ Initialize the RedisDeduplication instance.
15
+ Args:
16
+ url (str): The Redis server URL. This should be a valid Redis URL
17
+ in the format 'redis://[:password]@hostname:port/db_number'.
18
+ Review the Redis documentation for more details on the URL format.
19
+ ttl (int): Time to live for the event in seconds.
20
+ Default is 3600 seconds (1 hour).
21
+ """
22
+ super().__init__()
23
+ self.redis_client = redis.Redis.from_url(url)
24
+ self.ttl = ttl
25
+
26
+ def is_duplicate(self, event_id: str) -> bool:
27
+ return self.redis_client.exists(event_id) > 0
28
+
29
+ def mark_as_processed(self, event_id: str):
30
+ self.redis_client.set(event_id, "processed", ex=self.ttl)
@@ -0,0 +1,28 @@
1
+ from .. import BaseHandler
2
+
3
+
4
+ class EventHandlerRegistry:
5
+ """Registry for event handlers.
6
+ This class is responsible for registering event handlers and
7
+ providing access to them.
8
+ """
9
+ _registry: dict[str, BaseHandler] = {}
10
+
11
+ @classmethod
12
+ def register(cls, queue_url: str, handler: BaseHandler):
13
+ """Register an event handler with the registry.
14
+ Args:
15
+ queue_url (str): The URL of the queue to which the handler is
16
+ associated.
17
+ handler (BaseHandler): The event handler to register.
18
+ """
19
+ cls._registry[queue_url] = handler
20
+
21
+ @classmethod
22
+ def get_handlers(cls) -> dict[str, BaseHandler]:
23
+ """Get all registered event handlers.
24
+ Returns:
25
+ dict[str, BaseHandler]: A dictionary mapping queue URLs to
26
+ their associated event handlers.
27
+ """
28
+ return cls._registry
@@ -0,0 +1,31 @@
1
+ from .core.contracts.handler import BaseHandler
2
+
3
+
4
+ class HandlerExecutionError(BaseException):
5
+ """
6
+ Exception raised when an event handler fails to execute.
7
+ """
8
+
9
+ def __init__(self, handler: BaseHandler, event_name: str):
10
+ super().__init__()
11
+ self.handler = handler
12
+ self.event_name = event_name
13
+
14
+ def __str__(self):
15
+ return (
16
+ f"Error processing event {self.event_name} with handler "
17
+ f"{self.handler.__class__.__name__}"
18
+ )
19
+
20
+
21
+ class DeserializationEventError(BaseException):
22
+ """
23
+ Exception raised when an event cannot be deserialized.
24
+ """
25
+
26
+ def __init__(self, handler: BaseHandler):
27
+ super().__init__()
28
+ self.handler = handler
29
+
30
+ def __str__(self):
31
+ return f'Error deserializing event with handler {self.handler.__class__.__name__}' # noqa: E501
@@ -0,0 +1,20 @@
1
+ [tool.poetry]
2
+ name = "package-events-bus"
3
+ version = "0.1.0"
4
+ description = "Una librería para construir microservicios desacoplados en Python usando AWS EventBridge y SQS, donde cada caso de uso maneja su propia cola y se despacha automáticamente mediante handlers tipados."
5
+ authors = ["Jose Luis Rosales Meza <jose.rosales@finkargo.com>"]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+
9
+ [[tool.poetry.packages]]
10
+ include = "events_bus"
11
+
12
+ [tool.poetry.dependencies]
13
+ python = "^3.10"
14
+ boto3 = "^1.38.13"
15
+ python-dotenv = "^1.1.0"
16
+ redis = "^5.0"
17
+
18
+ [build-system]
19
+ requires = ["poetry-core"]
20
+ build-backend = "poetry.core.masonry.api"