wappa 0.1.8__py3-none-any.whl → 0.1.9__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 (78) hide show
  1. wappa/cli/examples/init/.env.example +33 -0
  2. wappa/cli/examples/init/app/__init__.py +0 -0
  3. wappa/cli/examples/init/app/main.py +8 -0
  4. wappa/cli/examples/init/app/master_event.py +8 -0
  5. wappa/cli/examples/json_cache_example/.env.example +33 -0
  6. wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
  7. wappa/cli/examples/json_cache_example/app/main.py +235 -0
  8. wappa/cli/examples/json_cache_example/app/master_event.py +419 -0
  9. wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
  10. wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +275 -0
  11. wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
  12. wappa/cli/examples/json_cache_example/app/scores/score_base.py +186 -0
  13. wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +248 -0
  14. wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +190 -0
  15. wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +260 -0
  16. wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +223 -0
  17. wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
  18. wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +176 -0
  19. wappa/cli/examples/json_cache_example/app/utils/message_utils.py +246 -0
  20. wappa/cli/examples/openai_transcript/.gitignore +63 -4
  21. wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
  22. wappa/cli/examples/openai_transcript/app/main.py +8 -0
  23. wappa/cli/examples/openai_transcript/app/master_event.py +53 -0
  24. wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
  25. wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +76 -0
  26. wappa/cli/examples/redis_cache_example/.env.example +33 -0
  27. wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
  28. wappa/cli/examples/redis_cache_example/app/main.py +234 -0
  29. wappa/cli/examples/redis_cache_example/app/master_event.py +419 -0
  30. wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +275 -0
  31. wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
  32. wappa/cli/examples/redis_cache_example/app/scores/score_base.py +186 -0
  33. wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +248 -0
  34. wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +190 -0
  35. wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +260 -0
  36. wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +223 -0
  37. wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
  38. wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +176 -0
  39. wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +246 -0
  40. wappa/cli/examples/simple_echo_example/.env.example +33 -0
  41. wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
  42. wappa/cli/examples/simple_echo_example/app/main.py +183 -0
  43. wappa/cli/examples/simple_echo_example/app/master_event.py +209 -0
  44. wappa/cli/examples/wappa_full_example/.env.example +33 -0
  45. wappa/cli/examples/wappa_full_example/.gitignore +63 -4
  46. wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
  47. wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
  48. wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +484 -0
  49. wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +551 -0
  50. wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +492 -0
  51. wappa/cli/examples/wappa_full_example/app/main.py +257 -0
  52. wappa/cli/examples/wappa_full_example/app/master_event.py +445 -0
  53. wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
  54. wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
  55. wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
  56. wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
  57. wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
  58. wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
  59. wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
  60. wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
  61. wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
  62. wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
  63. wappa/cli/examples/wappa_full_example/app/models/state_models.py +425 -0
  64. wappa/cli/examples/wappa_full_example/app/models/user_models.py +287 -0
  65. wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +301 -0
  66. wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
  67. wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +483 -0
  68. wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +473 -0
  69. wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +298 -0
  70. wappa/cli/main.py +8 -4
  71. {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/METADATA +1 -1
  72. {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/RECORD +75 -11
  73. wappa/cli/examples/init/pyproject.toml +0 -7
  74. wappa/cli/examples/simple_echo_example/.python-version +0 -1
  75. wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
  76. {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/WHEEL +0 -0
  77. {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/entry_points.txt +0 -0
  78. {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,234 @@
1
+ """
2
+ Redis Cache Example - SOLID Architecture Implementation
3
+
4
+ This is the main initialization file following SOLID principles:
5
+ - Single Responsibility: Pure initialization and configuration
6
+ - Open/Closed: Extensible through score module registration
7
+ - Liskov Substitution: Compatible with Wappa framework interface
8
+ - Interface Segregation: Clean dependency interfaces
9
+ - Dependency Inversion: Abstractions injected into concrete implementations
10
+
11
+ SETUP REQUIRED:
12
+ 1. Create a .env file with your WhatsApp Business API credentials:
13
+ WP_ACCESS_TOKEN=your_access_token_here
14
+ WP_PHONE_ID=your_phone_number_id_here
15
+ WP_BID=your_business_id_here
16
+
17
+ 2. Set up Redis:
18
+ REDIS_URL=redis://localhost:6379
19
+
20
+ DEMO FEATURES:
21
+ - SOLID architecture with separated concerns
22
+ - Master event orchestrator with score modules
23
+ - User management, message history, and state commands
24
+ - Cache statistics and monitoring
25
+ - Professional logging and error handling
26
+
27
+ USAGE:
28
+ - Direct Python: python -m app.main (from project root)
29
+ - FastAPI-style: uvicorn app.main:app --reload (from project root)
30
+ - Wappa CLI: wappa dev app.main (when CLI is available)
31
+ """
32
+
33
+ # Import core Wappa components
34
+ from wappa import Wappa, __version__
35
+ from wappa.core.config.settings import settings
36
+ from wappa.core.logging import get_logger
37
+
38
+ logger = get_logger(__name__)
39
+
40
+ # Import our SOLID architecture WappaEventHandler implementation
41
+ from .master_event import RedisCacheExampleHandler
42
+
43
+
44
+ def validate_configuration() -> bool:
45
+ """
46
+ Validate required configuration settings.
47
+
48
+ Returns:
49
+ True if configuration is valid, False otherwise
50
+ """
51
+
52
+ # Check required WhatsApp credentials
53
+ missing_configs = []
54
+
55
+ if not settings.wp_access_token:
56
+ missing_configs.append("WP_ACCESS_TOKEN")
57
+
58
+ if not settings.wp_phone_id:
59
+ missing_configs.append("WP_PHONE_ID")
60
+
61
+ if not settings.wp_bid:
62
+ missing_configs.append("WP_BID")
63
+
64
+ if not settings.has_redis:
65
+ missing_configs.append("REDIS_URL")
66
+
67
+ if missing_configs:
68
+ logger.error(f"❌ Missing required configuration: {', '.join(missing_configs)}")
69
+ logger.error("💡 Create a .env file with the required credentials")
70
+ return False
71
+
72
+ logger.info("✅ Configuration validation passed")
73
+ return True
74
+
75
+
76
+ def display_startup_information() -> None:
77
+ """
78
+ Display startup information and demo features.
79
+
80
+ Shows configuration status, architecture overview,
81
+ and available demo features.
82
+ """
83
+ print(f"🚀 Wappa v{__version__} - Redis Cache Example (SOLID Architecture)")
84
+ print("=" * 80)
85
+ print()
86
+
87
+ print("🏗️ *SOLID ARCHITECTURE IMPLEMENTATION:*")
88
+ print(" • Single Responsibility: Each score module has one specific concern")
89
+ print(" • Open/Closed: New score modules can be added without modification")
90
+ print(" • Liskov Substitution: All scores implement the same interface")
91
+ print(" • Interface Segregation: Clean, focused interfaces for each concern")
92
+ print(" • Dependency Inversion: Dependencies injected through abstractions")
93
+ print()
94
+
95
+ print("📋 *CONFIGURATION STATUS:*")
96
+ print(f" • Access Token: {'✅ Configured' if settings.wp_access_token else '❌ Missing'}")
97
+ print(f" • Phone ID: {settings.wp_phone_id if settings.wp_phone_id else '❌ Missing'}")
98
+ print(f" • Business ID: {'✅ Configured' if settings.wp_bid else '❌ Missing'}")
99
+ print(f" • Redis URL: {'✅ Configured' if settings.has_redis else '❌ Missing'}")
100
+ print(f" • Environment: {'🛠️ Development' if settings.is_development else '🚀 Production'}")
101
+ print()
102
+
103
+ print("🎯 *SCORE MODULES (BUSINESS LOGIC):*")
104
+ print(" • UserManagementScore: User profile and caching logic")
105
+ print(" • MessageHistoryScore: Message logging and /HISTORY command")
106
+ print(" • StateCommandsScore: /WAPPA and /EXIT command processing")
107
+ print(" • CacheStatisticsScore: Cache monitoring and /STATS command")
108
+ print()
109
+
110
+ print("🧪 *DEMO FEATURES:*")
111
+ print(" 1. Send any message → User profile created/updated + message logged")
112
+ print(" 2. Send '/WAPPA' → Enter special state with cache management")
113
+ print(" 3. While in WAPPA state → All messages replied with 'Hola Wapp@ ;)'")
114
+ print(" 4. Send '/EXIT' → Leave special state with session summary")
115
+ print(" 5. Send '/HISTORY' → View your last 20 messages with timestamps")
116
+ print(" 6. Send '/STATS' → View comprehensive cache statistics")
117
+ print()
118
+
119
+ print("💎 *REDIS CACHE ARCHITECTURE:*")
120
+ print(" • user_cache: User profiles with dependency injection")
121
+ print(" • table_cache: Message history with BaseModel auto-serialization")
122
+ print(" • state_cache: Command state management with TTL")
123
+ print(" • Comprehensive error handling and logging")
124
+ print()
125
+
126
+ print("🔧 *TECHNICAL FEATURES:*")
127
+ print(" • Dependency injection with interface abstractions")
128
+ print(" • Score module registry with automatic discovery")
129
+ print(" • Professional error handling and recovery")
130
+ print(" • Performance monitoring and statistics")
131
+ print(" • Comprehensive logging with structured output")
132
+ print(" • Proper WappaEventHandler implementation with all required methods")
133
+ print()
134
+
135
+
136
+ def create_wappa_application() -> Wappa:
137
+ """
138
+ Create and configure the Wappa application.
139
+
140
+ This function follows Single Responsibility Principle by focusing
141
+ only on application creation and initial configuration.
142
+
143
+ Returns:
144
+ Configured Wappa application instance
145
+ """
146
+
147
+ try:
148
+ # Create Wappa instance with Redis cache
149
+ logger.info("🏗️ Creating Wappa application with Redis cache...")
150
+ app = Wappa(cache="redis")
151
+
152
+ logger.info("✅ Wappa application created successfully")
153
+ return app
154
+
155
+ except Exception as e:
156
+ logger.error(f"❌ Failed to create Wappa application: {e}")
157
+ raise
158
+
159
+
160
+ def main() -> None:
161
+ """
162
+ Main application entry point.
163
+
164
+ Demonstrates SOLID principles in action:
165
+ - Single Responsibility: Each function has one clear purpose
166
+ - Dependency Inversion: Dependencies flow from abstractions
167
+ - Open/Closed: System is open for extension via score modules
168
+ """
169
+
170
+ logger.info("🚀 Starting Redis Cache Example with SOLID Architecture")
171
+
172
+ try:
173
+ # Display startup information
174
+ display_startup_information()
175
+
176
+ # Validate configuration before proceeding
177
+ if not validate_configuration():
178
+ logger.error("❌ Configuration validation failed - cannot start application")
179
+ return
180
+
181
+ # Create Wappa application
182
+ app = create_wappa_application()
183
+
184
+ # Create and set the SOLID WappaEventHandler implementation
185
+ handler = RedisCacheExampleHandler()
186
+ app.set_event_handler(handler)
187
+
188
+ logger.info("✅ Application initialization completed with SOLID WappaEventHandler")
189
+
190
+ print("🌐 Starting SOLID Redis cache demo server...")
191
+ print("💡 Press CTRL+C to stop the server")
192
+ print("=" * 80)
193
+ print()
194
+
195
+ # Start the application
196
+ # The framework will handle dependency injection automatically
197
+ app.run()
198
+
199
+ except KeyboardInterrupt:
200
+ logger.info("👋 Application stopped by user")
201
+ print("\n👋 Redis cache demo stopped by user")
202
+
203
+ except Exception as e:
204
+ logger.error(f"❌ Application startup error: {e}", exc_info=True)
205
+ print(f"\n❌ Server error: {e}")
206
+
207
+ finally:
208
+ logger.info("🏁 Redis cache demo completed")
209
+ print("🏁 Redis cache demo completed")
210
+
211
+
212
+ # Module-level app instance for uvicorn compatibility
213
+ # This enables: uvicorn main:app --reload
214
+
215
+ # Set up basic logging for module-level initialization
216
+
217
+
218
+ try:
219
+ logger.info("📦 Creating module-level Wappa application instance")
220
+ app = Wappa(cache="redis")
221
+
222
+ # Create and set the SOLID WappaEventHandler implementation
223
+ handler = RedisCacheExampleHandler()
224
+ app.set_event_handler(handler)
225
+
226
+ logger.info("✅ Module-level application instance ready with SOLID WappaEventHandler")
227
+
228
+ except Exception as e:
229
+ logger.error(f"❌ Failed to create module-level app instance: {e}")
230
+ raise
231
+
232
+
233
+ if __name__ == "__main__":
234
+ main()
@@ -0,0 +1,419 @@
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
+ import logging
15
+ from typing import Any, Dict
16
+
17
+ from .scores import AVAILABLE_SCORES, ScoreBase, ScoreDependencies
18
+ from .scores.score_base import ScoreRegistry
19
+ from .utils.message_utils import extract_user_data, sanitize_message_text
20
+ from wappa import WappaEventHandler
21
+ from wappa.webhooks import ErrorWebhook, IncomingMessageWebhook, StatusWebhook
22
+
23
+
24
+ class RedisCacheExampleHandler(WappaEventHandler):
25
+ """
26
+ Main WappaEventHandler implementation for Redis 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 Redis 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("🎯 RedisCacheExampleHandler initialized - ready for SOLID architecture setup")
51
+
52
+ async def process_message(self, webhook: IncomingMessageWebhook) -> None:
53
+ """
54
+ Main message processing method required by WappaEventHandler.
55
+
56
+ This method orchestrates score modules following SOLID principles and
57
+ demonstrates proper webhook processing with dependency injection.
58
+
59
+ Args:
60
+ webhook: Incoming message webhook to process
61
+ """
62
+ self._total_messages += 1
63
+ start_time = self._get_current_timestamp()
64
+
65
+ try:
66
+ # Initialize SOLID architecture on first message if not already done
67
+ if not self._initialized:
68
+ await self._initialize_solid_architecture()
69
+
70
+ # Extract basic user information for logging
71
+ user_data = extract_user_data(webhook)
72
+ user_id = user_data['user_id']
73
+ message_text = webhook.get_message_text() or "[NON-TEXT MESSAGE]"
74
+
75
+ self.logger.info(
76
+ f"📨 Processing message from {user_id}: "
77
+ f"{sanitize_message_text(message_text)[:50]}..."
78
+ )
79
+
80
+ # Execute score module processing pipeline
81
+ processing_result = await self._execute_score_pipeline(webhook)
82
+
83
+ # Record processing results
84
+ if processing_result['success']:
85
+ self._successful_processing += 1
86
+ processing_time = self._get_current_timestamp() - start_time
87
+
88
+ self.logger.info(
89
+ f"✅ Message processed successfully in {processing_time:.2f}s "
90
+ f"(processed by {processing_result['processed_count']} score modules)"
91
+ )
92
+ else:
93
+ self._failed_processing += 1
94
+ self.logger.warning(
95
+ f"⚠️ Message processing completed with issues: "
96
+ f"{processing_result.get('error', 'Unknown error')}"
97
+ )
98
+
99
+ # Send fallback response to user
100
+ await self._send_error_response(webhook, processing_result.get('error', 'Processing error'))
101
+
102
+ except Exception as e:
103
+ self._failed_processing += 1
104
+ self.logger.error(f"❌ Critical error in message processing: {e}", exc_info=True)
105
+ await self._send_error_response(webhook, f"System error: {str(e)}")
106
+
107
+ async def process_status(self, webhook: StatusWebhook) -> None:
108
+ """
109
+ Process status webhooks from WhatsApp Business API.
110
+
111
+ Args:
112
+ webhook: Status webhook containing delivery status information
113
+ """
114
+ try:
115
+ status_value = webhook.status.value
116
+ recipient = webhook.recipient_id
117
+
118
+ self.logger.info(f"📊 Message status: {status_value.upper()} for {recipient}")
119
+
120
+ # You can add custom status processing logic here
121
+ # For example, updating delivery statistics or handling failed deliveries
122
+
123
+ except Exception as e:
124
+ self.logger.error(f"❌ Error processing status webhook: {e}", exc_info=True)
125
+
126
+ async def process_error(self, webhook: ErrorWebhook) -> None:
127
+ """
128
+ Process error webhooks from WhatsApp Business API.
129
+
130
+ Args:
131
+ webhook: Error webhook containing error information
132
+ """
133
+ try:
134
+ error_count = webhook.get_error_count()
135
+ primary_error = webhook.get_primary_error()
136
+
137
+ self.logger.error(
138
+ f"🚨 WhatsApp API error: {error_count} errors, "
139
+ f"primary: {primary_error.error_code} - {primary_error.error_title}"
140
+ )
141
+
142
+ # Record error in statistics
143
+ self._failed_processing += 1
144
+
145
+ # You can add custom error handling logic here
146
+ # For example, alerting systems or retry mechanisms
147
+
148
+ except Exception as e:
149
+ self.logger.error(f"❌ Error processing error webhook: {e}", exc_info=True)
150
+
151
+ async def _initialize_solid_architecture(self) -> None:
152
+ """
153
+ Initialize SOLID architecture with score modules and dependency injection.
154
+
155
+ This method demonstrates Dependency Inversion Principle by injecting
156
+ abstractions and follows Single Responsibility Principle.
157
+ """
158
+ try:
159
+ if not self.validate_dependencies():
160
+ self.logger.error("❌ Dependencies not properly injected - cannot initialize SOLID architecture")
161
+ return
162
+
163
+ if not self.cache_factory:
164
+ self.logger.error("❌ Cache factory not available - cannot initialize SOLID architecture")
165
+ return
166
+
167
+ # Create cache instances from factory (Dependency Inversion)
168
+ user_cache = self.cache_factory.create_user_cache()
169
+ table_cache = self.cache_factory.create_table_cache()
170
+ state_cache = self.cache_factory.create_state_cache()
171
+
172
+ # Create dependencies container
173
+ dependencies = ScoreDependencies(
174
+ messenger=self.messenger,
175
+ user_cache=user_cache,
176
+ table_cache=table_cache,
177
+ state_cache=state_cache,
178
+ logger=self.logger
179
+ )
180
+
181
+ # Auto-register all available score modules (Open/Closed Principle)
182
+ registered_count = 0
183
+ for score_class in AVAILABLE_SCORES:
184
+ try:
185
+ # Instantiate score with dependency injection
186
+ score_instance = score_class(dependencies)
187
+ self.score_registry.register_score(score_instance)
188
+ registered_count += 1
189
+
190
+ self.logger.info(
191
+ f"✅ Registered score module: {score_instance.score_name}"
192
+ )
193
+
194
+ except Exception as e:
195
+ self.logger.error(
196
+ f"❌ Failed to register {score_class.__name__}: {e}"
197
+ )
198
+
199
+ self._initialized = True
200
+ self.logger.info(
201
+ f"🎯 SOLID architecture initialized successfully: {registered_count} score modules registered"
202
+ )
203
+
204
+ except Exception as e:
205
+ self.logger.error(f"❌ Critical error initializing SOLID architecture: {e}", exc_info=True)
206
+ raise
207
+
208
+ async def _execute_score_pipeline(self, webhook: IncomingMessageWebhook) -> Dict[str, Any]:
209
+ """
210
+ Execute the score module processing pipeline.
211
+
212
+ Processes webhook through all applicable score modules following
213
+ the Chain of Responsibility pattern.
214
+
215
+ Args:
216
+ webhook: Webhook to process
217
+
218
+ Returns:
219
+ Processing result with success status and metadata
220
+ """
221
+ try:
222
+ if not self._initialized:
223
+ return {
224
+ 'success': False,
225
+ 'error': 'SOLID architecture not initialized',
226
+ 'processed_count': 0
227
+ }
228
+
229
+ scores = self.score_registry.get_scores()
230
+ processed_count = 0
231
+ processing_errors = []
232
+
233
+ # Process webhook through all applicable score modules
234
+ for score in scores:
235
+ try:
236
+ # Check if score can handle this webhook (Interface Segregation)
237
+ can_handle = await score.can_handle(webhook)
238
+
239
+ if can_handle:
240
+ self.logger.debug(f"🎯 Processing with {score.score_name}")
241
+
242
+ # Process with the score module
243
+ success = await score.process(webhook)
244
+
245
+ if success:
246
+ processed_count += 1
247
+ self.logger.debug(f"✅ {score.score_name} completed successfully")
248
+ else:
249
+ processing_errors.append(f"{score.score_name}: Processing failed")
250
+ self.logger.warning(f"⚠️ {score.score_name} reported processing failure")
251
+ else:
252
+ self.logger.debug(f"⏭️ {score.score_name} skipped (cannot handle this webhook)")
253
+
254
+ except Exception as score_error:
255
+ processing_errors.append(f"{score.score_name}: {str(score_error)}")
256
+ self.logger.error(
257
+ f"❌ Error in {score.score_name}: {score_error}",
258
+ exc_info=True
259
+ )
260
+
261
+ # Determine overall success
262
+ overall_success = processed_count > 0 and len(processing_errors) == 0
263
+
264
+ return {
265
+ 'success': overall_success,
266
+ 'processed_count': processed_count,
267
+ 'total_scores': len(scores),
268
+ 'errors': processing_errors if processing_errors else None,
269
+ 'message': (
270
+ f"Processed by {processed_count}/{len(scores)} score modules"
271
+ + (f" with {len(processing_errors)} errors" if processing_errors else "")
272
+ )
273
+ }
274
+
275
+ except Exception as e:
276
+ self.logger.error(f"❌ Critical error in score pipeline: {e}", exc_info=True)
277
+ return {
278
+ 'success': False,
279
+ 'processed_count': 0,
280
+ 'error': f"Pipeline error: {str(e)}"
281
+ }
282
+
283
+ async def _send_error_response(self, webhook: IncomingMessageWebhook, error_details: str) -> None:
284
+ """
285
+ Send user-friendly error response when processing fails.
286
+
287
+ Args:
288
+ webhook: Original webhook that failed to process
289
+ error_details: Details about the error for logging
290
+ """
291
+ try:
292
+ user_data = extract_user_data(webhook)
293
+ user_id = user_data['user_id']
294
+
295
+ error_message = (
296
+ "🚨 SOLID Redis Cache Example\n\n"
297
+ "❌ An error occurred while processing your message.\n"
298
+ "Our team has been notified and will resolve this issue soon.\n\n"
299
+ "Please try again later or contact support if the problem persists."
300
+ )
301
+
302
+ result = await self.messenger.send_text(
303
+ recipient=user_id,
304
+ text=error_message,
305
+ reply_to_message_id=webhook.message.message_id
306
+ )
307
+
308
+ if result.success:
309
+ self.logger.info(f"🚨 Error response sent to {user_id}")
310
+ else:
311
+ self.logger.error(f"❌ Failed to send error response: {result.error}")
312
+
313
+ except Exception as e:
314
+ self.logger.error(f"❌ Error sending error response: {e}")
315
+
316
+ def _get_current_timestamp(self) -> float:
317
+ """Get current timestamp for performance measurement."""
318
+ import time
319
+ return time.time()
320
+
321
+ async def get_handler_statistics(self) -> Dict[str, Any]:
322
+ """
323
+ Get comprehensive handler and score module statistics.
324
+
325
+ Returns:
326
+ Dictionary with processing statistics and score module metrics
327
+ """
328
+ try:
329
+ # Calculate success rate
330
+ success_rate = (
331
+ (self._successful_processing / self._total_messages)
332
+ if self._total_messages > 0 else 0.0
333
+ )
334
+
335
+ # Get score-specific statistics if initialized
336
+ score_stats = {}
337
+ if self._initialized:
338
+ score_stats = self.score_registry.get_score_stats()
339
+
340
+ return {
341
+ 'handler_status': 'initialized' if self._initialized else 'pending_initialization',
342
+ 'total_messages': self._total_messages,
343
+ 'successful_processing': self._successful_processing,
344
+ 'failed_processing': self._failed_processing,
345
+ 'success_rate': success_rate,
346
+ 'registered_scores': len(self.score_registry.get_scores()) if self._initialized else 0,
347
+ 'score_modules': score_stats
348
+ }
349
+
350
+ except Exception as e:
351
+ self.logger.error(f"❌ Error collecting handler statistics: {e}")
352
+ return {
353
+ 'error': f"Statistics collection failed: {str(e)}"
354
+ }
355
+
356
+ async def validate_system_health(self) -> Dict[str, Any]:
357
+ """
358
+ Validate system health including all score modules and dependencies.
359
+
360
+ Returns:
361
+ Health check results for the entire system
362
+ """
363
+ try:
364
+ health_results = {
365
+ 'overall_healthy': True,
366
+ 'initialized': self._initialized,
367
+ 'components': {},
368
+ 'registered_scores': len(self.score_registry.get_scores()) if self._initialized else 0
369
+ }
370
+
371
+ # Check core dependencies
372
+ core_components = {
373
+ 'messenger': self.messenger,
374
+ 'cache_factory': self.cache_factory
375
+ }
376
+
377
+ for component_name, component in core_components.items():
378
+ if component is not None:
379
+ health_results['components'][component_name] = 'Available'
380
+ else:
381
+ health_results['components'][component_name] = 'Missing'
382
+ health_results['overall_healthy'] = False
383
+
384
+ # Check score modules if initialized
385
+ if self._initialized:
386
+ scores = self.score_registry.get_scores()
387
+ for score in scores:
388
+ try:
389
+ # Basic validation check
390
+ is_valid = await score.validate_dependencies()
391
+ health_results['components'][score.score_name] = (
392
+ 'Healthy' if is_valid else 'Dependency Issues'
393
+ )
394
+ if not is_valid:
395
+ health_results['overall_healthy'] = False
396
+
397
+ except Exception as e:
398
+ health_results['components'][score.score_name] = f'Error: {str(e)}'
399
+ health_results['overall_healthy'] = False
400
+
401
+ return health_results
402
+
403
+ except Exception as e:
404
+ self.logger.error(f"❌ Error validating system health: {e}")
405
+ return {
406
+ 'overall_healthy': False,
407
+ 'error': f"Health check failed: {str(e)}"
408
+ }
409
+
410
+ def __str__(self) -> str:
411
+ """String representation of the handler."""
412
+ return (
413
+ f"RedisCacheExampleHandler("
414
+ f"messages={self._total_messages}, "
415
+ f"success_rate={self._successful_processing/max(1, self._total_messages):.2%}, "
416
+ f"scores={len(self.score_registry.get_scores()) if self._initialized else 'pending'}, "
417
+ f"initialized={self._initialized}"
418
+ f")"
419
+ )