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,550 @@
1
+ """
2
+ Webhook processor factory for platform-agnostic processor creation.
3
+
4
+ This module provides factory classes for creating platform-specific processors,
5
+ schemas, and webhook containers based on incoming webhook data.
6
+ """
7
+
8
+ from typing import Any, Optional
9
+
10
+ from wappa.core.logging.logger import get_logger
11
+ from wappa.processors.base_processor import BaseWebhookProcessor, ProcessorError
12
+ from wappa.schemas.core.base_webhook import BaseWebhook
13
+ from wappa.schemas.core.types import ErrorCode, PlatformType
14
+
15
+
16
+ class PlatformDetector:
17
+ """
18
+ Platform detection utility for identifying messaging platforms from webhook data.
19
+
20
+ Uses various heuristics including URL patterns, headers, payload structure,
21
+ and platform-specific identifiers to determine the source platform.
22
+ """
23
+
24
+ @staticmethod
25
+ def detect_from_payload(payload: dict[str, Any]) -> PlatformType | None:
26
+ """
27
+ Detect platform from webhook payload structure.
28
+
29
+ Args:
30
+ payload: Raw webhook payload dictionary
31
+
32
+ Returns:
33
+ Detected platform type or None if cannot determine
34
+ """
35
+ if not isinstance(payload, dict):
36
+ return None
37
+
38
+ # WhatsApp Business API detection
39
+ if payload.get("object") == "whatsapp_business_account" and "entry" in payload:
40
+ return PlatformType.WHATSAPP
41
+
42
+ # Telegram Bot API detection
43
+ if "update_id" in payload and (
44
+ "message" in payload or "callback_query" in payload
45
+ ):
46
+ return PlatformType.TELEGRAM
47
+
48
+ # Microsoft Teams detection
49
+ if (
50
+ payload.get("type") in ["message", "messageReaction"]
51
+ and "channelData" in payload
52
+ and payload.get("channelData", {}).get("tenant")
53
+ ):
54
+ return PlatformType.TEAMS
55
+
56
+ # Instagram Messaging API detection (similar to WhatsApp but different object)
57
+ if payload.get("object") == "instagram" and "entry" in payload:
58
+ return PlatformType.INSTAGRAM
59
+
60
+ return None
61
+
62
+ @staticmethod
63
+ def detect_from_url(url_path: str) -> PlatformType | None:
64
+ """
65
+ Detect platform from webhook URL path.
66
+
67
+ Args:
68
+ url_path: The URL path of the webhook endpoint
69
+
70
+ Returns:
71
+ Detected platform type or None if cannot determine
72
+ """
73
+ path_lower = url_path.lower()
74
+
75
+ if "/whatsapp" in path_lower:
76
+ return PlatformType.WHATSAPP
77
+ elif "/telegram" in path_lower:
78
+ return PlatformType.TELEGRAM
79
+ elif "/teams" in path_lower:
80
+ return PlatformType.TEAMS
81
+ elif "/instagram" in path_lower:
82
+ return PlatformType.INSTAGRAM
83
+
84
+ return None
85
+
86
+ @staticmethod
87
+ def detect_from_headers(headers: dict[str, str]) -> PlatformType | None:
88
+ """
89
+ Detect platform from HTTP headers.
90
+
91
+ Args:
92
+ headers: Dictionary of HTTP headers
93
+
94
+ Returns:
95
+ Detected platform type or None if cannot determine
96
+ """
97
+ headers_lower = {k.lower(): v.lower() for k, v in headers.items()}
98
+
99
+ # WhatsApp/Facebook webhook signature
100
+ if "x-hub-signature-256" in headers_lower:
101
+ return PlatformType.WHATSAPP
102
+
103
+ # Telegram webhook
104
+ if "x-telegram-bot-api-secret-token" in headers_lower:
105
+ return PlatformType.TELEGRAM
106
+
107
+ # Microsoft Teams
108
+ if (
109
+ "authorization" in headers_lower
110
+ and "bearer" in headers_lower["authorization"]
111
+ ):
112
+ # Could be Teams, but need more specific detection
113
+ return None
114
+
115
+ return None
116
+
117
+ @classmethod
118
+ def detect_platform(
119
+ self,
120
+ payload: dict[str, Any],
121
+ url_path: str | None = None,
122
+ headers: dict[str, str] | None = None,
123
+ ) -> PlatformType:
124
+ """
125
+ Comprehensive platform detection using multiple heuristics.
126
+
127
+ Args:
128
+ payload: Raw webhook payload dictionary
129
+ url_path: Optional URL path for additional detection
130
+ headers: Optional HTTP headers for additional detection
131
+
132
+ Returns:
133
+ Detected platform type
134
+
135
+ Raises:
136
+ ProcessorError: If platform cannot be detected
137
+ """
138
+ # Try payload detection first (most reliable)
139
+ platform = self.detect_from_payload(payload)
140
+ if platform:
141
+ return platform
142
+
143
+ # Try URL path detection
144
+ if url_path:
145
+ platform = self.detect_from_url(url_path)
146
+ if platform:
147
+ return platform
148
+
149
+ # Try header detection
150
+ if headers:
151
+ platform = self.detect_from_headers(headers)
152
+ if platform:
153
+ return platform
154
+
155
+ # If all detection methods fail, raise error
156
+ raise ProcessorError(
157
+ "Unable to detect platform from webhook data",
158
+ ErrorCode.PLATFORM_ERROR,
159
+ PlatformType.WHATSAPP, # Default for error reporting
160
+ )
161
+
162
+
163
+ class ProcessorFactory:
164
+ """
165
+ Factory for creating platform-specific webhook processors.
166
+
167
+ Implements singleton pattern with processor caching for performance
168
+ and provides automatic platform detection and processor instantiation.
169
+ """
170
+
171
+ _instance: Optional["ProcessorFactory"] = None
172
+ _processors: dict[PlatformType, BaseWebhookProcessor] = {}
173
+
174
+ def __new__(cls) -> "ProcessorFactory":
175
+ """Singleton pattern implementation."""
176
+ if cls._instance is None:
177
+ cls._instance = super().__new__(cls)
178
+ return cls._instance
179
+
180
+ def __init__(self):
181
+ """Initialize the factory with logger."""
182
+ if not hasattr(self, "_initialized"):
183
+ self.logger = get_logger(__name__)
184
+ self._initialized = True
185
+
186
+ def get_processor(self, platform: PlatformType, **kwargs) -> BaseWebhookProcessor:
187
+ """
188
+ Get or create a processor for the specified platform.
189
+
190
+ Args:
191
+ platform: The platform type to get processor for
192
+ **kwargs: Additional processor configuration parameters
193
+
194
+ Returns:
195
+ Platform-specific processor instance
196
+
197
+ Raises:
198
+ ProcessorError: If processor cannot be created for platform
199
+ """
200
+ # Return cached processor if available
201
+ if platform in self._processors:
202
+ return self._processors[platform]
203
+
204
+ # Create new processor based on platform
205
+ processor = self._create_processor(platform, **kwargs)
206
+
207
+ # Cache the processor
208
+ self._processors[platform] = processor
209
+
210
+ # Use context-aware logger that automatically gets tenant/user context
211
+ logger = get_logger(__name__)
212
+ logger.info(f"Created and cached processor for platform: {platform.value}")
213
+ return processor
214
+
215
+ def _create_processor(
216
+ self, platform: PlatformType, **kwargs
217
+ ) -> BaseWebhookProcessor:
218
+ """
219
+ Create a new processor instance for the specified platform.
220
+
221
+ Args:
222
+ platform: The platform type to create processor for
223
+ **kwargs: Additional processor configuration parameters
224
+
225
+ Returns:
226
+ New processor instance
227
+
228
+ Raises:
229
+ ProcessorError: If processor class not found for platform
230
+ """
231
+ if platform == PlatformType.WHATSAPP:
232
+ from .whatsapp_processor import WhatsAppWebhookProcessor
233
+
234
+ return WhatsAppWebhookProcessor(**kwargs)
235
+
236
+ elif platform == PlatformType.TELEGRAM:
237
+ # TODO: Implement Telegram processor
238
+ # from .telegram_processor import TelegramWebhookProcessor
239
+ # return TelegramWebhookProcessor(**kwargs)
240
+ raise ProcessorError(
241
+ "Telegram processor not yet implemented",
242
+ ErrorCode.PLATFORM_ERROR,
243
+ platform,
244
+ )
245
+
246
+ elif platform == PlatformType.TEAMS:
247
+ # TODO: Implement Teams processor
248
+ # from .teams_processor import TeamsWebhookProcessor
249
+ # return TeamsWebhookProcessor(**kwargs)
250
+ raise ProcessorError(
251
+ "Teams processor not yet implemented",
252
+ ErrorCode.PLATFORM_ERROR,
253
+ platform,
254
+ )
255
+
256
+ elif platform == PlatformType.INSTAGRAM:
257
+ # TODO: Implement Instagram processor
258
+ # from .instagram_processor import InstagramWebhookProcessor
259
+ # return InstagramWebhookProcessor(**kwargs)
260
+ raise ProcessorError(
261
+ "Instagram processor not yet implemented",
262
+ ErrorCode.PLATFORM_ERROR,
263
+ platform,
264
+ )
265
+
266
+ else:
267
+ raise ProcessorError(
268
+ f"Unknown platform: {platform}", ErrorCode.PLATFORM_ERROR, platform
269
+ )
270
+
271
+ def get_processor_for_payload(
272
+ self,
273
+ payload: dict[str, Any],
274
+ url_path: str | None = None,
275
+ headers: dict[str, str] | None = None,
276
+ **kwargs,
277
+ ) -> BaseWebhookProcessor:
278
+ """
279
+ Auto-detect platform and return appropriate processor.
280
+
281
+ Args:
282
+ payload: Raw webhook payload dictionary
283
+ url_path: Optional URL path for platform detection
284
+ headers: Optional HTTP headers for platform detection
285
+ **kwargs: Additional processor configuration parameters
286
+
287
+ Returns:
288
+ Platform-specific processor instance
289
+
290
+ Raises:
291
+ ProcessorError: If platform cannot be detected or processor cannot be created
292
+ """
293
+ try:
294
+ # Detect platform from webhook data
295
+ platform = PlatformDetector.detect_platform(payload, url_path, headers)
296
+
297
+ self.logger.info(f"Auto-detected platform: {platform.value}")
298
+
299
+ # Get appropriate processor
300
+ return self.get_processor(platform, **kwargs)
301
+
302
+ except Exception as e:
303
+ self.logger.error(
304
+ f"Failed to get processor for payload: {e}", exc_info=True
305
+ )
306
+ raise ProcessorError(
307
+ f"Failed to create processor: {e}",
308
+ ErrorCode.PROCESSING_ERROR,
309
+ PlatformType.WHATSAPP, # Default for error reporting
310
+ )
311
+
312
+ def get_supported_platforms(self) -> set[PlatformType]:
313
+ """
314
+ Get the set of platforms supported by this factory.
315
+
316
+ Returns:
317
+ Set of supported platform types
318
+ """
319
+ return {
320
+ PlatformType.WHATSAPP,
321
+ # PlatformType.TELEGRAM, # TODO: Implement
322
+ # PlatformType.TEAMS, # TODO: Implement
323
+ # PlatformType.INSTAGRAM, # TODO: Implement
324
+ }
325
+
326
+ def is_platform_supported(self, platform: PlatformType) -> bool:
327
+ """
328
+ Check if a platform is supported by this factory.
329
+
330
+ Args:
331
+ platform: Platform type to check
332
+
333
+ Returns:
334
+ True if platform is supported, False otherwise
335
+ """
336
+ return platform in self.get_supported_platforms()
337
+
338
+ def get_processor_capabilities(
339
+ self, platform: PlatformType
340
+ ) -> dict[str, Any] | None:
341
+ """
342
+ Get capabilities for a specific platform processor.
343
+
344
+ Args:
345
+ platform: Platform type to get capabilities for
346
+
347
+ Returns:
348
+ Capabilities dictionary or None if platform not supported
349
+ """
350
+ if not self.is_platform_supported(platform):
351
+ return None
352
+
353
+ try:
354
+ processor = self.get_processor(platform)
355
+ return processor.capabilities.to_dict()
356
+ except Exception as e:
357
+ self.logger.error(f"Failed to get capabilities for {platform.value}: {e}")
358
+ return None
359
+
360
+ def clear_cache(self) -> None:
361
+ """Clear the processor cache, forcing recreation on next access."""
362
+ self._processors.clear()
363
+ self.logger.info("Processor cache cleared")
364
+
365
+ def get_cache_stats(self) -> dict[str, Any]:
366
+ """
367
+ Get processor cache statistics for monitoring.
368
+
369
+ Returns:
370
+ Dictionary with cache statistics
371
+ """
372
+ return {
373
+ "cached_processors": len(self._processors),
374
+ "cached_platforms": [p.value for p in self._processors.keys()],
375
+ "supported_platforms": [p.value for p in self.get_supported_platforms()],
376
+ "cache_size_bytes": sum(
377
+ len(str(processor)) for processor in self._processors.values()
378
+ ),
379
+ }
380
+
381
+
382
+ class WebhookFactory:
383
+ """
384
+ Factory for creating platform-specific webhook instances from raw payload data.
385
+
386
+ Combines platform detection with schema factory to provide a unified interface
387
+ for parsing webhook payloads from any supported platform.
388
+ """
389
+
390
+ def __init__(self):
391
+ """Initialize the webhook factory with schema factory."""
392
+ self.logger = get_logger(__name__)
393
+
394
+ # Import schema factory (avoid circular imports)
395
+ from wappa.schemas.factory import schema_factory
396
+
397
+ self.schema_factory = schema_factory
398
+
399
+ def create_webhook_from_payload(
400
+ self,
401
+ payload: dict[str, Any],
402
+ url_path: str | None = None,
403
+ headers: dict[str, str] | None = None,
404
+ **kwargs,
405
+ ) -> BaseWebhook:
406
+ """
407
+ Create a webhook instance from raw payload with automatic platform detection.
408
+
409
+ Args:
410
+ payload: Raw webhook payload dictionary
411
+ url_path: Optional URL path for platform detection
412
+ headers: Optional HTTP headers for platform detection
413
+ **kwargs: Additional parameters for webhook creation
414
+
415
+ Returns:
416
+ Parsed webhook instance with universal interface
417
+
418
+ Raises:
419
+ ProcessorError: If platform cannot be detected
420
+ ValidationError: If webhook data is invalid
421
+ """
422
+ try:
423
+ # Detect platform from payload
424
+ platform = PlatformDetector.detect_platform(payload, url_path, headers)
425
+
426
+ self.logger.info(
427
+ f"Creating webhook for detected platform: {platform.value}"
428
+ )
429
+
430
+ # Create webhook instance using schema factory
431
+ webhook_instance = self.schema_factory.create_webhook_instance(
432
+ platform=platform, webhook_data=payload, **kwargs
433
+ )
434
+
435
+ self.logger.debug(
436
+ f"Created webhook instance: {webhook_instance.get_webhook_id()}"
437
+ )
438
+
439
+ return webhook_instance
440
+
441
+ except Exception as e:
442
+ self.logger.error(
443
+ f"Failed to create webhook from payload: {e}", exc_info=True
444
+ )
445
+ raise ProcessorError(
446
+ f"Failed to create webhook: {e}",
447
+ ErrorCode.PROCESSING_ERROR,
448
+ PlatformType.WHATSAPP, # Default for error reporting
449
+ )
450
+
451
+ def create_webhook_for_platform(
452
+ self, platform: PlatformType, payload: dict[str, Any], **kwargs
453
+ ) -> BaseWebhook:
454
+ """
455
+ Create a webhook instance for a specific platform.
456
+
457
+ Args:
458
+ platform: The platform to create webhook for
459
+ payload: Raw webhook payload dictionary
460
+ **kwargs: Additional parameters for webhook creation
461
+
462
+ Returns:
463
+ Parsed webhook instance with universal interface
464
+
465
+ Raises:
466
+ ValidationError: If webhook data is invalid
467
+ """
468
+ try:
469
+ webhook_instance = self.schema_factory.create_webhook_instance(
470
+ platform=platform, webhook_data=payload, **kwargs
471
+ )
472
+
473
+ self.logger.debug(
474
+ f"Created {platform.value} webhook instance: {webhook_instance.get_webhook_id()}"
475
+ )
476
+
477
+ return webhook_instance
478
+
479
+ except Exception as e:
480
+ self.logger.error(
481
+ f"Failed to create {platform.value} webhook: {e}", exc_info=True
482
+ )
483
+ raise
484
+
485
+ def validate_webhook_payload(
486
+ self, payload: dict[str, Any], expected_platform: PlatformType | None = None
487
+ ) -> tuple[bool, str | None, PlatformType | None]:
488
+ """
489
+ Validate webhook payload and return platform information.
490
+
491
+ Args:
492
+ payload: Raw webhook payload dictionary
493
+ expected_platform: Optional expected platform for validation
494
+
495
+ Returns:
496
+ Tuple of (is_valid, error_message, detected_platform)
497
+ """
498
+ try:
499
+ # Detect platform
500
+ detected_platform = PlatformDetector.detect_platform(payload)
501
+
502
+ # Check if detected platform matches expected platform
503
+ if expected_platform and detected_platform != expected_platform:
504
+ return (
505
+ False,
506
+ (
507
+ f"Platform mismatch: expected {expected_platform.value}, "
508
+ f"detected {detected_platform.value}"
509
+ ),
510
+ detected_platform,
511
+ )
512
+
513
+ # Try to create webhook instance for validation
514
+ try:
515
+ self.schema_factory.create_webhook_instance(
516
+ platform=detected_platform, webhook_data=payload
517
+ )
518
+ return True, None, detected_platform
519
+
520
+ except Exception as e:
521
+ return False, f"Webhook validation failed: {e}", detected_platform
522
+
523
+ except Exception as e:
524
+ return False, f"Platform detection failed: {e}", None
525
+
526
+ def get_supported_platforms(self) -> set[PlatformType]:
527
+ """
528
+ Get platforms supported by this factory.
529
+
530
+ Returns:
531
+ Set of supported platform types
532
+ """
533
+ return self.schema_factory.webhook_registry.get_supported_platforms()
534
+
535
+ def is_platform_supported(self, platform: PlatformType) -> bool:
536
+ """
537
+ Check if a platform is supported by this factory.
538
+
539
+ Args:
540
+ platform: Platform type to check
541
+
542
+ Returns:
543
+ True if platform is supported, False otherwise
544
+ """
545
+ return self.schema_factory.webhook_registry.is_platform_supported(platform)
546
+
547
+
548
+ # Singleton instances for global access
549
+ processor_factory = ProcessorFactory()
550
+ webhook_factory = WebhookFactory()