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.
- wappa/__init__.py +85 -0
- wappa/api/__init__.py +1 -0
- wappa/api/controllers/__init__.py +10 -0
- wappa/api/controllers/webhook_controller.py +441 -0
- wappa/api/dependencies/__init__.py +15 -0
- wappa/api/dependencies/whatsapp_dependencies.py +220 -0
- wappa/api/dependencies/whatsapp_media_dependencies.py +26 -0
- wappa/api/middleware/__init__.py +7 -0
- wappa/api/middleware/error_handler.py +158 -0
- wappa/api/middleware/owner.py +99 -0
- wappa/api/middleware/request_logging.py +184 -0
- wappa/api/routes/__init__.py +6 -0
- wappa/api/routes/health.py +102 -0
- wappa/api/routes/webhooks.py +211 -0
- wappa/api/routes/whatsapp/__init__.py +15 -0
- wappa/api/routes/whatsapp/whatsapp_interactive.py +429 -0
- wappa/api/routes/whatsapp/whatsapp_media.py +440 -0
- wappa/api/routes/whatsapp/whatsapp_messages.py +195 -0
- wappa/api/routes/whatsapp/whatsapp_specialized.py +516 -0
- wappa/api/routes/whatsapp/whatsapp_templates.py +431 -0
- wappa/api/routes/whatsapp_combined.py +35 -0
- wappa/cli/__init__.py +9 -0
- wappa/cli/main.py +199 -0
- wappa/core/__init__.py +6 -0
- wappa/core/config/__init__.py +5 -0
- wappa/core/config/settings.py +161 -0
- wappa/core/events/__init__.py +41 -0
- wappa/core/events/default_handlers.py +642 -0
- wappa/core/events/event_dispatcher.py +244 -0
- wappa/core/events/event_handler.py +247 -0
- wappa/core/events/webhook_factory.py +219 -0
- wappa/core/factory/__init__.py +15 -0
- wappa/core/factory/plugin.py +68 -0
- wappa/core/factory/wappa_builder.py +326 -0
- wappa/core/logging/__init__.py +5 -0
- wappa/core/logging/context.py +100 -0
- wappa/core/logging/logger.py +343 -0
- wappa/core/plugins/__init__.py +34 -0
- wappa/core/plugins/auth_plugin.py +169 -0
- wappa/core/plugins/cors_plugin.py +128 -0
- wappa/core/plugins/custom_middleware_plugin.py +182 -0
- wappa/core/plugins/database_plugin.py +235 -0
- wappa/core/plugins/rate_limit_plugin.py +183 -0
- wappa/core/plugins/redis_plugin.py +224 -0
- wappa/core/plugins/wappa_core_plugin.py +261 -0
- wappa/core/plugins/webhook_plugin.py +253 -0
- wappa/core/types.py +108 -0
- wappa/core/wappa_app.py +546 -0
- wappa/database/__init__.py +18 -0
- wappa/database/adapter.py +107 -0
- wappa/database/adapters/__init__.py +17 -0
- wappa/database/adapters/mysql_adapter.py +187 -0
- wappa/database/adapters/postgresql_adapter.py +169 -0
- wappa/database/adapters/sqlite_adapter.py +174 -0
- wappa/domain/__init__.py +28 -0
- wappa/domain/builders/__init__.py +5 -0
- wappa/domain/builders/message_builder.py +189 -0
- wappa/domain/entities/__init__.py +5 -0
- wappa/domain/enums/messenger_platform.py +123 -0
- wappa/domain/factories/__init__.py +6 -0
- wappa/domain/factories/media_factory.py +450 -0
- wappa/domain/factories/message_factory.py +497 -0
- wappa/domain/factories/messenger_factory.py +244 -0
- wappa/domain/interfaces/__init__.py +32 -0
- wappa/domain/interfaces/base_repository.py +94 -0
- wappa/domain/interfaces/cache_factory.py +85 -0
- wappa/domain/interfaces/cache_interface.py +199 -0
- wappa/domain/interfaces/expiry_repository.py +68 -0
- wappa/domain/interfaces/media_interface.py +311 -0
- wappa/domain/interfaces/messaging_interface.py +523 -0
- wappa/domain/interfaces/pubsub_repository.py +151 -0
- wappa/domain/interfaces/repository_factory.py +108 -0
- wappa/domain/interfaces/shared_state_repository.py +122 -0
- wappa/domain/interfaces/state_repository.py +123 -0
- wappa/domain/interfaces/tables_repository.py +215 -0
- wappa/domain/interfaces/user_repository.py +114 -0
- wappa/domain/interfaces/webhooks/__init__.py +1 -0
- wappa/domain/models/media_result.py +110 -0
- wappa/domain/models/platforms/__init__.py +15 -0
- wappa/domain/models/platforms/platform_config.py +104 -0
- wappa/domain/services/__init__.py +11 -0
- wappa/domain/services/tenant_credentials_service.py +56 -0
- wappa/messaging/__init__.py +7 -0
- wappa/messaging/whatsapp/__init__.py +1 -0
- wappa/messaging/whatsapp/client/__init__.py +5 -0
- wappa/messaging/whatsapp/client/whatsapp_client.py +417 -0
- wappa/messaging/whatsapp/handlers/__init__.py +13 -0
- wappa/messaging/whatsapp/handlers/whatsapp_interactive_handler.py +653 -0
- wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +579 -0
- wappa/messaging/whatsapp/handlers/whatsapp_specialized_handler.py +434 -0
- wappa/messaging/whatsapp/handlers/whatsapp_template_handler.py +416 -0
- wappa/messaging/whatsapp/messenger/__init__.py +5 -0
- wappa/messaging/whatsapp/messenger/whatsapp_messenger.py +904 -0
- wappa/messaging/whatsapp/models/__init__.py +61 -0
- wappa/messaging/whatsapp/models/basic_models.py +65 -0
- wappa/messaging/whatsapp/models/interactive_models.py +287 -0
- wappa/messaging/whatsapp/models/media_models.py +215 -0
- wappa/messaging/whatsapp/models/specialized_models.py +304 -0
- wappa/messaging/whatsapp/models/template_models.py +261 -0
- wappa/persistence/cache_factory.py +93 -0
- wappa/persistence/json/__init__.py +14 -0
- wappa/persistence/json/cache_adapters.py +271 -0
- wappa/persistence/json/handlers/__init__.py +1 -0
- wappa/persistence/json/handlers/state_handler.py +250 -0
- wappa/persistence/json/handlers/table_handler.py +263 -0
- wappa/persistence/json/handlers/user_handler.py +213 -0
- wappa/persistence/json/handlers/utils/__init__.py +1 -0
- wappa/persistence/json/handlers/utils/file_manager.py +153 -0
- wappa/persistence/json/handlers/utils/key_factory.py +11 -0
- wappa/persistence/json/handlers/utils/serialization.py +121 -0
- wappa/persistence/json/json_cache_factory.py +76 -0
- wappa/persistence/json/storage_manager.py +285 -0
- wappa/persistence/memory/__init__.py +14 -0
- wappa/persistence/memory/cache_adapters.py +271 -0
- wappa/persistence/memory/handlers/__init__.py +1 -0
- wappa/persistence/memory/handlers/state_handler.py +250 -0
- wappa/persistence/memory/handlers/table_handler.py +280 -0
- wappa/persistence/memory/handlers/user_handler.py +213 -0
- wappa/persistence/memory/handlers/utils/__init__.py +1 -0
- wappa/persistence/memory/handlers/utils/key_factory.py +11 -0
- wappa/persistence/memory/handlers/utils/memory_store.py +317 -0
- wappa/persistence/memory/handlers/utils/ttl_manager.py +235 -0
- wappa/persistence/memory/memory_cache_factory.py +76 -0
- wappa/persistence/memory/storage_manager.py +235 -0
- wappa/persistence/redis/README.md +699 -0
- wappa/persistence/redis/__init__.py +11 -0
- wappa/persistence/redis/cache_adapters.py +285 -0
- wappa/persistence/redis/ops.py +880 -0
- wappa/persistence/redis/redis_cache_factory.py +71 -0
- wappa/persistence/redis/redis_client.py +231 -0
- wappa/persistence/redis/redis_handler/__init__.py +26 -0
- wappa/persistence/redis/redis_handler/state_handler.py +176 -0
- wappa/persistence/redis/redis_handler/table.py +158 -0
- wappa/persistence/redis/redis_handler/user.py +138 -0
- wappa/persistence/redis/redis_handler/utils/__init__.py +12 -0
- wappa/persistence/redis/redis_handler/utils/key_factory.py +32 -0
- wappa/persistence/redis/redis_handler/utils/serde.py +146 -0
- wappa/persistence/redis/redis_handler/utils/tenant_cache.py +268 -0
- wappa/persistence/redis/redis_manager.py +189 -0
- wappa/processors/__init__.py +6 -0
- wappa/processors/base_processor.py +262 -0
- wappa/processors/factory.py +550 -0
- wappa/processors/whatsapp_processor.py +810 -0
- wappa/schemas/__init__.py +6 -0
- wappa/schemas/core/__init__.py +71 -0
- wappa/schemas/core/base_message.py +499 -0
- wappa/schemas/core/base_status.py +322 -0
- wappa/schemas/core/base_webhook.py +312 -0
- wappa/schemas/core/types.py +253 -0
- wappa/schemas/core/webhook_interfaces/__init__.py +48 -0
- wappa/schemas/core/webhook_interfaces/base_components.py +293 -0
- wappa/schemas/core/webhook_interfaces/universal_webhooks.py +348 -0
- wappa/schemas/factory.py +754 -0
- wappa/schemas/webhooks/__init__.py +3 -0
- wappa/schemas/whatsapp/__init__.py +6 -0
- wappa/schemas/whatsapp/base_models.py +285 -0
- wappa/schemas/whatsapp/message_types/__init__.py +93 -0
- wappa/schemas/whatsapp/message_types/audio.py +350 -0
- wappa/schemas/whatsapp/message_types/button.py +267 -0
- wappa/schemas/whatsapp/message_types/contact.py +464 -0
- wappa/schemas/whatsapp/message_types/document.py +421 -0
- wappa/schemas/whatsapp/message_types/errors.py +195 -0
- wappa/schemas/whatsapp/message_types/image.py +424 -0
- wappa/schemas/whatsapp/message_types/interactive.py +430 -0
- wappa/schemas/whatsapp/message_types/location.py +416 -0
- wappa/schemas/whatsapp/message_types/order.py +372 -0
- wappa/schemas/whatsapp/message_types/reaction.py +271 -0
- wappa/schemas/whatsapp/message_types/sticker.py +328 -0
- wappa/schemas/whatsapp/message_types/system.py +317 -0
- wappa/schemas/whatsapp/message_types/text.py +411 -0
- wappa/schemas/whatsapp/message_types/unsupported.py +273 -0
- wappa/schemas/whatsapp/message_types/video.py +344 -0
- wappa/schemas/whatsapp/status_models.py +479 -0
- wappa/schemas/whatsapp/validators.py +454 -0
- wappa/schemas/whatsapp/webhook_container.py +438 -0
- wappa/webhooks/__init__.py +17 -0
- wappa/webhooks/core/__init__.py +71 -0
- wappa/webhooks/core/base_message.py +499 -0
- wappa/webhooks/core/base_status.py +322 -0
- wappa/webhooks/core/base_webhook.py +312 -0
- wappa/webhooks/core/types.py +253 -0
- wappa/webhooks/core/webhook_interfaces/__init__.py +48 -0
- wappa/webhooks/core/webhook_interfaces/base_components.py +293 -0
- wappa/webhooks/core/webhook_interfaces/universal_webhooks.py +441 -0
- wappa/webhooks/factory.py +754 -0
- wappa/webhooks/whatsapp/__init__.py +6 -0
- wappa/webhooks/whatsapp/base_models.py +285 -0
- wappa/webhooks/whatsapp/message_types/__init__.py +93 -0
- wappa/webhooks/whatsapp/message_types/audio.py +350 -0
- wappa/webhooks/whatsapp/message_types/button.py +267 -0
- wappa/webhooks/whatsapp/message_types/contact.py +464 -0
- wappa/webhooks/whatsapp/message_types/document.py +421 -0
- wappa/webhooks/whatsapp/message_types/errors.py +195 -0
- wappa/webhooks/whatsapp/message_types/image.py +424 -0
- wappa/webhooks/whatsapp/message_types/interactive.py +430 -0
- wappa/webhooks/whatsapp/message_types/location.py +416 -0
- wappa/webhooks/whatsapp/message_types/order.py +372 -0
- wappa/webhooks/whatsapp/message_types/reaction.py +271 -0
- wappa/webhooks/whatsapp/message_types/sticker.py +328 -0
- wappa/webhooks/whatsapp/message_types/system.py +317 -0
- wappa/webhooks/whatsapp/message_types/text.py +411 -0
- wappa/webhooks/whatsapp/message_types/unsupported.py +273 -0
- wappa/webhooks/whatsapp/message_types/video.py +344 -0
- wappa/webhooks/whatsapp/status_models.py +479 -0
- wappa/webhooks/whatsapp/validators.py +454 -0
- wappa/webhooks/whatsapp/webhook_container.py +438 -0
- wappa-0.1.0.dist-info/METADATA +269 -0
- wappa-0.1.0.dist-info/RECORD +211 -0
- wappa-0.1.0.dist-info/WHEEL +4 -0
- wappa-0.1.0.dist-info/entry_points.txt +2 -0
- 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
|
+
}
|