wappa 0.1.8__py3-none-any.whl → 0.1.10__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.

Potentially problematic release.


This version of wappa might be problematic. Click here for more details.

Files changed (147) hide show
  1. wappa/__init__.py +4 -5
  2. wappa/api/controllers/webhook_controller.py +5 -2
  3. wappa/api/dependencies/__init__.py +0 -5
  4. wappa/api/middleware/error_handler.py +4 -4
  5. wappa/api/middleware/owner.py +11 -5
  6. wappa/api/routes/webhooks.py +2 -2
  7. wappa/cli/__init__.py +1 -1
  8. wappa/cli/examples/init/.env.example +33 -0
  9. wappa/cli/examples/init/app/__init__.py +0 -0
  10. wappa/cli/examples/init/app/main.py +9 -0
  11. wappa/cli/examples/init/app/master_event.py +10 -0
  12. wappa/cli/examples/json_cache_example/.env.example +33 -0
  13. wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
  14. wappa/cli/examples/json_cache_example/app/main.py +247 -0
  15. wappa/cli/examples/json_cache_example/app/master_event.py +455 -0
  16. wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
  17. wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +256 -0
  18. wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
  19. wappa/cli/examples/json_cache_example/app/scores/score_base.py +192 -0
  20. wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +256 -0
  21. wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +187 -0
  22. wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +272 -0
  23. wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +239 -0
  24. wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
  25. wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +174 -0
  26. wappa/cli/examples/json_cache_example/app/utils/message_utils.py +251 -0
  27. wappa/cli/examples/openai_transcript/.gitignore +63 -4
  28. wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
  29. wappa/cli/examples/openai_transcript/app/main.py +9 -0
  30. wappa/cli/examples/openai_transcript/app/master_event.py +62 -0
  31. wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
  32. wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +89 -0
  33. wappa/cli/examples/redis_cache_example/.env.example +33 -0
  34. wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
  35. wappa/cli/examples/redis_cache_example/app/main.py +246 -0
  36. wappa/cli/examples/redis_cache_example/app/master_event.py +455 -0
  37. wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +256 -0
  38. wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
  39. wappa/cli/examples/redis_cache_example/app/scores/score_base.py +192 -0
  40. wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +256 -0
  41. wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +187 -0
  42. wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +272 -0
  43. wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +239 -0
  44. wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
  45. wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +174 -0
  46. wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +251 -0
  47. wappa/cli/examples/simple_echo_example/.env.example +33 -0
  48. wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
  49. wappa/cli/examples/simple_echo_example/app/main.py +191 -0
  50. wappa/cli/examples/simple_echo_example/app/master_event.py +230 -0
  51. wappa/cli/examples/wappa_full_example/.env.example +33 -0
  52. wappa/cli/examples/wappa_full_example/.gitignore +63 -4
  53. wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
  54. wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
  55. wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +492 -0
  56. wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +559 -0
  57. wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +514 -0
  58. wappa/cli/examples/wappa_full_example/app/main.py +269 -0
  59. wappa/cli/examples/wappa_full_example/app/master_event.py +504 -0
  60. wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
  61. wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
  62. wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
  63. wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
  64. wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
  65. wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
  66. wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
  67. wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
  68. wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
  69. wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
  70. wappa/cli/examples/wappa_full_example/app/models/state_models.py +434 -0
  71. wappa/cli/examples/wappa_full_example/app/models/user_models.py +303 -0
  72. wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +327 -0
  73. wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
  74. wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +502 -0
  75. wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +516 -0
  76. wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +337 -0
  77. wappa/cli/main.py +14 -5
  78. wappa/core/__init__.py +18 -23
  79. wappa/core/config/settings.py +7 -5
  80. wappa/core/events/default_handlers.py +1 -1
  81. wappa/core/factory/wappa_builder.py +38 -25
  82. wappa/core/plugins/redis_plugin.py +1 -3
  83. wappa/core/plugins/wappa_core_plugin.py +7 -6
  84. wappa/core/types.py +12 -12
  85. wappa/core/wappa_app.py +10 -8
  86. wappa/database/__init__.py +3 -4
  87. wappa/domain/enums/messenger_platform.py +1 -2
  88. wappa/domain/factories/media_factory.py +5 -20
  89. wappa/domain/factories/message_factory.py +5 -20
  90. wappa/domain/factories/messenger_factory.py +2 -4
  91. wappa/domain/interfaces/cache_interface.py +7 -7
  92. wappa/domain/interfaces/media_interface.py +2 -5
  93. wappa/domain/models/media_result.py +1 -3
  94. wappa/domain/models/platforms/platform_config.py +1 -3
  95. wappa/messaging/__init__.py +9 -12
  96. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
  97. wappa/models/__init__.py +27 -35
  98. wappa/persistence/__init__.py +12 -15
  99. wappa/persistence/cache_factory.py +0 -1
  100. wappa/persistence/json/__init__.py +1 -1
  101. wappa/persistence/json/cache_adapters.py +37 -25
  102. wappa/persistence/json/handlers/state_handler.py +60 -52
  103. wappa/persistence/json/handlers/table_handler.py +51 -49
  104. wappa/persistence/json/handlers/user_handler.py +71 -55
  105. wappa/persistence/json/handlers/utils/file_manager.py +42 -39
  106. wappa/persistence/json/handlers/utils/key_factory.py +1 -1
  107. wappa/persistence/json/handlers/utils/serialization.py +13 -11
  108. wappa/persistence/json/json_cache_factory.py +4 -8
  109. wappa/persistence/json/storage_manager.py +66 -79
  110. wappa/persistence/memory/__init__.py +1 -1
  111. wappa/persistence/memory/cache_adapters.py +37 -25
  112. wappa/persistence/memory/handlers/state_handler.py +62 -52
  113. wappa/persistence/memory/handlers/table_handler.py +59 -53
  114. wappa/persistence/memory/handlers/user_handler.py +75 -55
  115. wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
  116. wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
  117. wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
  118. wappa/persistence/memory/memory_cache_factory.py +3 -7
  119. wappa/persistence/memory/storage_manager.py +52 -62
  120. wappa/persistence/redis/cache_adapters.py +27 -21
  121. wappa/persistence/redis/ops.py +11 -11
  122. wappa/persistence/redis/redis_client.py +4 -6
  123. wappa/persistence/redis/redis_manager.py +12 -4
  124. wappa/processors/factory.py +5 -5
  125. wappa/schemas/factory.py +2 -5
  126. wappa/schemas/whatsapp/message_types/errors.py +3 -12
  127. wappa/schemas/whatsapp/validators.py +3 -3
  128. wappa/webhooks/__init__.py +17 -18
  129. wappa/webhooks/factory.py +3 -5
  130. wappa/webhooks/whatsapp/__init__.py +10 -13
  131. wappa/webhooks/whatsapp/message_types/audio.py +0 -4
  132. wappa/webhooks/whatsapp/message_types/document.py +1 -9
  133. wappa/webhooks/whatsapp/message_types/errors.py +3 -12
  134. wappa/webhooks/whatsapp/message_types/location.py +1 -21
  135. wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
  136. wappa/webhooks/whatsapp/message_types/text.py +0 -6
  137. wappa/webhooks/whatsapp/message_types/video.py +1 -20
  138. wappa/webhooks/whatsapp/status_models.py +2 -2
  139. wappa/webhooks/whatsapp/validators.py +3 -3
  140. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
  141. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/RECORD +144 -80
  142. wappa/cli/examples/init/pyproject.toml +0 -7
  143. wappa/cli/examples/simple_echo_example/.python-version +0 -1
  144. wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
  145. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
  146. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
  147. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,455 @@
