wappa 0.1.0__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 (211) hide show
  1. wappa/__init__.py +85 -0
  2. wappa/api/__init__.py +1 -0
  3. wappa/api/controllers/__init__.py +10 -0
  4. wappa/api/controllers/webhook_controller.py +441 -0
  5. wappa/api/dependencies/__init__.py +15 -0
  6. wappa/api/dependencies/whatsapp_dependencies.py +220 -0
  7. wappa/api/dependencies/whatsapp_media_dependencies.py +26 -0
  8. wappa/api/middleware/__init__.py +7 -0
  9. wappa/api/middleware/error_handler.py +158 -0
  10. wappa/api/middleware/owner.py +99 -0
  11. wappa/api/middleware/request_logging.py +184 -0
  12. wappa/api/routes/__init__.py +6 -0
  13. wappa/api/routes/health.py +102 -0
  14. wappa/api/routes/webhooks.py +211 -0
  15. wappa/api/routes/whatsapp/__init__.py +15 -0
  16. wappa/api/routes/whatsapp/whatsapp_interactive.py +429 -0
  17. wappa/api/routes/whatsapp/whatsapp_media.py +440 -0
  18. wappa/api/routes/whatsapp/whatsapp_messages.py +195 -0
  19. wappa/api/routes/whatsapp/whatsapp_specialized.py +516 -0
  20. wappa/api/routes/whatsapp/whatsapp_templates.py +431 -0
  21. wappa/api/routes/whatsapp_combined.py +35 -0
  22. wappa/cli/__init__.py +9 -0
  23. wappa/cli/main.py +199 -0
  24. wappa/core/__init__.py +6 -0
  25. wappa/core/config/__init__.py +5 -0
  26. wappa/core/config/settings.py +161 -0
  27. wappa/core/events/__init__.py +41 -0
  28. wappa/core/events/default_handlers.py +642 -0
  29. wappa/core/events/event_dispatcher.py +244 -0
  30. wappa/core/events/event_handler.py +247 -0
  31. wappa/core/events/webhook_factory.py +219 -0
  32. wappa/core/factory/__init__.py +15 -0
  33. wappa/core/factory/plugin.py +68 -0
  34. wappa/core/factory/wappa_builder.py +326 -0
  35. wappa/core/logging/__init__.py +5 -0
  36. wappa/core/logging/context.py +100 -0
  37. wappa/core/logging/logger.py +343 -0
  38. wappa/core/plugins/__init__.py +34 -0
  39. wappa/core/plugins/auth_plugin.py +169 -0
  40. wappa/core/plugins/cors_plugin.py +128 -0
  41. wappa/core/plugins/custom_middleware_plugin.py +182 -0
  42. wappa/core/plugins/database_plugin.py +235 -0
  43. wappa/core/plugins/rate_limit_plugin.py +183 -0
  44. wappa/core/plugins/redis_plugin.py +224 -0
  45. wappa/core/plugins/wappa_core_plugin.py +261 -0
  46. wappa/core/plugins/webhook_plugin.py +253 -0
  47. wappa/core/types.py +108 -0
  48. wappa/core/wappa_app.py +546 -0
  49. wappa/database/__init__.py +18 -0
  50. wappa/database/adapter.py +107 -0
  51. wappa/database/adapters/__init__.py +17 -0
  52. wappa/database/adapters/mysql_adapter.py +187 -0
  53. wappa/database/adapters/postgresql_adapter.py +169 -0
  54. wappa/database/adapters/sqlite_adapter.py +174 -0
  55. wappa/domain/__init__.py +28 -0
  56. wappa/domain/builders/__init__.py +5 -0
  57. wappa/domain/builders/message_builder.py +189 -0
  58. wappa/domain/entities/__init__.py +5 -0
  59. wappa/domain/enums/messenger_platform.py +123 -0
  60. wappa/domain/factories/__init__.py +6 -0
  61. wappa/domain/factories/media_factory.py +450 -0
  62. wappa/domain/factories/message_factory.py +497 -0
  63. wappa/domain/factories/messenger_factory.py +244 -0
  64. wappa/domain/interfaces/__init__.py +32 -0
  65. wappa/domain/interfaces/base_repository.py +94 -0
  66. wappa/domain/interfaces/cache_factory.py +85 -0
  67. wappa/domain/interfaces/cache_interface.py +199 -0
  68. wappa/domain/interfaces/expiry_repository.py +68 -0
  69. wappa/domain/interfaces/media_interface.py +311 -0
  70. wappa/domain/interfaces/messaging_interface.py +523 -0
  71. wappa/domain/interfaces/pubsub_repository.py +151 -0
  72. wappa/domain/interfaces/repository_factory.py +108 -0
  73. wappa/domain/interfaces/shared_state_repository.py +122 -0
  74. wappa/domain/interfaces/state_repository.py +123 -0
  75. wappa/domain/interfaces/tables_repository.py +215 -0
  76. wappa/domain/interfaces/user_repository.py +114 -0
  77. wappa/domain/interfaces/webhooks/__init__.py +1 -0
  78. wappa/domain/models/media_result.py +110 -0
  79. wappa/domain/models/platforms/__init__.py +15 -0
  80. wappa/domain/models/platforms/platform_config.py +104 -0
  81. wappa/domain/services/__init__.py +11 -0
  82. wappa/domain/services/tenant_credentials_service.py +56 -0
  83. wappa/messaging/__init__.py +7 -0
  84. wappa/messaging/whatsapp/__init__.py +1 -0
  85. wappa/messaging/whatsapp/client/__init__.py +5 -0
  86. wappa/messaging/whatsapp/client/whatsapp_client.py +417 -0
  87. wappa/messaging/whatsapp/handlers/__init__.py +13 -0
  88. wappa/messaging/whatsapp/handlers/whatsapp_interactive_handler.py +653 -0
  89. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +579 -0
  90. wappa/messaging/whatsapp/handlers/whatsapp_specialized_handler.py +434 -0
  91. wappa/messaging/whatsapp/handlers/whatsapp_template_handler.py +416 -0
  92. wappa/messaging/whatsapp/messenger/__init__.py +5 -0
  93. wappa/messaging/whatsapp/messenger/whatsapp_messenger.py +904 -0
  94. wappa/messaging/whatsapp/models/__init__.py +61 -0
  95. wappa/messaging/whatsapp/models/basic_models.py +65 -0
  96. wappa/messaging/whatsapp/models/interactive_models.py +287 -0
  97. wappa/messaging/whatsapp/models/media_models.py +215 -0
  98. wappa/messaging/whatsapp/models/specialized_models.py +304 -0
  99. wappa/messaging/whatsapp/models/template_models.py +261 -0
  100. wappa/persistence/cache_factory.py +93 -0
  101. wappa/persistence/json/__init__.py +14 -0
  102. wappa/persistence/json/cache_adapters.py +271 -0
  103. wappa/persistence/json/handlers/__init__.py +1 -0
  104. wappa/persistence/json/handlers/state_handler.py +250 -0
  105. wappa/persistence/json/handlers/table_handler.py +263 -0
  106. wappa/persistence/json/handlers/user_handler.py +213 -0
  107. wappa/persistence/json/handlers/utils/__init__.py +1 -0
  108. wappa/persistence/json/handlers/utils/file_manager.py +153 -0
  109. wappa/persistence/json/handlers/utils/key_factory.py +11 -0
  110. wappa/persistence/json/handlers/utils/serialization.py +121 -0
  111. wappa/persistence/json/json_cache_factory.py +76 -0
  112. wappa/persistence/json/storage_manager.py +285 -0
  113. wappa/persistence/memory/__init__.py +14 -0
  114. wappa/persistence/memory/cache_adapters.py +271 -0
  115. wappa/persistence/memory/handlers/__init__.py +1 -0
  116. wappa/persistence/memory/handlers/state_handler.py +250 -0
  117. wappa/persistence/memory/handlers/table_handler.py +280 -0
  118. wappa/persistence/memory/handlers/user_handler.py +213 -0
  119. wappa/persistence/memory/handlers/utils/__init__.py +1 -0
  120. wappa/persistence/memory/handlers/utils/key_factory.py +11 -0
  121. wappa/persistence/memory/handlers/utils/memory_store.py +317 -0
  122. wappa/persistence/memory/handlers/utils/ttl_manager.py +235 -0
  123. wappa/persistence/memory/memory_cache_factory.py +76 -0
  124. wappa/persistence/memory/storage_manager.py +235 -0
  125. wappa/persistence/redis/README.md +699 -0
  126. wappa/persistence/redis/__init__.py +11 -0
  127. wappa/persistence/redis/cache_adapters.py +285 -0
  128. wappa/persistence/redis/ops.py +880 -0
  129. wappa/persistence/redis/redis_cache_factory.py +71 -0
  130. wappa/persistence/redis/redis_client.py +231 -0
  131. wappa/persistence/redis/redis_handler/__init__.py +26 -0
  132. wappa/persistence/redis/redis_handler/state_handler.py +176 -0
  133. wappa/persistence/redis/redis_handler/table.py +158 -0
  134. wappa/persistence/redis/redis_handler/user.py +138 -0
  135. wappa/persistence/redis/redis_handler/utils/__init__.py +12 -0
  136. wappa/persistence/redis/redis_handler/utils/key_factory.py +32 -0
  137. wappa/persistence/redis/redis_handler/utils/serde.py +146 -0
  138. wappa/persistence/redis/redis_handler/utils/tenant_cache.py +268 -0
  139. wappa/persistence/redis/redis_manager.py +189 -0
  140. wappa/processors/__init__.py +6 -0
  141. wappa/processors/base_processor.py +262 -0
  142. wappa/processors/factory.py +550 -0
  143. wappa/processors/whatsapp_processor.py +810 -0
  144. wappa/schemas/__init__.py +6 -0
  145. wappa/schemas/core/__init__.py +71 -0
  146. wappa/schemas/core/base_message.py +499 -0
  147. wappa/schemas/core/base_status.py +322 -0
  148. wappa/schemas/core/base_webhook.py +312 -0
  149. wappa/schemas/core/types.py +253 -0
  150. wappa/schemas/core/webhook_interfaces/__init__.py +48 -0
  151. wappa/schemas/core/webhook_interfaces/base_components.py +293 -0
  152. wappa/schemas/core/webhook_interfaces/universal_webhooks.py +348 -0
  153. wappa/schemas/factory.py +754 -0
  154. wappa/schemas/webhooks/__init__.py +3 -0
  155. wappa/schemas/whatsapp/__init__.py +6 -0
  156. wappa/schemas/whatsapp/base_models.py +285 -0
  157. wappa/schemas/whatsapp/message_types/__init__.py +93 -0
  158. wappa/schemas/whatsapp/message_types/audio.py +350 -0
  159. wappa/schemas/whatsapp/message_types/button.py +267 -0
  160. wappa/schemas/whatsapp/message_types/contact.py +464 -0
  161. wappa/schemas/whatsapp/message_types/document.py +421 -0
  162. wappa/schemas/whatsapp/message_types/errors.py +195 -0
  163. wappa/schemas/whatsapp/message_types/image.py +424 -0
  164. wappa/schemas/whatsapp/message_types/interactive.py +430 -0
  165. wappa/schemas/whatsapp/message_types/location.py +416 -0
  166. wappa/schemas/whatsapp/message_types/order.py +372 -0
  167. wappa/schemas/whatsapp/message_types/reaction.py +271 -0
  168. wappa/schemas/whatsapp/message_types/sticker.py +328 -0
  169. wappa/schemas/whatsapp/message_types/system.py +317 -0
  170. wappa/schemas/whatsapp/message_types/text.py +411 -0
  171. wappa/schemas/whatsapp/message_types/unsupported.py +273 -0
  172. wappa/schemas/whatsapp/message_types/video.py +344 -0
  173. wappa/schemas/whatsapp/status_models.py +479 -0
  174. wappa/schemas/whatsapp/validators.py +454 -0
  175. wappa/schemas/whatsapp/webhook_container.py +438 -0
  176. wappa/webhooks/__init__.py +17 -0
  177. wappa/webhooks/core/__init__.py +71 -0
  178. wappa/webhooks/core/base_message.py +499 -0
  179. wappa/webhooks/core/base_status.py +322 -0
  180. wappa/webhooks/core/base_webhook.py +312 -0
  181. wappa/webhooks/core/types.py +253 -0
  182. wappa/webhooks/core/webhook_interfaces/__init__.py +48 -0
  183. wappa/webhooks/core/webhook_interfaces/base_components.py +293 -0
  184. wappa/webhooks/core/webhook_interfaces/universal_webhooks.py +441 -0
  185. wappa/webhooks/factory.py +754 -0
  186. wappa/webhooks/whatsapp/__init__.py +6 -0
  187. wappa/webhooks/whatsapp/base_models.py +285 -0
  188. wappa/webhooks/whatsapp/message_types/__init__.py +93 -0
  189. wappa/webhooks/whatsapp/message_types/audio.py +350 -0
  190. wappa/webhooks/whatsapp/message_types/button.py +267 -0
  191. wappa/webhooks/whatsapp/message_types/contact.py +464 -0
  192. wappa/webhooks/whatsapp/message_types/document.py +421 -0
  193. wappa/webhooks/whatsapp/message_types/errors.py +195 -0
  194. wappa/webhooks/whatsapp/message_types/image.py +424 -0
  195. wappa/webhooks/whatsapp/message_types/interactive.py +430 -0
  196. wappa/webhooks/whatsapp/message_types/location.py +416 -0
  197. wappa/webhooks/whatsapp/message_types/order.py +372 -0
  198. wappa/webhooks/whatsapp/message_types/reaction.py +271 -0
  199. wappa/webhooks/whatsapp/message_types/sticker.py +328 -0
  200. wappa/webhooks/whatsapp/message_types/system.py +317 -0
  201. wappa/webhooks/whatsapp/message_types/text.py +411 -0
  202. wappa/webhooks/whatsapp/message_types/unsupported.py +273 -0
  203. wappa/webhooks/whatsapp/message_types/video.py +344 -0
  204. wappa/webhooks/whatsapp/status_models.py +479 -0
  205. wappa/webhooks/whatsapp/validators.py +454 -0
  206. wappa/webhooks/whatsapp/webhook_container.py +438 -0
  207. wappa-0.1.0.dist-info/METADATA +269 -0
  208. wappa-0.1.0.dist-info/RECORD +211 -0
  209. wappa-0.1.0.dist-info/WHEEL +4 -0
  210. wappa-0.1.0.dist-info/entry_points.txt +2 -0
  211. wappa-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,244 @@
