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
wappa/__init__.py ADDED
@@ -0,0 +1,85 @@
1
+ """
2
+ Wappa - Open Source WhatsApp Business Framework
3
+
4
+ A clean, modern Python library for building WhatsApp Business applications
5
+ with minimal setup and maximum flexibility.
6
+ """
7
+
8
+ from .core.config.settings import settings
9
+ from .core.events import (
10
+ DefaultErrorHandler,
11
+ DefaultHandlerFactory,
12
+ DefaultMessageHandler,
13
+ DefaultStatusHandler,
14
+ ErrorLogStrategy,
15
+ MessageLogStrategy,
16
+ StatusLogStrategy,
17
+ WappaEventHandler,
18
+ WebhookURLFactory,
19
+ webhook_url_factory,
20
+ )
21
+
22
+ # Factory System
23
+ from .core.factory import WappaBuilder, WappaPlugin
24
+
25
+ # Core Plugins
26
+ from .core.plugins import (
27
+ AuthPlugin,
28
+ CORSPlugin,
29
+ CustomMiddlewarePlugin,
30
+ DatabasePlugin,
31
+ RateLimitPlugin,
32
+ RedisPlugin,
33
+ WebhookPlugin,
34
+ )
35
+ from .core.wappa_app import Wappa
36
+
37
+ # Database System
38
+ from .database import DatabaseAdapter, MySQLAdapter, PostgreSQLAdapter, SQLiteAdapter
39
+
40
+ # Messaging Interface
41
+ from .domain.interfaces.messaging_interface import IMessenger
42
+ from .messaging.whatsapp.messenger.whatsapp_messenger import WhatsAppMessenger
43
+
44
+ # Redis System
45
+ from .persistence.redis import RedisClient, RedisManager
46
+
47
+ __version__ = settings.version
48
+ __all__ = [
49
+ # Core Framework
50
+ "Wappa",
51
+ "WappaEventHandler",
52
+ # Factory System
53
+ "WappaBuilder",
54
+ "WappaPlugin",
55
+ # Core Plugins
56
+ "DatabasePlugin",
57
+ "RedisPlugin",
58
+ "WebhookPlugin",
59
+ "CORSPlugin",
60
+ "AuthPlugin",
61
+ "RateLimitPlugin",
62
+ "CustomMiddlewarePlugin",
63
+ # Database Adapters
64
+ "DatabaseAdapter",
65
+ "PostgreSQLAdapter",
66
+ "SQLiteAdapter",
67
+ "MySQLAdapter",
68
+ # Redis System
69
+ "RedisClient",
70
+ "RedisManager",
71
+ # Webhook Management
72
+ "WebhookURLFactory",
73
+ "webhook_url_factory",
74
+ # Default Handlers
75
+ "DefaultMessageHandler",
76
+ "DefaultStatusHandler",
77
+ "DefaultErrorHandler",
78
+ "DefaultHandlerFactory",
79
+ "MessageLogStrategy",
80
+ "StatusLogStrategy",
81
+ "ErrorLogStrategy",
82
+ # Messaging
83
+ "WhatsAppMessenger",
84
+ "IMessenger",
85
+ ]
wappa/api/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """API module for Wappa framework."""
@@ -0,0 +1,10 @@
1
+ """
2
+ API Controllers for Wappa framework.
3
+
4
+ Controllers handle business logic and coordinate between routes and services,
5
+ following clean architecture principles and single responsibility principle.
6
+ """
7
+
8
+ from .webhook_controller import WebhookController
9
+
10
+ __all__ = ["WebhookController"]
@@ -0,0 +1,441 @@
1
+ """
2
+ Webhook controller with per-request dependency injection.
3
+
4
+ This controller handles webhook processing with proper separation of concerns:
5
+ - Routes handle HTTP validation and responses
6
+ - Controller handles business logic and dependency management
7
+ - Per-request dependency injection for proper multi-tenant support
8
+ """
9
+
10
+ import asyncio
11
+ from typing import Any
12
+
13
+ from fastapi import HTTPException, Request
14
+ from fastapi.responses import PlainTextResponse
15
+
16
+ from wappa.core.events import WappaEventDispatcher
17
+ from wappa.core.logging.context import (
18
+ get_current_owner_context,
19
+ get_current_tenant_context,
20
+ )
21
+ from wappa.core.logging.logger import get_logger
22
+ from wappa.domain.factories import MessengerFactory
23
+ from wappa.processors.factory import processor_factory
24
+ from wappa.schemas.core.types import PlatformType
25
+
26
+
27
+ class WebhookController:
28
+ """
29
+ Webhook controller with per-request dependency injection.
30
+
31
+ Handles webhook verification and processing with proper multi-tenant support.
32
+ Creates messenger instances per request using the tenant extracted from middleware.
33
+
34
+ Key improvements over direct route handling:
35
+ - Proper tenant isolation (different tenant_id per request)
36
+ - Per-request dependency creation for multi-tenant support
37
+ - Clean separation of HTTP concerns from business logic
38
+ - Follows SRP and clean architecture principles
39
+ """
40
+
41
+ def __init__(self, event_dispatcher: WappaEventDispatcher):
42
+ """
43
+ Initialize webhook controller with event dispatcher.
44
+
45
+ Args:
46
+ event_dispatcher: WappaEventDispatcher instance with user's event handler
47
+ """
48
+ self.event_dispatcher = event_dispatcher
49
+ self.logger = get_logger(__name__)
50
+
51
+ # Get supported platforms for validation
52
+ self.supported_platforms = {platform.value.lower() for platform in PlatformType}
53
+
54
+ self.logger.debug(
55
+ f"WebhookController initialized with supported platforms: {self.supported_platforms}"
56
+ )
57
+
58
+ async def verify_webhook(
59
+ self,
60
+ request: Request,
61
+ platform: str,
62
+ hub_mode: str = None,
63
+ hub_verify_token: str = None,
64
+ hub_challenge: str = None,
65
+ ):
66
+ """
67
+ Handle webhook verification (challenge-response) for messaging platforms.
68
+
69
+ Args:
70
+ request: FastAPI request object
71
+ platform: The messaging platform (whatsapp, telegram, etc.)
72
+ hub_mode: Verification mode (usually "subscribe")
73
+ hub_verify_token: Token provided by platform for verification
74
+ hub_challenge: Challenge string to return if verification succeeds
75
+
76
+ Returns:
77
+ PlainTextResponse with challenge string if verification succeeds
78
+
79
+ Raises:
80
+ HTTPException: For validation failures or unsupported platforms
81
+ """
82
+ # Extract owner from middleware
83
+ owner_id = get_current_owner_context()
84
+
85
+ self.logger.info(
86
+ f"Webhook verification request for platform: {platform}, owner: {owner_id}"
87
+ )
88
+
89
+ # Validate platform
90
+ if not self._is_supported_platform(platform):
91
+ self.logger.error(
92
+ f"Unsupported platform for webhook verification: {platform}"
93
+ )
94
+ raise HTTPException(
95
+ status_code=400, detail=f"Unsupported platform: {platform}"
96
+ )
97
+
98
+ # Check if this is a verification request
99
+ if hub_mode == "subscribe" and hub_challenge:
100
+ # For now, accept any verification token (production should validate)
101
+ # TODO: Implement proper token validation per platform
102
+ if hub_verify_token:
103
+ self.logger.info(
104
+ f"✅ Webhook verification successful for {platform}, owner: {owner_id}"
105
+ )
106
+ return PlainTextResponse(content=hub_challenge)
107
+ else:
108
+ self.logger.error(f"❌ Missing verification token for {platform}")
109
+ raise HTTPException(
110
+ status_code=403, detail="Missing verification token"
111
+ )
112
+
113
+ # If not a verification request, return method not allowed
114
+ raise HTTPException(
115
+ status_code=405,
116
+ detail="Method not allowed for webhook verification endpoint",
117
+ )
118
+
119
+ async def process_webhook(
120
+ self,
121
+ request: Request,
122
+ platform: str,
123
+ payload: dict[str, Any],
124
+ ) -> dict[str, str]:
125
+ """
126
+ Process incoming webhook payload with per-request dependency injection.
127
+
128
+ This method creates fresh dependencies for each request using the correct
129
+ tenant_id extracted from middleware, enabling proper multi-tenant support.
130
+
131
+ Args:
132
+ request: FastAPI request object containing HTTP session and context
133
+ platform: The messaging platform (whatsapp, telegram, etc.)
134
+ payload: Parsed webhook JSON payload
135
+
136
+ Returns:
137
+ Dict with status confirmation
138
+
139
+ Raises:
140
+ HTTPException: For validation failures or processing errors
141
+ """
142
+ # Extract owner from middleware (URL owner_id)
143
+ owner_id = get_current_owner_context()
144
+
145
+ # ENHANCED DEBUGGING: Show context details
146
+ from wappa.core.logging.context import get_context_info
147
+ context_info = get_context_info()
148
+
149
+ self.logger.debug(
150
+ f"Processing webhook for platform: {platform}, owner: {owner_id}"
151
+ )
152
+ self.logger.debug(f"🔍 Full context info: {context_info}")
153
+ self.logger.debug(f"🌐 Request URL: {request.url.path}")
154
+ self.logger.debug(f"📨 Request method: {request.method}")
155
+
156
+ # Validate platform
157
+ if not self._is_supported_platform(platform):
158
+ self.logger.error(
159
+ f"Unsupported platform for webhook processing: {platform}"
160
+ )
161
+ raise HTTPException(
162
+ status_code=400, detail=f"Unsupported platform: {platform}"
163
+ )
164
+
165
+ # Validate owner
166
+ if not owner_id:
167
+ self.logger.error("Missing owner ID for webhook processing")
168
+ raise HTTPException(status_code=400, detail="Owner ID is required")
169
+
170
+ try:
171
+ # Get platform type for processing
172
+ platform_type = PlatformType(platform.lower())
173
+ except ValueError as e:
174
+ self.logger.error(f"Invalid platform type: {platform}")
175
+ raise HTTPException(
176
+ status_code=400, detail=f"Invalid platform: {platform}"
177
+ ) from e
178
+
179
+ try:
180
+ # Process webhook asynchronously to return immediate response
181
+ # This prevents WhatsApp timeout (5 seconds) and duplicate retries
182
+ asyncio.create_task(
183
+ self._process_webhook_async(
184
+ request=request,
185
+ platform_type=platform_type,
186
+ owner_id=owner_id,
187
+ payload=payload,
188
+ )
189
+ )
190
+
191
+ # Return immediate response (within milliseconds)
192
+ self.logger.debug(
193
+ f"✅ Webhook queued for background processing: {platform}"
194
+ )
195
+ return {"status": "accepted"}
196
+
197
+ except Exception as e:
198
+ self.logger.error(f"❌ Error processing webhook: {e}", exc_info=True)
199
+ raise HTTPException(
200
+ status_code=500, detail="Internal server error processing webhook"
201
+ ) from e
202
+
203
+ async def _process_webhook_async(
204
+ self,
205
+ request: Request,
206
+ platform_type: PlatformType,
207
+ owner_id: str,
208
+ payload: dict[str, Any],
209
+ ) -> None:
210
+ """
211
+ Asynchronous webhook processing with per-request dependency creation.
212
+
213
+ This is where the CRITICAL architectural improvement happens:
214
+ 1. Extract tenant_id from request (different per request)
215
+ 2. Create MessengerFactory per request
216
+ 3. Create tenant-specific messenger
217
+ 4. Inject fresh dependencies into event handler
218
+ 5. Process webhook with correct tenant context
219
+
220
+ Args:
221
+ request: FastAPI request object
222
+ platform_type: Platform type enum
223
+ owner_id: Owner identifier extracted from middleware (URL)
224
+ payload: Webhook payload to process
225
+ """
226
+ try:
227
+ self.logger.debug(
228
+ f"🚀 Starting async webhook processing for {platform_type.value}, owner: {owner_id}"
229
+ )
230
+
231
+ # First: Get appropriate processor and create Universal Webhook
232
+ processor = processor_factory.get_processor(platform_type)
233
+
234
+ if hasattr(processor, "create_universal_webhook"):
235
+ # Create Universal Webhook from processor (this sets the webhook context)
236
+ universal_webhook = await processor.create_universal_webhook(
237
+ payload=payload, tenant_id=owner_id
238
+ )
239
+
240
+ # After webhook processing, we now have the JSON tenant_id in context
241
+ webhook_tenant_id = get_current_tenant_context() # From webhook JSON
242
+ effective_tenant_id = (
243
+ webhook_tenant_id if webhook_tenant_id else owner_id
244
+ ) # Fallback to URL owner_id
245
+
246
+ self.logger.debug(
247
+ f"🎯 Using tenant_id: {effective_tenant_id} (webhook: {webhook_tenant_id}, owner: {owner_id})"
248
+ )
249
+
250
+ # CRITICAL: Create dependencies per request with WEBHOOK tenant_id (not URL)
251
+ await self._inject_per_request_dependencies(
252
+ request, platform_type, effective_tenant_id
253
+ )
254
+
255
+ self.logger.info(
256
+ f"✨ Created {type(universal_webhook).__name__} from {platform_type.value} (effective_tenant: {effective_tenant_id})"
257
+ )
258
+
259
+ # Dispatch to event handler via WappaEventDispatcher
260
+ dispatch_result = (
261
+ await self.event_dispatcher.dispatch_universal_webhook(
262
+ universal_webhook=universal_webhook,
263
+ tenant_id=effective_tenant_id, # Use webhook tenant_id
264
+ )
265
+ )
266
+
267
+ if dispatch_result.get("success", False):
268
+ self.logger.debug(
269
+ f"✅ Webhook processing completed successfully for tenant: {effective_tenant_id}"
270
+ )
271
+ else:
272
+ self.logger.error(
273
+ f"❌ Webhook dispatch failed for tenant {effective_tenant_id}: {dispatch_result.get('error')}"
274
+ )
275
+ else:
276
+ self.logger.error(
277
+ f"❌ Processor for {platform_type.value} does not support Universal Webhook Interface"
278
+ )
279
+
280
+ except Exception as e:
281
+ self.logger.error(
282
+ f"❌ Error in async webhook processing for owner {owner_id}: {e}",
283
+ exc_info=True,
284
+ )
285
+
286
+ async def _inject_per_request_dependencies(
287
+ self, request: Request, platform_type: PlatformType, tenant_id: str
288
+ ) -> None:
289
+ """
290
+ Inject fresh dependencies into event handler for this specific request.
291
+
292
+ This is the CORE of the architectural improvement:
293
+ - Creates MessengerFactory with request-specific HTTP session
294
+ - Creates messenger with request-specific tenant_id
295
+ - Injects into event handler for this request only
296
+
297
+ Args:
298
+ request: FastAPI request object
299
+ platform_type: Platform type for messenger creation
300
+ tenant_id: Request-specific tenant identifier
301
+ """
302
+ try:
303
+ # Get HTTP session from app state (shared for connection pooling - correct scope)
304
+ http_session = getattr(request.app.state, "http_session", None)
305
+ if not http_session:
306
+ self.logger.warning("No HTTP session available in app state")
307
+
308
+ # Create MessengerFactory per request (CRITICAL - not singleton!)
309
+ messenger_factory = MessengerFactory(http_session)
310
+
311
+ # Create messenger with request-specific tenant_id (CRITICAL!)
312
+ messenger = await messenger_factory.create_messenger(
313
+ platform=platform_type,
314
+ tenant_id=tenant_id, # This is different per request!
315
+ )
316
+
317
+ # Extract user_id from context (set by webhook processor) with fallback
318
+ from wappa.core.logging.context import get_current_user_context
319
+
320
+ user_id = get_current_user_context()
321
+ if not user_id:
322
+ raise RuntimeError(
323
+ "No user context available for cache factory creation"
324
+ )
325
+
326
+ # Create cache factory per request with context injection
327
+ cache_factory = self._create_cache_factory(request, tenant_id, user_id)
328
+
329
+ # Inject dependencies into event handler for THIS REQUEST
330
+ event_handler = self.event_dispatcher._event_handler
331
+ if event_handler:
332
+ event_handler.messenger = messenger
333
+ event_handler.cache_factory = cache_factory
334
+
335
+ self.logger.debug(
336
+ f"✅ Injected per-request dependencies for tenant {tenant_id}: "
337
+ f"messenger={messenger.__class__.__name__}, platform={platform_type.value}"
338
+ )
339
+ else:
340
+ self.logger.error(
341
+ "❌ No event handler available for dependency injection"
342
+ )
343
+
344
+ except Exception as e:
345
+ self.logger.error(
346
+ f"❌ Failed to inject per-request dependencies for tenant {tenant_id}: {e}",
347
+ exc_info=True,
348
+ )
349
+ raise RuntimeError(f"Dependency injection failed: {e}") from e
350
+
351
+ def _create_cache_factory(self, request: Request, tenant_id: str, user_id: str):
352
+ """
353
+ Create context-aware cache factory using unified plugin architecture.
354
+
355
+ Gets cache type from app.state.wappa_cache_type (set by WappaCorePlugin) as the
356
+ single source of truth. Validates Redis availability for redis cache type.
357
+
358
+ Args:
359
+ request: FastAPI request object
360
+ tenant_id: Tenant identifier for cache isolation
361
+ user_id: User identifier for user-specific cache contexts
362
+
363
+ Returns:
364
+ Cache factory instance with injected context
365
+
366
+ Raises:
367
+ RuntimeError: If Redis not available or cache creation fails
368
+ """
369
+ try:
370
+ # Single source of truth: app.state.wappa_cache_type (set by WappaCorePlugin)
371
+ cache_type = getattr(request.app.state, "wappa_cache_type", "memory")
372
+
373
+ self.logger.debug(
374
+ f"Creating {cache_type} cache factory for tenant: {tenant_id}, user: {user_id}"
375
+ )
376
+
377
+ # Validate Redis availability if redis cache type is requested
378
+ if cache_type == "redis":
379
+ if not hasattr(request.app.state, "redis_manager"):
380
+ raise RuntimeError(
381
+ "Redis cache requested but RedisPlugin not available. "
382
+ "Ensure Wappa(cache='redis') is used or RedisPlugin is added manually."
383
+ )
384
+
385
+ redis_manager = request.app.state.redis_manager
386
+ if not redis_manager.is_initialized():
387
+ raise RuntimeError(
388
+ "Redis cache requested but RedisManager not initialized. "
389
+ "Check Redis server connectivity and startup logs."
390
+ )
391
+
392
+ # Create cache factory using the detected cache type
393
+ from wappa.persistence.cache_factory import create_cache_factory
394
+
395
+ factory_class = create_cache_factory(cache_type)
396
+ cache_factory = factory_class(tenant_id=tenant_id, user_id=user_id)
397
+
398
+ self.logger.debug(f"✅ Created {cache_factory.__class__.__name__} successfully")
399
+ return cache_factory
400
+
401
+ except Exception as e:
402
+ self.logger.error(
403
+ f"❌ Failed to create cache factory for tenant {tenant_id}: {e}",
404
+ exc_info=True,
405
+ )
406
+ # FAIL FAST - no fallback to prevent silent failures
407
+ raise RuntimeError(f"Cache factory creation failed: {e}") from e
408
+
409
+ def _is_supported_platform(self, platform: str) -> bool:
410
+ """
411
+ Check if the platform is supported.
412
+
413
+ Args:
414
+ platform: Platform name to validate
415
+
416
+ Returns:
417
+ True if platform is supported, False otherwise
418
+ """
419
+ return platform.lower() in self.supported_platforms
420
+
421
+ def get_health_status(self) -> dict[str, Any]:
422
+ """
423
+ Get health status of the webhook controller.
424
+
425
+ Returns:
426
+ Dictionary with health status information
427
+ """
428
+ return {
429
+ "controller": "healthy",
430
+ "supported_platforms": list(self.supported_platforms),
431
+ "event_dispatcher": {
432
+ "initialized": self.event_dispatcher is not None,
433
+ "event_handler": (
434
+ self.event_dispatcher._event_handler.__class__.__name__
435
+ if self.event_dispatcher and self.event_dispatcher._event_handler
436
+ else None
437
+ ),
438
+ },
439
+ "dependency_injection": "per_request", # Key improvement!
440
+ "multi_tenant_support": True, # Key improvement!
441
+ }
@@ -0,0 +1,15 @@
1
+ """
2
+ FastAPI dependency injection for the Wappa WhatsApp Framework.
3
+
4
+ Provides reusable dependencies for controllers, services, and middleware
5
+ following clean architecture patterns.
6
+ """
7
+
8
+ from functools import lru_cache
9
+ from typing import Annotated
10
+
11
+ from fastapi import Depends, HTTPException, Request
12
+
13
+ # Import existing dependencies
14
+ from .whatsapp_dependencies import *
15
+ from .whatsapp_media_dependencies import *