1
+ """
2
+ Master Event Handler - WappaEventHandler implementation following SOLID principles.
3
+
4
+ This module defines the main WappaEventHandler that:
5
+ - Extends WappaEventHandler with proper method signatures
6
+ - Coordinates multiple score modules using dependency injection
7
+ - Follows Single Responsibility Principle for event handling
8
+ - Uses Open/Closed Principle for score module extensibility
9
+ - Implements Liskov Substitution for handler compatibility
10
+ - Uses Interface Segregation with focused score interfaces
11
+ - Follows Dependency Inversion with injected dependencies
12
+ """
13
+
14
+ from typing import Any
15
+
16
+ from wappa import WappaEventHandler
17
+ from wappa.webhooks import ErrorWebhook, IncomingMessageWebhook, StatusWebhook
18
+
19
+ from .scores import AVAILABLE_SCORES, ScoreDependencies
20
+ from .scores.score_base import ScoreRegistry
21
+ from .utils.message_utils import extract_user_data, sanitize_message_text
22
+
23
+
24
+ class JSONCacheExampleHandler(WappaEventHandler):
25
+ """
26
+ Main WappaEventHandler implementation for JSON cache example following SOLID principles.
27
+
28
+ This handler serves as the main entry point for the Wappa framework and demonstrates:
29
+ - Proper WappaEventHandler method implementations
30
+ - SOLID architecture with score module orchestration
31
+ - Dependency injection and lifecycle management
32
+ - Professional error handling and logging
33
+ """
34
+
35
+ def __init__(self):
36
+ """Initialize the JSON cache example handler."""
37
+ super().__init__()
38
+
39
+ # Score module registry (following Open/Closed Principle)
40
+ self.score_registry = ScoreRegistry()
41
+
42
+ # Processing statistics
43
+ self._total_messages = 0
44
+ self._successful_processing = 0
45
+ self._failed_processing = 0
46
+
47
+ # Master handler state
48
+ self._initialized = False
49
+
50
+ self.logger.info(
51
+ "🎯 JSONCacheExampleHandler initialized - ready for SOLID architecture setup"
52
+ )
53
+
54
+ async def process_message(self, webhook: IncomingMessageWebhook) -> None:
55
+ """
56
+ Main message processing method required by WappaEventHandler.
57
+
58
+ This method orchestrates score modules following SOLID principles and
59
+ demonstrates proper webhook processing with dependency injection.
60
+
61
+ Args:
62
+ webhook: Incoming message webhook to process
63
+ """
64
+ self._total_messages += 1
65
+ start_time = self._get_current_timestamp()
66
+
67
+ try:
68
+ # Initialize SOLID architecture on first message if not already done
69
+ if not self._initialized:
70
+ await self._initialize_solid_architecture()
71
+
72
+ # Extract basic user information for logging
73
+ user_data = extract_user_data(webhook)
74
+ user_id = user_data["user_id"]
75
+ message_text = webhook.get_message_text() or "[NON-TEXT MESSAGE]"
76
+
77
+ self.logger.info(
78
+ f"📨 Processing message from {user_id}: "
79
+ f"{sanitize_message_text(message_text)[:50]}..."
80
+ )
81
+
82
+ # Execute score module processing pipeline
83
+ processing_result = await self._execute_score_pipeline(webhook)
84
+
85
+ # Record processing results
86
+ if processing_result["success"]:
87
+ self._successful_processing += 1
88
+ processing_time = self._get_current_timestamp() - start_time
89
+
90
+ self.logger.info(
91
+ f"✅ Message processed successfully in {processing_time:.2f}s "
92
+ f"(processed by {processing_result['processed_count']} score modules)"
93
+ )
94
+ else:
95
+ self._failed_processing += 1
96
+ self.logger.warning(
97
+ f"⚠️ Message processing completed with issues: "
98
+ f"{processing_result.get('error', 'Unknown error')}"
99
+ )
100
+
101
+ # Send fallback response to user
102
+ await self._send_error_response(
103
+ webhook, processing_result.get("error", "Processing error")
104
+ )
105
+
106
+ except Exception as e:
107
+ self._failed_processing += 1
108
+ self.logger.error(
109
+ f"❌ Critical error in message processing: {e}", exc_info=True
110
+ )
111
+ await self._send_error_response(webhook, f"System error: {str(e)}")
112
+
113
+ async def process_status(self, webhook: StatusWebhook) -> None:
114
+ """
115
+ Process status webhooks from WhatsApp Business API.
116
+
117
+ Args:
118
+ webhook: Status webhook containing delivery status information
119
+ """
120
+ try:
121
+ status_value = webhook.status.value
122
+ recipient = webhook.recipient_id
123
+
124
+ self.logger.info(
125
+ f"📊 Message status: {status_value.upper()} for {recipient}"
126
+ )
127
+
128
+ # You can add custom status processing logic here
129
+ # For example, updating delivery statistics or handling failed deliveries
130
+
131
+ except Exception as e:
132
+ self.logger.error(f"❌ Error processing status webhook: {e}", exc_info=True)
133
+
134
+ async def process_error(self, webhook: ErrorWebhook) -> None:
135
+ """
136
+ Process error webhooks from WhatsApp Business API.
137
+
138
+ Args:
139
+ webhook: Error webhook containing error information
140
+ """
141
+ try:
142
+ error_count = webhook.get_error_count()
143
+ primary_error = webhook.get_primary_error()
144
+
145
+ self.logger.error(
146
+ f"🚨 WhatsApp API error: {error_count} errors, "
147
+ f"primary: {primary_error.error_code} - {primary_error.error_title}"
148
+ )
149
+
150
+ # Record error in statistics
151
+ self._failed_processing += 1
152
+
153
+ # You can add custom error handling logic here
154
+ # For example, alerting systems or retry mechanisms
155
+
156
+ except Exception as e:
157
+ self.logger.error(f"❌ Error processing error webhook: {e}", exc_info=True)
158
+
159
+ async def _initialize_solid_architecture(self) -> None:
160
+ """
161
+ Initialize SOLID architecture with score modules and dependency injection.
162
+
163
+ This method demonstrates Dependency Inversion Principle by injecting
164
+ abstractions and follows Single Responsibility Principle.
165
+ """
166
+ try:
167
+ if not self.validate_dependencies():
168
+ self.logger.error(
169
+ "❌ Dependencies not properly injected - cannot initialize SOLID architecture"
170
+ )
171
+ return
172
+
173
+ if not self.cache_factory:
174
+ self.logger.error(
175
+ "❌ Cache factory not available - cannot initialize SOLID architecture"
176
+ )
177
+ return
178
+
179
+ # Create cache instances from factory (Dependency Inversion)
180
+ user_cache = self.cache_factory.create_user_cache()
181
+ table_cache = self.cache_factory.create_table_cache()
182
+ state_cache = self.cache_factory.create_state_cache()
183
+
184
+ # Create dependencies container
185
+ dependencies = ScoreDependencies(
186
+ messenger=self.messenger,
187
+ user_cache=user_cache,
188
+ table_cache=table_cache,
189
+ state_cache=state_cache,
190
+ logger=self.logger,
191
+ )
192
+
193
+ # Auto-register all available score modules (Open/Closed Principle)
194
+ registered_count = 0
195
+ for score_class in AVAILABLE_SCORES:
196
+ try:
197
+ # Instantiate score with dependency injection
198
+ score_instance = score_class(dependencies)
199
+ self.score_registry.register_score(score_instance)
200
+ registered_count += 1
201
+
202
+ self.logger.info(
203
+ f"✅ Registered score module: {score_instance.score_name}"
204
+ )
205
+
206
+ except Exception as e:
207
+ self.logger.error(
208
+ f"❌ Failed to register {score_class.__name__}: {e}"
209
+ )
210
+
211
+ self._initialized = True
212
+ self.logger.info(
213
+ f"🎯 SOLID architecture initialized successfully: {registered_count} score modules registered"
214
+ )
215
+
216
+ except Exception as e:
217
+ self.logger.error(
218
+ f"❌ Critical error initializing SOLID architecture: {e}", exc_info=True
219
+ )
220
+ raise
221
+
222
+ async def _execute_score_pipeline(
223
+ self, webhook: IncomingMessageWebhook
224
+ ) -> dict[str, Any]:
225
+ """
226
+ Execute the score module processing pipeline.
227
+
228
+ Processes webhook through all applicable score modules following
229
+ the Chain of Responsibility pattern.
230
+
231
+ Args:
232
+ webhook: Webhook to process
233
+
234
+ Returns:
235
+ Processing result with success status and metadata
236
+ """
237
+ try:
238
+ if not self._initialized:
239
+ return {
240
+ "success": False,
241
+ "error": "SOLID architecture not initialized",
242
+ "processed_count": 0,
243
+ }
244
+
245
+ scores = self.score_registry.get_scores()
246
+ processed_count = 0
247
+ processing_errors = []
248
+
249
+ # Process webhook through all applicable score modules
250
+ for score in scores:
251
+ try:
252
+ # Check if score can handle this webhook (Interface Segregation)
253
+ can_handle = await score.can_handle(webhook)
254
+
255
+ if can_handle:
256
+ self.logger.debug(f"🎯 Processing with {score.score_name}")
257
+
258
+ # Process with the score module
259
+ success = await score.process(webhook)
260
+
261
+ if success:
262
+ processed_count += 1
263
+ self.logger.debug(
264
+ f"✅ {score.score_name} completed successfully"
265
+ )
266
+ else:
267
+ processing_errors.append(
268
+ f"{score.score_name}: Processing failed"
269
+ )
270
+ self.logger.warning(
271
+ f"⚠️ {score.score_name} reported processing failure"
272
+ )
273
+ else:
274
+ self.logger.debug(
275
+ f"⏭️ {score.score_name} skipped (cannot handle this webhook)"
276
+ )
277
+
278
+ except Exception as score_error:
279
+ processing_errors.append(f"{score.score_name}: {str(score_error)}")
280
+ self.logger.error(
281
+ f"❌ Error in {score.score_name}: {score_error}", exc_info=True
282
+ )
283
+
284
+ # Determine overall success
285
+ overall_success = processed_count > 0 and len(processing_errors) == 0
286
+
287
+ return {
288
+ "success": overall_success,
289
+ "processed_count": processed_count,
290
+ "total_scores": len(scores),
291
+ "errors": processing_errors if processing_errors else None,
292
+ "message": (
293
+ f"Processed by {processed_count}/{len(scores)} score modules"
294
+ + (
295
+ f" with {len(processing_errors)} errors"
296
+ if processing_errors
297
+ else ""
298
+ )
299
+ ),
300
+ }
301
+
302
+ except Exception as e:
303
+ self.logger.error(
304
+ f"❌ Critical error in score pipeline: {e}", exc_info=True
305
+ )
306
+ return {
307
+ "success": False,
308
+ "processed_count": 0,
309
+ "error": f"Pipeline error: {str(e)}",
310
+ }
311
+
312
+ async def _send_error_response(
313
+ self, webhook: IncomingMessageWebhook, error_details: str
314
+ ) -> None:
315
+ """
316
+ Send user-friendly error response when processing fails.
317
+
318
+ Args:
319
+ webhook: Original webhook that failed to process
320
+ error_details: Details about the error for logging
321
+ """
322
+ try:
323
+ user_data = extract_user_data(webhook)
324
+ user_id = user_data["user_id"]
325
+
326
+ error_message = (
327
+ "🚨 SOLID JSON Cache Example\n\n"
328
+ "❌ An error occurred while processing your message.\n"
329
+ "Our team has been notified and will resolve this issue soon.\n\n"
330
+ "Please try again later or contact support if the problem persists."
331
+ )
332
+
333
+ result = await self.messenger.send_text(
334
+ recipient=user_id,
335
+ text=error_message,
336
+ reply_to_message_id=webhook.message.message_id,
337
+ )
338
+
339
+ if result.success:
340
+ self.logger.info(f"🚨 Error response sent to {user_id}")
341
+ else:
342
+ self.logger.error(f"❌ Failed to send error response: {result.error}")
343
+
344
+ except Exception as e:
345
+ self.logger.error(f"❌ Error sending error response: {e}")
346
+
347
+ def _get_current_timestamp(self) -> float:
348
+ """Get current timestamp for performance measurement."""
349
+ import time
350
+
351
+ return time.time()
352
+
353
+ async def get_handler_statistics(self) -> dict[str, Any]:
354
+ """
355
+ Get comprehensive handler and score module statistics.
356
+
357
+ Returns:
358
+ Dictionary with processing statistics and score module metrics
359
+ """
360
+ try:
361
+ # Calculate success rate
362
+ success_rate = (
363
+ (self._successful_processing / self._total_messages)
364
+ if self._total_messages > 0
365
+ else 0.0
366
+ )
367
+
368
+ # Get score-specific statistics if initialized
369
+ score_stats = {}
370
+ if self._initialized:
371
+ score_stats = self.score_registry.get_score_stats()
372
+
373
+ return {
374
+ "handler_status": "initialized"
375
+ if self._initialized
376
+ else "pending_initialization",
377
+ "total_messages": self._total_messages,
378
+ "successful_processing": self._successful_processing,
379
+ "failed_processing": self._failed_processing,
380
+ "success_rate": success_rate,
381
+ "registered_scores": len(self.score_registry.get_scores())
382
+ if self._initialized
383
+ else 0,
384
+ "score_modules": score_stats,
385
+ }
386
+
387
+ except Exception as e:
388
+ self.logger.error(f"❌ Error collecting handler statistics: {e}")
389
+ return {"error": f"Statistics collection failed: {str(e)}"}
390
+
391
+ async def validate_system_health(self) -> dict[str, Any]:
392
+ """
393
+ Validate system health including all score modules and dependencies.
394
+
395
+ Returns:
396
+ Health check results for the entire system
397
+ """
398
+ try:
399
+ health_results = {
400
+ "overall_healthy": True,
401
+ "initialized": self._initialized,
402
+ "components": {},
403
+ "registered_scores": len(self.score_registry.get_scores())
404
+ if self._initialized
405
+ else 0,
406
+ }
407
+
408
+ # Check core dependencies
409
+ core_components = {
410
+ "messenger": self.messenger,
411
+ "cache_factory": self.cache_factory,
412
+ }
413
+
414
+ for component_name, component in core_components.items():
415
+ if component is not None:
416
+ health_results["components"][component_name] = "Available"
417
+ else:
418
+ health_results["components"][component_name] = "Missing"
419
+ health_results["overall_healthy"] = False
420
+
421
+ # Check score modules if initialized
422
+ if self._initialized:
423
+ scores = self.score_registry.get_scores()
424
+ for score in scores:
425
+ try:
426
+ # Basic validation check
427
+ is_valid = await score.validate_dependencies()
428
+ health_results["components"][score.score_name] = (
429
+ "Healthy" if is_valid else "Dependency Issues"
430
+ )
431
+ if not is_valid:
432
+ health_results["overall_healthy"] = False
433
+
434
+ except Exception as e:
435
+ health_results["components"][score.score_name] = (
436
+ f"Error: {str(e)}"
437
+ )
438
+ health_results["overall_healthy"] = False
439
+
440
+ return health_results
441
+
442
+ except Exception as e:
443
+ self.logger.error(f"❌ Error validating system health: {e}")
444
+ return {"overall_healthy": False, "error": f"Health check failed: {str(e)}"}
445
+
446
+ def __str__(self) -> str:
447
+ """String representation of the handler."""
448
+ return (
449
+ f"JSONCacheExampleHandler("
450
+ f"messages={self._total_messages}, "
451
+ f"success_rate={self._successful_processing / max(1, self._total_messages):.2%}, "
452
+ f"scores={len(self.score_registry.get_scores()) if self._initialized else 'pending'}, "
453
+ f"initialized={self._initialized}"
454
+ f")"
455
+ )
@@ -0,0 +1 @@
1
+ """JSON cache example models package."""