vega-framework 0.1.28__py3-none-any.whl → 0.1.30__py3-none-any.whl
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.
- vega/cli/commands/generate.py +114 -0
- vega/cli/commands/init.py +6 -0
- vega/cli/commands/web.py +9 -0
- vega/cli/main.py +18 -1
- vega/cli/templates/__init__.py +6 -0
- vega/cli/templates/components.py +34 -0
- vega/cli/templates/domain/event.py.j2 +23 -0
- vega/cli/templates/domain/event_handler.py.j2 +22 -0
- vega/cli/templates/domain/repository_interface.py.j2 +1 -1
- vega/cli/templates/project/events_init.py.j2 +32 -0
- vega/discovery/__init__.py +2 -1
- vega/discovery/events.py +86 -0
- vega/events/README.md +564 -0
- vega/events/SYNTAX_GUIDE.md +360 -0
- vega/events/__init__.py +30 -0
- vega/events/bus.py +382 -0
- vega/events/decorators.py +181 -0
- vega/events/event.py +156 -0
- vega/events/middleware.py +259 -0
- vega/patterns/interactor.py +47 -1
- {vega_framework-0.1.28.dist-info → vega_framework-0.1.30.dist-info}/METADATA +1 -1
- {vega_framework-0.1.28.dist-info → vega_framework-0.1.30.dist-info}/RECORD +25 -14
- {vega_framework-0.1.28.dist-info → vega_framework-0.1.30.dist-info}/WHEEL +0 -0
- {vega_framework-0.1.28.dist-info → vega_framework-0.1.30.dist-info}/entry_points.txt +0 -0
- {vega_framework-0.1.28.dist-info → vega_framework-0.1.30.dist-info}/licenses/LICENSE +0 -0
vega/events/bus.py
ADDED
@@ -0,0 +1,382 @@
|
|
1
|
+
"""Event Bus implementation for pub/sub pattern"""
|
2
|
+
import asyncio
|
3
|
+
import logging
|
4
|
+
from typing import Type, Callable, List, Dict, Any, Optional
|
5
|
+
from collections import defaultdict
|
6
|
+
|
7
|
+
from vega.events.event import Event
|
8
|
+
|
9
|
+
logger = logging.getLogger(__name__)
|
10
|
+
|
11
|
+
|
12
|
+
class EventBus:
|
13
|
+
"""
|
14
|
+
Event Bus for publishing and subscribing to domain events.
|
15
|
+
|
16
|
+
Supports:
|
17
|
+
- Async event handlers
|
18
|
+
- Multiple subscribers per event
|
19
|
+
- Event inheritance (handlers for base events receive derived events)
|
20
|
+
- Priority ordering
|
21
|
+
- Error handling with retries
|
22
|
+
- Middleware support
|
23
|
+
|
24
|
+
Example:
|
25
|
+
from vega.events import EventBus, Event
|
26
|
+
|
27
|
+
bus = EventBus()
|
28
|
+
|
29
|
+
# Subscribe to event
|
30
|
+
@bus.subscribe(UserCreated)
|
31
|
+
async def send_welcome_email(event: UserCreated):
|
32
|
+
await email_service.send(event.email, "Welcome!")
|
33
|
+
|
34
|
+
# Publish event
|
35
|
+
await bus.publish(UserCreated(user_id="123", email="test@test.com"))
|
36
|
+
"""
|
37
|
+
|
38
|
+
def __init__(self):
|
39
|
+
"""Initialize event bus"""
|
40
|
+
self._subscribers: Dict[Type[Event], List[Dict[str, Any]]] = defaultdict(list)
|
41
|
+
self._middleware: List[Any] = []
|
42
|
+
self._error_handlers: List[Callable] = []
|
43
|
+
|
44
|
+
def subscribe(
|
45
|
+
self,
|
46
|
+
event_type: Type[Event],
|
47
|
+
handler: Optional[Callable] = None,
|
48
|
+
priority: int = 0,
|
49
|
+
retry_on_error: bool = False,
|
50
|
+
max_retries: int = 3
|
51
|
+
):
|
52
|
+
"""
|
53
|
+
Subscribe a handler to an event type.
|
54
|
+
|
55
|
+
Can be used as a decorator or called directly.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
event_type: Type of event to subscribe to
|
59
|
+
handler: Handler function (if not used as decorator)
|
60
|
+
priority: Handler priority (higher runs first)
|
61
|
+
retry_on_error: Whether to retry on failure
|
62
|
+
max_retries: Maximum retry attempts
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
Handler function (for decorator usage) or None
|
66
|
+
|
67
|
+
Example:
|
68
|
+
# As decorator
|
69
|
+
@bus.subscribe(UserCreated)
|
70
|
+
async def handle_user_created(event: UserCreated):
|
71
|
+
...
|
72
|
+
|
73
|
+
# Direct call
|
74
|
+
bus.subscribe(UserCreated, handle_user_created)
|
75
|
+
|
76
|
+
# With options
|
77
|
+
@bus.subscribe(UserCreated, priority=10, retry_on_error=True)
|
78
|
+
async def important_handler(event: UserCreated):
|
79
|
+
...
|
80
|
+
"""
|
81
|
+
def decorator(func: Callable) -> Callable:
|
82
|
+
subscriber_info = {
|
83
|
+
'handler': func,
|
84
|
+
'priority': priority,
|
85
|
+
'retry_on_error': retry_on_error,
|
86
|
+
'max_retries': max_retries,
|
87
|
+
}
|
88
|
+
|
89
|
+
# Add to subscribers list
|
90
|
+
self._subscribers[event_type].append(subscriber_info)
|
91
|
+
|
92
|
+
# Sort by priority (higher priority first)
|
93
|
+
self._subscribers[event_type].sort(
|
94
|
+
key=lambda x: x['priority'],
|
95
|
+
reverse=True
|
96
|
+
)
|
97
|
+
|
98
|
+
logger.debug(
|
99
|
+
f"Registered handler '{func.__name__}' for event '{event_type.__name__}' "
|
100
|
+
f"(priority={priority})"
|
101
|
+
)
|
102
|
+
|
103
|
+
return func
|
104
|
+
|
105
|
+
# If handler is provided, apply decorator immediately
|
106
|
+
if handler is not None:
|
107
|
+
return decorator(handler)
|
108
|
+
|
109
|
+
# Otherwise return decorator for @subscribe usage
|
110
|
+
return decorator
|
111
|
+
|
112
|
+
def unsubscribe(self, event_type: Type[Event], handler: Callable) -> bool:
|
113
|
+
"""
|
114
|
+
Unsubscribe a handler from an event type.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
event_type: Type of event
|
118
|
+
handler: Handler function to remove
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
True if handler was found and removed, False otherwise
|
122
|
+
"""
|
123
|
+
if event_type not in self._subscribers:
|
124
|
+
return False
|
125
|
+
|
126
|
+
initial_count = len(self._subscribers[event_type])
|
127
|
+
self._subscribers[event_type] = [
|
128
|
+
sub for sub in self._subscribers[event_type]
|
129
|
+
if sub['handler'] != handler
|
130
|
+
]
|
131
|
+
|
132
|
+
removed = initial_count > len(self._subscribers[event_type])
|
133
|
+
if removed:
|
134
|
+
logger.debug(f"Unsubscribed handler '{handler.__name__}' from '{event_type.__name__}'")
|
135
|
+
|
136
|
+
return removed
|
137
|
+
|
138
|
+
def add_middleware(self, middleware: 'EventMiddleware') -> None:
|
139
|
+
"""
|
140
|
+
Add middleware to the event bus.
|
141
|
+
|
142
|
+
Middleware is executed for all events in the order added.
|
143
|
+
|
144
|
+
Args:
|
145
|
+
middleware: Middleware instance
|
146
|
+
"""
|
147
|
+
self._middleware.append(middleware)
|
148
|
+
logger.debug(f"Added middleware: {middleware.__class__.__name__}")
|
149
|
+
|
150
|
+
def on_error(self, handler: Callable) -> Callable:
|
151
|
+
"""
|
152
|
+
Register error handler for failed event processing.
|
153
|
+
|
154
|
+
Error handlers receive (event, exception, handler_name) and should not raise.
|
155
|
+
|
156
|
+
Example:
|
157
|
+
@bus.on_error
|
158
|
+
async def log_errors(event, exception, handler_name):
|
159
|
+
logger.error(f"Handler {handler_name} failed: {exception}")
|
160
|
+
"""
|
161
|
+
self._error_handlers.append(handler)
|
162
|
+
return handler
|
163
|
+
|
164
|
+
async def publish(self, event: Event) -> None:
|
165
|
+
"""
|
166
|
+
Publish an event to all subscribers.
|
167
|
+
|
168
|
+
Executes all handlers in priority order. If a handler fails and has
|
169
|
+
retry enabled, it will be retried up to max_retries times.
|
170
|
+
|
171
|
+
Args:
|
172
|
+
event: Event instance to publish
|
173
|
+
|
174
|
+
Example:
|
175
|
+
await bus.publish(UserCreated(user_id="123", email="test@test.com"))
|
176
|
+
"""
|
177
|
+
logger.debug(f"Publishing event: {event.event_name} (id={event.event_id})")
|
178
|
+
|
179
|
+
# Execute middleware (before)
|
180
|
+
for middleware in self._middleware:
|
181
|
+
await middleware.before_publish(event)
|
182
|
+
|
183
|
+
# Get handlers for this event type and all parent event types
|
184
|
+
handlers = self._get_handlers_for_event(event)
|
185
|
+
|
186
|
+
if not handlers:
|
187
|
+
logger.debug(f"No subscribers for event: {event.event_name}")
|
188
|
+
# Execute middleware (after) even if no handlers
|
189
|
+
for middleware in reversed(self._middleware):
|
190
|
+
await middleware.after_publish(event)
|
191
|
+
return
|
192
|
+
|
193
|
+
logger.debug(f"Found {len(handlers)} handler(s) for event: {event.event_name}")
|
194
|
+
|
195
|
+
# Execute all handlers
|
196
|
+
tasks = []
|
197
|
+
for subscriber_info in handlers:
|
198
|
+
task = self._execute_handler(event, subscriber_info)
|
199
|
+
tasks.append(task)
|
200
|
+
|
201
|
+
# Wait for all handlers to complete
|
202
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
203
|
+
|
204
|
+
# Check for errors
|
205
|
+
for idx, result in enumerate(results):
|
206
|
+
if isinstance(result, Exception):
|
207
|
+
handler_name = handlers[idx]['handler'].__name__
|
208
|
+
logger.error(
|
209
|
+
f"Handler '{handler_name}' failed for event '{event.event_name}': {result}"
|
210
|
+
)
|
211
|
+
|
212
|
+
# Execute middleware (after)
|
213
|
+
for middleware in reversed(self._middleware):
|
214
|
+
await middleware.after_publish(event)
|
215
|
+
|
216
|
+
logger.debug(f"Event published: {event.event_name} (id={event.event_id})")
|
217
|
+
|
218
|
+
async def publish_many(self, events: List[Event]) -> None:
|
219
|
+
"""
|
220
|
+
Publish multiple events.
|
221
|
+
|
222
|
+
Events are published sequentially in order.
|
223
|
+
|
224
|
+
Args:
|
225
|
+
events: List of events to publish
|
226
|
+
"""
|
227
|
+
for event in events:
|
228
|
+
await self.publish(event)
|
229
|
+
|
230
|
+
def _get_handlers_for_event(self, event: Event) -> List[Dict[str, Any]]:
|
231
|
+
"""
|
232
|
+
Get all handlers that should receive this event.
|
233
|
+
|
234
|
+
Includes handlers for the exact event type and all parent event types.
|
235
|
+
"""
|
236
|
+
handlers = []
|
237
|
+
event_type = type(event)
|
238
|
+
|
239
|
+
# Get handlers for exact type
|
240
|
+
if event_type in self._subscribers:
|
241
|
+
handlers.extend(self._subscribers[event_type])
|
242
|
+
|
243
|
+
# Get handlers for parent types (event inheritance)
|
244
|
+
for base_type in event_type.__mro__[1:]:
|
245
|
+
if base_type == Event or not issubclass(base_type, Event):
|
246
|
+
continue
|
247
|
+
if base_type in self._subscribers:
|
248
|
+
handlers.extend(self._subscribers[base_type])
|
249
|
+
|
250
|
+
return handlers
|
251
|
+
|
252
|
+
async def _execute_handler(
|
253
|
+
self,
|
254
|
+
event: Event,
|
255
|
+
subscriber_info: Dict[str, Any]
|
256
|
+
) -> None:
|
257
|
+
"""
|
258
|
+
Execute a single event handler with retry logic.
|
259
|
+
|
260
|
+
Args:
|
261
|
+
event: Event to handle
|
262
|
+
subscriber_info: Subscriber configuration
|
263
|
+
"""
|
264
|
+
handler = subscriber_info['handler']
|
265
|
+
retry_on_error = subscriber_info['retry_on_error']
|
266
|
+
max_retries = subscriber_info['max_retries']
|
267
|
+
|
268
|
+
attempt = 0
|
269
|
+
last_exception = None
|
270
|
+
|
271
|
+
while attempt <= (max_retries if retry_on_error else 0):
|
272
|
+
try:
|
273
|
+
# Execute handler
|
274
|
+
result = handler(event)
|
275
|
+
|
276
|
+
# Await if coroutine
|
277
|
+
if asyncio.iscoroutine(result):
|
278
|
+
await result
|
279
|
+
|
280
|
+
# Success - exit retry loop
|
281
|
+
return
|
282
|
+
|
283
|
+
except Exception as e:
|
284
|
+
last_exception = e
|
285
|
+
attempt += 1
|
286
|
+
|
287
|
+
if attempt <= max_retries and retry_on_error:
|
288
|
+
logger.warning(
|
289
|
+
f"Handler '{handler.__name__}' failed (attempt {attempt}/{max_retries}): {e}"
|
290
|
+
)
|
291
|
+
# Exponential backoff
|
292
|
+
await asyncio.sleep(0.1 * (2 ** (attempt - 1)))
|
293
|
+
else:
|
294
|
+
# All retries exhausted or retry disabled
|
295
|
+
logger.error(
|
296
|
+
f"Handler '{handler.__name__}' failed for event '{event.event_name}': {e}",
|
297
|
+
exc_info=True
|
298
|
+
)
|
299
|
+
|
300
|
+
# Call error handlers
|
301
|
+
for error_handler in self._error_handlers:
|
302
|
+
try:
|
303
|
+
result = error_handler(event, e, handler.__name__)
|
304
|
+
if asyncio.iscoroutine(result):
|
305
|
+
await result
|
306
|
+
except Exception as err_ex:
|
307
|
+
logger.error(
|
308
|
+
f"Error handler failed: {err_ex}",
|
309
|
+
exc_info=True
|
310
|
+
)
|
311
|
+
|
312
|
+
# Re-raise exception
|
313
|
+
raise last_exception
|
314
|
+
|
315
|
+
def clear_subscribers(self, event_type: Optional[Type[Event]] = None) -> None:
|
316
|
+
"""
|
317
|
+
Clear all subscribers.
|
318
|
+
|
319
|
+
Args:
|
320
|
+
event_type: If provided, only clear subscribers for this event type.
|
321
|
+
If None, clear all subscribers.
|
322
|
+
"""
|
323
|
+
if event_type is None:
|
324
|
+
self._subscribers.clear()
|
325
|
+
logger.debug("Cleared all subscribers")
|
326
|
+
elif event_type in self._subscribers:
|
327
|
+
del self._subscribers[event_type]
|
328
|
+
logger.debug(f"Cleared subscribers for: {event_type.__name__}")
|
329
|
+
|
330
|
+
def get_subscriber_count(self, event_type: Type[Event]) -> int:
|
331
|
+
"""
|
332
|
+
Get the number of subscribers for an event type.
|
333
|
+
|
334
|
+
Args:
|
335
|
+
event_type: Event type
|
336
|
+
|
337
|
+
Returns:
|
338
|
+
Number of subscribers
|
339
|
+
"""
|
340
|
+
return len(self._subscribers.get(event_type, []))
|
341
|
+
|
342
|
+
|
343
|
+
# Global event bus instance
|
344
|
+
_global_event_bus: Optional[EventBus] = None
|
345
|
+
|
346
|
+
|
347
|
+
def get_event_bus() -> EventBus:
|
348
|
+
"""
|
349
|
+
Get the global event bus instance.
|
350
|
+
|
351
|
+
Creates the instance on first call (singleton pattern).
|
352
|
+
|
353
|
+
Returns:
|
354
|
+
Global EventBus instance
|
355
|
+
|
356
|
+
Example:
|
357
|
+
from vega.events import get_event_bus
|
358
|
+
|
359
|
+
bus = get_event_bus()
|
360
|
+
await bus.publish(MyEvent())
|
361
|
+
"""
|
362
|
+
global _global_event_bus
|
363
|
+
|
364
|
+
if _global_event_bus is None:
|
365
|
+
_global_event_bus = EventBus()
|
366
|
+
logger.debug("Created global event bus")
|
367
|
+
|
368
|
+
return _global_event_bus
|
369
|
+
|
370
|
+
|
371
|
+
def set_event_bus(bus: EventBus) -> None:
|
372
|
+
"""
|
373
|
+
Set a custom event bus as the global instance.
|
374
|
+
|
375
|
+
Useful for testing or custom configurations.
|
376
|
+
|
377
|
+
Args:
|
378
|
+
bus: EventBus instance to use globally
|
379
|
+
"""
|
380
|
+
global _global_event_bus
|
381
|
+
_global_event_bus = bus
|
382
|
+
logger.debug("Set custom global event bus")
|
@@ -0,0 +1,181 @@
|
|
1
|
+
"""Decorators for event handling"""
|
2
|
+
from typing import Type, Callable, Optional
|
3
|
+
from vega.events.event import Event
|
4
|
+
from vega.events.bus import get_event_bus
|
5
|
+
|
6
|
+
|
7
|
+
def subscribe(
|
8
|
+
event_type: Type[Event],
|
9
|
+
priority: int = 0,
|
10
|
+
retry_on_error: bool = False,
|
11
|
+
max_retries: int = 3
|
12
|
+
):
|
13
|
+
"""
|
14
|
+
Decorator to subscribe a function to an event on the global event bus.
|
15
|
+
|
16
|
+
This is a convenience decorator that uses the global event bus instance.
|
17
|
+
|
18
|
+
Args:
|
19
|
+
event_type: Type of event to subscribe to
|
20
|
+
priority: Handler priority (higher runs first)
|
21
|
+
retry_on_error: Whether to retry on failure
|
22
|
+
max_retries: Maximum retry attempts
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
Decorated function
|
26
|
+
|
27
|
+
Example:
|
28
|
+
from vega.events import subscribe, Event
|
29
|
+
from dataclasses import dataclass
|
30
|
+
|
31
|
+
@dataclass(frozen=True)
|
32
|
+
class UserCreated(Event):
|
33
|
+
user_id: str
|
34
|
+
email: str
|
35
|
+
|
36
|
+
@subscribe(UserCreated)
|
37
|
+
async def send_welcome_email(event: UserCreated):
|
38
|
+
print(f"Sending welcome email to {event.email}")
|
39
|
+
|
40
|
+
@subscribe(UserCreated, priority=10)
|
41
|
+
async def critical_handler(event: UserCreated):
|
42
|
+
# This runs first due to higher priority
|
43
|
+
print("Critical handler")
|
44
|
+
|
45
|
+
@subscribe(UserCreated, retry_on_error=True, max_retries=5)
|
46
|
+
async def retry_handler(event: UserCreated):
|
47
|
+
# Will retry up to 5 times on failure
|
48
|
+
await external_api.call()
|
49
|
+
"""
|
50
|
+
def decorator(func: Callable) -> Callable:
|
51
|
+
bus = get_event_bus()
|
52
|
+
bus.subscribe(
|
53
|
+
event_type=event_type,
|
54
|
+
handler=func,
|
55
|
+
priority=priority,
|
56
|
+
retry_on_error=retry_on_error,
|
57
|
+
max_retries=max_retries
|
58
|
+
)
|
59
|
+
return func
|
60
|
+
|
61
|
+
return decorator
|
62
|
+
|
63
|
+
|
64
|
+
def event_handler(
|
65
|
+
event_type: Type[Event],
|
66
|
+
bus: Optional['EventBus'] = None,
|
67
|
+
priority: int = 0,
|
68
|
+
retry_on_error: bool = False,
|
69
|
+
max_retries: int = 3
|
70
|
+
):
|
71
|
+
"""
|
72
|
+
Decorator to mark a method as an event handler.
|
73
|
+
|
74
|
+
Similar to @subscribe but allows specifying a custom event bus.
|
75
|
+
Useful for class-based handlers or testing.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
event_type: Type of event to subscribe to
|
79
|
+
bus: Custom event bus instance (uses global if None)
|
80
|
+
priority: Handler priority
|
81
|
+
retry_on_error: Whether to retry on failure
|
82
|
+
max_retries: Maximum retry attempts
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
Decorated function
|
86
|
+
|
87
|
+
Example:
|
88
|
+
from vega.events import event_handler, Event, EventBus
|
89
|
+
|
90
|
+
class UserEventHandlers:
|
91
|
+
def __init__(self, email_service):
|
92
|
+
self.email_service = email_service
|
93
|
+
|
94
|
+
@event_handler(UserCreated)
|
95
|
+
async def handle_user_created(self, event: UserCreated):
|
96
|
+
await self.email_service.send_welcome(event.email)
|
97
|
+
|
98
|
+
# With custom bus
|
99
|
+
custom_bus = EventBus()
|
100
|
+
|
101
|
+
class CustomHandlers:
|
102
|
+
@event_handler(UserCreated, bus=custom_bus)
|
103
|
+
async def handle(self, event: UserCreated):
|
104
|
+
pass
|
105
|
+
"""
|
106
|
+
def decorator(func: Callable) -> Callable:
|
107
|
+
event_bus = bus or get_event_bus()
|
108
|
+
event_bus.subscribe(
|
109
|
+
event_type=event_type,
|
110
|
+
handler=func,
|
111
|
+
priority=priority,
|
112
|
+
retry_on_error=retry_on_error,
|
113
|
+
max_retries=max_retries
|
114
|
+
)
|
115
|
+
# Mark function as event handler for introspection
|
116
|
+
func._is_event_handler = True
|
117
|
+
func._event_type = event_type
|
118
|
+
return func
|
119
|
+
|
120
|
+
return decorator
|
121
|
+
|
122
|
+
|
123
|
+
def trigger(event_class: Type[Event]):
|
124
|
+
"""
|
125
|
+
Decorator for Interactor classes to automatically trigger an event after call() completes.
|
126
|
+
|
127
|
+
The event is constructed with the return value of call() method and auto-published.
|
128
|
+
This is perfect for domain events that should be triggered after a use case completes.
|
129
|
+
|
130
|
+
Args:
|
131
|
+
event_class: The event class to trigger (must accept call() result in constructor)
|
132
|
+
|
133
|
+
Example:
|
134
|
+
from vega.patterns import Interactor
|
135
|
+
from vega.events import trigger
|
136
|
+
from vega.di import bind
|
137
|
+
from dataclasses import dataclass
|
138
|
+
|
139
|
+
@dataclass(frozen=True)
|
140
|
+
class UserCreated(Event):
|
141
|
+
user_id: str
|
142
|
+
email: str
|
143
|
+
name: str
|
144
|
+
|
145
|
+
def __post_init__(self):
|
146
|
+
super().__init__()
|
147
|
+
|
148
|
+
@trigger(UserCreated)
|
149
|
+
class CreateUser(Interactor[dict]):
|
150
|
+
def __init__(self, name: str, email: str):
|
151
|
+
self.name = name
|
152
|
+
self.email = email
|
153
|
+
|
154
|
+
@bind
|
155
|
+
async def call(self, repository: UserRepository) -> dict:
|
156
|
+
user = await repository.create(name=self.name, email=self.email)
|
157
|
+
# Return dict that matches UserCreated constructor
|
158
|
+
return {
|
159
|
+
"user_id": user.id,
|
160
|
+
"email": user.email,
|
161
|
+
"name": user.name
|
162
|
+
}
|
163
|
+
|
164
|
+
# Usage
|
165
|
+
result = await CreateUser(name="John", email="john@test.com")
|
166
|
+
# After call() completes:
|
167
|
+
# 1. Returns result to caller
|
168
|
+
# 2. Automatically publishes UserCreated event with result as input
|
169
|
+
# 3. All @subscribe(UserCreated) handlers are triggered
|
170
|
+
|
171
|
+
Note:
|
172
|
+
- The event class constructor must accept the call() result
|
173
|
+
- If call() returns a dict, event(**result) is called
|
174
|
+
- If call() returns an object, event is called with the object as first arg
|
175
|
+
- Works seamlessly with auto-publish (events publish themselves)
|
176
|
+
"""
|
177
|
+
def decorator(cls):
|
178
|
+
# Store the event class on the Interactor class
|
179
|
+
cls._trigger_event = event_class
|
180
|
+
return cls
|
181
|
+
return decorator
|