1
+ """
2
+ Event dispatcher for routing webhooks to event handlers.
3
+
4
+ Simplified version of the SimpleEventDispatcher focused on the core webhook routing pattern.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from typing import TYPE_CHECKING, Any
9
+
10
+ from wappa.core.logging.logger import get_logger
11
+
12
+ if TYPE_CHECKING:
13
+ from wappa.webhooks import (
14
+ ErrorWebhook,
15
+ IncomingMessageWebhook,
16
+ StatusWebhook,
17
+ UniversalWebhook,
18
+ )
19
+
20
+ from .event_handler import WappaEventHandler
21
+
22
+
23
+ class WappaEventDispatcher:
24
+ """
25
+ Event dispatcher service for Wappa applications.
26
+
27
+ Routes universal webhooks to the user's event handler with clean dispatch logic.
28
+ This is the core abstraction that developers interact with - they implement
29
+ WappaEventHandler methods and this dispatcher routes webhooks to them.
30
+ """
31
+
32
+ def __init__(self, event_handler: "WappaEventHandler"):
33
+ """
34
+ Initialize the event dispatcher with the user's event handler.
35
+
36
+ Args:
37
+ event_handler: WappaEventHandler instance with injected dependencies
38
+ """
39
+ # Use context-aware logger that automatically gets tenant/user context
40
+ self.logger = get_logger(__name__)
41
+ self._event_handler = event_handler
42
+
43
+ self.logger.info(
44
+ f"WappaEventDispatcher initialized with {event_handler.__class__.__name__}"
45
+ )
46
+
47
+ async def dispatch_universal_webhook(
48
+ self,
49
+ universal_webhook: "UniversalWebhook",
50
+ tenant_id: str | None = None,
51
+ **kwargs,
52
+ ) -> dict[str, Any]:
53
+ """
54
+ Dispatch Universal Webhook to the appropriate handler method.
55
+
56
+ Args:
57
+ universal_webhook: Universal webhook interface instance
58
+ tenant_id: Optional tenant ID for context
59
+ **kwargs: Additional dispatch parameters
60
+
61
+ Returns:
62
+ Dictionary with dispatch results
63
+ """
64
+ dispatch_start = datetime.utcnow()
65
+
66
+ try:
67
+ # Log webhook type and basic info
68
+ webhook_type = type(universal_webhook).__name__
69
+ platform_or_provider = getattr(
70
+ universal_webhook,
71
+ "platform",
72
+ getattr(universal_webhook, "provider", "unknown"),
73
+ )
74
+ if hasattr(platform_or_provider, "value"):
75
+ platform_or_provider = platform_or_provider.value
76
+
77
+ # Use emoji and shorter format for webhook processing
78
+ webhook_emoji = {
79
+ "IncomingMessageWebhook": "💬",
80
+ "StatusWebhook": "📊",
81
+ "ErrorWebhook": "🚨",
82
+ }.get(webhook_type, "📨")
83
+
84
+ self.logger.info(
85
+ f"{webhook_emoji} {webhook_type.replace('Webhook', '')} from {platform_or_provider}"
86
+ )
87
+
88
+ # Route to appropriate handler method
89
+ result = None
90
+ if universal_webhook.__class__.__name__ == "IncomingMessageWebhook":
91
+ result = await self._handle_message_webhook(universal_webhook)
92
+ elif universal_webhook.__class__.__name__ == "StatusWebhook":
93
+ result = await self._handle_status_webhook(universal_webhook)
94
+ elif universal_webhook.__class__.__name__ == "ErrorWebhook":
95
+ result = await self._handle_error_webhook(universal_webhook)
96
+ else:
97
+ return {
98
+ "success": False,
99
+ "error": f"Unknown webhook type: {webhook_type}",
100
+ "processed_at": datetime.utcnow().isoformat(),
101
+ }
102
+
103
+ # Add timing information if result exists
104
+ if result:
105
+ dispatch_end = datetime.utcnow()
106
+ result["dispatch_time"] = (
107
+ dispatch_end - dispatch_start
108
+ ).total_seconds()
109
+ result["processed_at"] = dispatch_end.isoformat()
110
+ self.logger.info(f"⚡ Processed in {result['dispatch_time']:.3f}s")
111
+
112
+ return result
113
+
114
+ except Exception as e:
115
+ self.logger.error(f"Error processing webhook: {e}", exc_info=True)
116
+ return {
117
+ "success": False,
118
+ "error": str(e),
119
+ "processed_at": datetime.utcnow().isoformat(),
120
+ }
121
+
122
+ async def _handle_message_webhook(
123
+ self, webhook: "IncomingMessageWebhook"
124
+ ) -> dict[str, Any]:
125
+ """
126
+ Handle incoming message webhook by routing to user's handler.
127
+
128
+ Args:
129
+ webhook: Incoming message webhook
130
+
131
+ Returns:
132
+ Dictionary with handling results
133
+ """
134
+ try:
135
+ # Enhanced message routing log
136
+ handler_name = self._event_handler.__class__.__name__.replace(
137
+ "EventHandler", ""
138
+ )
139
+ msg_type = webhook.get_message_type_name()
140
+
141
+ self.logger.info(
142
+ f"💬 {msg_type} message → {handler_name} (from: {webhook.user.user_id})"
143
+ )
144
+
145
+ # Call user's handle_message method
146
+ await self._event_handler.handle_message(webhook)
147
+
148
+ return {
149
+ "success": True,
150
+ "action": "message_processed",
151
+ "dispatcher": "WappaEventDispatcher",
152
+ "handler": self._event_handler.__class__.__name__,
153
+ }
154
+
155
+ except Exception as e:
156
+ self.logger.error(f"Error in message handler: {e}", exc_info=True)
157
+ return {
158
+ "success": False,
159
+ "error": str(e),
160
+ "action": "handler_error",
161
+ "dispatcher": "WappaEventDispatcher",
162
+ "handler": self._event_handler.__class__.__name__,
163
+ }
164
+
165
+ async def _handle_status_webhook(self, webhook: "StatusWebhook") -> dict[str, Any]:
166
+ """
167
+ Handle status webhook by routing to user's handler.
168
+
169
+ Args:
170
+ webhook: Status webhook
171
+
172
+ Returns:
173
+ Dictionary with handling results
174
+ """
175
+ try:
176
+ # Enhanced status logging without message ID
177
+ status_emoji = {
178
+ "sent": "📤",
179
+ "delivered": "✅",
180
+ "read": "👁️",
181
+ "failed": "❌",
182
+ }.get(webhook.status.value, "📋")
183
+
184
+ self.logger.info(
185
+ f"{status_emoji} Status Update: {webhook.status.value.upper()} "
186
+ f"(recipient: {webhook.recipient_id})"
187
+ )
188
+
189
+ # Call user's handle_status method (optional)
190
+ await self._event_handler.handle_status(webhook)
191
+
192
+ return {
193
+ "success": True,
194
+ "action": "status_processed",
195
+ "message_id": webhook.message_id,
196
+ "status": webhook.status.value,
197
+ "recipient": webhook.recipient_id,
198
+ }
199
+
200
+ except Exception as e:
201
+ self.logger.error(f"Error in status handler: {e}", exc_info=True)
202
+ return {
203
+ "success": False,
204
+ "error": str(e),
205
+ "action": "handler_error",
206
+ }
207
+
208
+ async def _handle_error_webhook(self, webhook: "ErrorWebhook") -> dict[str, Any]:
209
+ """
210
+ Handle error webhook by routing to user's handler.
211
+
212
+ Args:
213
+ webhook: Error webhook
214
+
215
+ Returns:
216
+ Dictionary with handling results
217
+ """
218
+ try:
219
+ error_count = webhook.get_error_count()
220
+ primary_error = webhook.get_primary_error()
221
+
222
+ self.logger.error(
223
+ f"Platform error webhook: {error_count} errors, "
224
+ f"primary: {primary_error.error_code} - {primary_error.error_title}"
225
+ )
226
+
227
+ # Call user's handle_error method (optional)
228
+ await self._event_handler.handle_error(webhook)
229
+
230
+ return {
231
+ "success": True,
232
+ "action": "error_processed",
233
+ "error_count": error_count,
234
+ "primary_error_code": primary_error.error_code,
235
+ "primary_error_title": primary_error.error_title,
236
+ }
237
+
238
+ except Exception as e:
239
+ self.logger.error(f"Error in error handler: {e}", exc_info=True)
240
+ return {
241
+ "success": False,
242
+ "error": str(e),
243
+ "action": "handler_error",
244
+ }
@@ -0,0 +1,247 @@
1
+ """
2
+ Base event handler class for Wappa applications.
3
+
4
+ This provides the interface that developers implement to handle WhatsApp webhooks.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import TYPE_CHECKING
9
+
10
+ from .default_handlers import (
11
+ DefaultErrorHandler,
12
+ DefaultMessageHandler,
13
+ DefaultStatusHandler,
14
+ )
15
+
16
+ if TYPE_CHECKING:
17
+ from wappa.domain.interfaces.messaging_interface import IMessenger
18
+ from wappa.webhooks import (
19
+ ErrorWebhook,
20
+ IncomingMessageWebhook,
21
+ StatusWebhook,
22
+ )
23
+
24
+
25
+ class WappaEventHandler(ABC):
26
+ """
27
+ Base class for handling WhatsApp events in Wappa applications.
28
+
29
+ Developers inherit from this class and implement the handler methods
30
+ to define their application's behavior for different webhook events.
31
+
32
+ Dependencies are injected PER-REQUEST by WebhookController:
33
+ - messenger: IMessenger created with correct tenant_id for each request
34
+ - cache_factory: Future cache factory for tenant-specific data persistence (currently None)
35
+
36
+ This ensures proper multi-tenant support where each webhook is processed
37
+ with the correct tenant-specific messenger instance.
38
+ """
39
+
40
+ def __init__(self):
41
+ """Initialize event handler with None dependencies (injected per-request)."""
42
+ self.messenger: IMessenger = None # Injected per-request by WebhookController
43
+ self.cache_factory = (
44
+ None # Injected per-request by WebhookController (placeholder)
45
+ )
46
+
47
+ # Set up logger with the actual class module name (not the base class)
48
+ from wappa.core.logging.logger import get_logger
49
+
50
+ self.logger = get_logger(self.__class__.__module__)
51
+
52
+ # Default handlers for all webhook types (core framework infrastructure)
53
+ self._default_message_handler = DefaultMessageHandler()
54
+ self._default_status_handler = DefaultStatusHandler()
55
+ self._default_error_handler = DefaultErrorHandler()
56
+
57
+ async def handle_message(self, webhook: "IncomingMessageWebhook") -> None:
58
+ """
59
+ Handle incoming message webhook using Template Method pattern.
60
+
61
+ This method provides a structured flow:
62
+ 1. Pre-processing: Default logging (non-optional framework infrastructure)
63
+ 2. User processing: Custom business logic (implemented in process_message)
64
+ 3. Post-processing: Optional cleanup/metrics
65
+
66
+ Args:
67
+ webhook: IncomingMessageWebhook containing the message data
68
+ """
69
+ # 1. Pre-processing: Framework logging (always happens)
70
+ await self._default_message_handler.log_incoming_message(webhook)
71
+
72
+ # 2. User processing: Custom business logic
73
+ await self.process_message(webhook)
74
+
75
+ # 3. Post-processing: Framework cleanup (future extensibility)
76
+ await self._default_message_handler.post_process_message(webhook)
77
+
78
+ @abstractmethod
79
+ async def process_message(self, webhook: "IncomingMessageWebhook") -> None:
80
+ """
81
+ Process incoming message webhook with custom business logic.
82
+
83
+ This is where users implement their message processing logic.
84
+ The framework handles logging automatically before calling this method.
85
+
86
+ Args:
87
+ webhook: IncomingMessageWebhook containing the message data
88
+ """
89
+ pass
90
+
91
+ async def handle_status(self, webhook: "StatusWebhook") -> None:
92
+ """
93
+ Handle message status updates using Template Method pattern.
94
+
95
+ This method provides a structured flow:
96
+ 1. Pre-processing: Default logging (non-optional framework infrastructure)
97
+ 2. User processing: Custom status logic (implemented in process_status)
98
+ 3. Post-processing: Framework cleanup (future extensibility)
99
+
100
+ Args:
101
+ webhook: StatusWebhook containing the status update
102
+ """
103
+ # 1. Pre-processing: Framework logging (always happens)
104
+ await self._default_status_handler.handle_status(webhook)
105
+
106
+ # 2. User processing: Custom business logic (optional)
107
+ await self.process_status(webhook)
108
+
109
+ async def process_status(self, webhook: "StatusWebhook") -> None:
110
+ """
111
+ Process status webhook with custom business logic.
112
+
113
+ Optional method - override if you need custom status processing.
114
+ The framework handles logging automatically before calling this method.
115
+
116
+ Args:
117
+ webhook: StatusWebhook containing the status update
118
+ """
119
+ # Default implementation: no additional processing
120
+ pass
121
+
122
+ async def handle_error(self, webhook: "ErrorWebhook") -> None:
123
+ """
124
+ Handle platform errors using Template Method pattern.
125
+
126
+ This method provides a structured flow:
127
+ 1. Pre-processing: Default logging and escalation (non-optional framework infrastructure)
128
+ 2. User processing: Custom error handling (implemented in process_error)
129
+ 3. Post-processing: Framework cleanup (future extensibility)
130
+
131
+ Args:
132
+ webhook: ErrorWebhook containing error information
133
+ """
134
+ # 1. Pre-processing: Framework logging and escalation (always happens)
135
+ await self._default_error_handler.handle_error(webhook)
136
+
137
+ # 2. User processing: Custom business logic (optional)
138
+ await self.process_error(webhook)
139
+
140
+ async def process_error(self, webhook: "ErrorWebhook") -> None:
141
+ """
142
+ Process error webhook with custom business logic.
143
+
144
+ Optional method - override if you need custom error processing.
145
+ The framework handles logging and escalation automatically before calling this method.
146
+
147
+ Args:
148
+ webhook: ErrorWebhook containing error information
149
+ """
150
+ # Default implementation: no additional processing
151
+ pass
152
+
153
+ def configure_default_handlers(
154
+ self,
155
+ message_handler: DefaultMessageHandler = None,
156
+ status_handler: DefaultStatusHandler = None,
157
+ error_handler: DefaultErrorHandler = None,
158
+ ):
159
+ """
160
+ Configure the default handlers used for message, status and error webhooks.
161
+
162
+ This allows users to customize the default behavior without overriding methods.
163
+
164
+ Args:
165
+ message_handler: Custom DefaultMessageHandler instance
166
+ status_handler: Custom DefaultStatusHandler instance
167
+ error_handler: Custom DefaultErrorHandler instance
168
+ """
169
+ if message_handler:
170
+ self._default_message_handler = message_handler
171
+ if status_handler:
172
+ self._default_status_handler = status_handler
173
+ if error_handler:
174
+ self._default_error_handler = error_handler
175
+
176
+ def get_message_stats(self):
177
+ """Get message processing statistics from default handler."""
178
+ return self._default_message_handler.get_stats()
179
+
180
+ def get_status_stats(self):
181
+ """Get status processing statistics from default handler."""
182
+ return self._default_status_handler.get_stats()
183
+
184
+ def get_error_stats(self):
185
+ """Get error processing statistics from default handler."""
186
+ return self._default_error_handler.get_stats()
187
+
188
+ def get_all_stats(self):
189
+ """Get all webhook processing statistics."""
190
+ return {
191
+ "messages": self.get_message_stats(),
192
+ "status": self.get_status_stats(),
193
+ "errors": self.get_error_stats(),
194
+ }
195
+
196
+ def validate_dependencies(self) -> bool:
197
+ """
198
+ Validate that required dependencies have been properly injected per-request.
199
+
200
+ Returns:
201
+ True if all required dependencies are available, False otherwise
202
+ """
203
+ if self.messenger is None:
204
+ self.logger.error(
205
+ "❌ Messenger dependency not injected - cannot send messages (check WebhookController)"
206
+ )
207
+ return False
208
+
209
+ # Cache factory is optional for now (placeholder)
210
+ if self.cache_factory is None:
211
+ self.logger.debug(
212
+ "ℹ️ Cache factory not injected - using placeholder (expected)"
213
+ )
214
+ else:
215
+ self.logger.debug(
216
+ f"✅ Cache factory injected: {type(self.cache_factory).__name__}"
217
+ )
218
+
219
+ self.logger.debug(
220
+ f"✅ Per-request dependencies validation passed - "
221
+ f"messenger: {self.messenger.__class__.__name__} "
222
+ f"(platform: {self.messenger.platform.value}, tenant: {self.messenger.tenant_id})"
223
+ )
224
+ return True
225
+
226
+ def get_dependency_status(self) -> dict:
227
+ """
228
+ Get the status of injected dependencies for debugging.
229
+
230
+ Returns:
231
+ Dictionary containing dependency injection status
232
+ """
233
+ return {
234
+ "messenger": {
235
+ "injected": self.messenger is not None,
236
+ "type": type(self.messenger).__name__ if self.messenger else None,
237
+ "platform": self.messenger.platform.value if self.messenger else None,
238
+ "tenant_id": self.messenger.tenant_id if self.messenger else None,
239
+ },
240
+ "cache_factory": {
241
+ "injected": self.cache_factory is not None,
242
+ "type": type(self.cache_factory).__name__
243
+ if self.cache_factory
244
+ else None,
245
+ "status": "placeholder" if self.cache_factory is None else "active",
246
+ },
247
+ }