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,546 @@
1
+ """
2
+ Main Wappa application class.
3
+
4
+ This is the core class that developers use to create and run their WhatsApp applications.
5
+ The class now uses WappaBuilder internally for unified plugin-based architecture.
6
+ """
7
+
8
+ import subprocess
9
+ import sys
10
+ from collections.abc import Callable
11
+ from typing import TYPE_CHECKING
12
+
13
+ import uvicorn
14
+ from fastapi import FastAPI
15
+
16
+ from wappa.api.routes.webhooks import create_webhook_router
17
+
18
+ from .config.settings import settings
19
+ from .events import WappaEventDispatcher
20
+ from .factory.wappa_builder import WappaBuilder
21
+ from .logging.logger import get_app_logger
22
+ from .plugins.wappa_core_plugin import WappaCorePlugin
23
+ from .types import CacheType, CacheTypeOptions, validate_cache_type
24
+
25
+ if TYPE_CHECKING:
26
+ from .events import WappaEventHandler
27
+ from .factory.plugin import WappaPlugin
28
+
29
+
30
+ # Traditional lifespan management has been moved to WappaCorePlugin
31
+ # This enables unified plugin-based architecture for all Wappa applications
32
+
33
+
34
+ class Wappa:
35
+ """
36
+ Main Wappa application class with unified plugin-based architecture.
37
+
38
+ This class now uses WappaBuilder internally, providing both simple usage
39
+ for beginners and advanced extensibility for power users. All functionality
40
+ is implemented through plugins, ensuring consistency and maintainability.
41
+
42
+ Simple Usage:
43
+ app = Wappa()
44
+ app.set_event_handler(MyEventHandler())
45
+ app.run()
46
+
47
+ Advanced Usage:
48
+ app = Wappa(cache="redis")
49
+ app.add_plugin(DatabasePlugin(...))
50
+ app.add_startup_hook(my_startup_func)
51
+ app.set_event_handler(MyEventHandler())
52
+ app.run()
53
+ """
54
+
55
+ def __init__(self, cache: CacheTypeOptions = "memory", config: dict | None = None):
56
+ """
57
+ Initialize Wappa application with plugin-based architecture.
58
+
59
+ Automatically creates WappaBuilder with WappaCorePlugin and adds
60
+ cache-specific plugins based on the cache type.
61
+
62
+ Args:
63
+ cache: Cache type ('memory', 'redis', 'json')
64
+ config: Optional configuration overrides for FastAPI app
65
+
66
+ Raises:
67
+ ValueError: If cache type is not supported
68
+ """
69
+ # Validate and convert cache type
70
+ self.cache_type = validate_cache_type(cache)
71
+ self.config = config or {}
72
+ self._event_handler: WappaEventHandler | None = None
73
+ self._app: FastAPI | None = None
74
+ self._asgi: FastAPI | None = None
75
+
76
+ # Initialize WappaBuilder with core plugin
77
+ self._builder = WappaBuilder()
78
+ self._core_plugin = WappaCorePlugin(cache_type=self.cache_type)
79
+ self._builder.add_plugin(self._core_plugin)
80
+
81
+ # Automatically add cache-specific plugins
82
+ self._auto_add_cache_plugins()
83
+
84
+ # Apply any FastAPI configuration overrides
85
+ if self.config:
86
+ self._builder.configure(**self.config)
87
+
88
+ logger = get_app_logger()
89
+ logger.debug(
90
+ f"🏗️ Wappa initialized with cache_type={self.cache_type.value}, "
91
+ f"plugins={len(self._builder.plugins)}, config_overrides={bool(self.config)}"
92
+ )
93
+
94
+ def _auto_add_cache_plugins(self) -> None:
95
+ """
96
+ Automatically add cache-specific plugins based on cache type.
97
+
98
+ This method ensures users get the appropriate cache infrastructure
99
+ automatically without manual plugin management.
100
+ """
101
+ logger = get_app_logger()
102
+
103
+ if self.cache_type == CacheType.REDIS:
104
+ from .plugins.redis_plugin import RedisPlugin
105
+
106
+ redis_plugin = RedisPlugin()
107
+ self._builder.add_plugin(redis_plugin)
108
+ logger.debug("🔴 Auto-added RedisPlugin for Redis cache type")
109
+ elif self.cache_type == CacheType.JSON:
110
+ # Future: JSONCachePlugin implementation
111
+ logger.debug("📄 JSON cache type selected (plugin not yet implemented)")
112
+ else: # CacheType.MEMORY
113
+ logger.debug("💾 Memory cache type selected (no additional plugin needed)")
114
+
115
+ def set_event_handler(self, handler: "WappaEventHandler") -> None:
116
+ """
117
+ Set the event handler for this application.
118
+
119
+ Dependencies are now injected per-request by the WebhookController,
120
+ ensuring proper multi-tenant support and correct tenant isolation.
121
+
122
+ Args:
123
+ handler: WappaEventHandler instance to handle webhooks
124
+ """
125
+ # Store handler reference - dependencies will be injected per request
126
+ self._event_handler = handler
127
+
128
+ logger = get_app_logger()
129
+ logger.debug(f"Event handler set: {handler.__class__.__name__}")
130
+
131
+ def set_app(self, app: FastAPI) -> None:
132
+ """
133
+ Set a pre-built FastAPI application instance.
134
+
135
+ This method allows users to provide a FastAPI app that was created
136
+ using WappaBuilder or other advanced configuration methods, while
137
+ still using the Wappa class for event handler integration and running.
138
+
139
+ Args:
140
+ app: Pre-configured FastAPI application instance
141
+
142
+ Example:
143
+ # Create advanced app with WappaBuilder
144
+ builder = WappaBuilder()
145
+ app = await (builder
146
+ .add_plugin(DatabasePlugin(...))
147
+ .add_plugin(RedisPlugin())
148
+ .build())
149
+
150
+ # Use with Wappa for event handling
151
+ wappa = Wappa()
152
+ wappa.set_app(app)
153
+ wappa.set_event_handler(MyHandler())
154
+ wappa.run()
155
+ """
156
+ self._app = app
157
+
158
+ logger = get_app_logger()
159
+ logger.debug(
160
+ f"Pre-built FastAPI app set: {app.title} v{app.version} "
161
+ f"(middleware: {len(app.user_middleware)}, routes: {len(app.routes)})"
162
+ )
163
+
164
+ @property
165
+ def asgi(self) -> FastAPI:
166
+ """
167
+ Return a FastAPI ASGI application, building synchronously if needed.
168
+
169
+ This property enables uvicorn reload compatibility by providing a synchronous
170
+ way to access the FastAPI app. Plugin configuration is deferred to lifespan
171
+ hooks to maintain async initialization while keeping this property sync.
172
+
173
+ Returns:
174
+ FastAPI ASGI application instance
175
+
176
+ Raises:
177
+ ValueError: If no event handler has been set
178
+ """
179
+ if self._asgi is None:
180
+ self._asgi = self._build_asgi_sync()
181
+ return self._asgi
182
+
183
+ def _build_asgi_sync(self) -> FastAPI:
184
+ """
185
+ Build FastAPI application synchronously using WappaBuilder.
186
+
187
+ Creates the FastAPI app with WappaBuilder's unified lifespan management.
188
+ Plugin configuration is deferred to lifespan startup hooks to maintain
189
+ proper async initialization.
190
+ """
191
+ if not self._event_handler:
192
+ raise ValueError(
193
+ "Must set event handler with set_event_handler() before accessing .asgi property"
194
+ )
195
+
196
+ logger = get_app_logger()
197
+ logger.debug("Building FastAPI ASGI app synchronously using WappaBuilder")
198
+
199
+ # Configure FastAPI settings for builder
200
+ self._builder.configure(
201
+ title="Wappa Application",
202
+ description="WhatsApp Business application built with Wappa framework",
203
+ version=settings.version,
204
+ docs_url="/docs" if settings.is_development else None,
205
+ redoc_url="/redoc" if settings.is_development else None,
206
+ )
207
+
208
+ # Use WappaBuilder.build() - creates app with lifespan,
209
+ # defers plugin configuration to startup hooks
210
+ app = self._builder.build()
211
+
212
+ # Add webhook routes to the built app
213
+ dispatcher = WappaEventDispatcher(self._event_handler)
214
+ webhook_router = create_webhook_router(dispatcher)
215
+ app.include_router(webhook_router)
216
+
217
+ logger.info(
218
+ f"✅ Wappa ASGI app built synchronously - cache: {self.cache_type.value}, "
219
+ f"plugins: {len(self._builder.plugins)}, "
220
+ f"event_handler: {self._event_handler.__class__.__name__}"
221
+ )
222
+
223
+ return app
224
+
225
+ def create_app(self) -> FastAPI:
226
+ """
227
+ Create FastAPI application.
228
+
229
+ This method directly uses WappaBuilder.build() which creates the app
230
+ with unified lifespan management.
231
+
232
+ Returns:
233
+ Configured FastAPI application instance
234
+ """
235
+ if self._asgi is None:
236
+ self._asgi = self._build_asgi_sync()
237
+ return self._asgi
238
+
239
+ def add_plugin(self, plugin: "WappaPlugin") -> "Wappa":
240
+ """
241
+ Add a plugin to extend Wappa functionality.
242
+
243
+ This method provides access to the underlying WappaBuilder's plugin system
244
+ while maintaining the simple Wappa interface. Plugins should be added
245
+ before calling create_app() or run().
246
+
247
+ Args:
248
+ plugin: WappaPlugin instance to add
249
+
250
+ Returns:
251
+ Self for method chaining
252
+
253
+ Example:
254
+ from wappa.plugins import DatabasePlugin
255
+
256
+ app = Wappa(cache="redis")
257
+ app.add_plugin(DatabasePlugin("postgresql://...", PostgreSQLAdapter()))
258
+ app.set_event_handler(MyEventHandler())
259
+ app.run()
260
+ """
261
+ self._builder.add_plugin(plugin)
262
+
263
+ logger = get_app_logger()
264
+ logger.debug(f"Plugin added to Wappa: {plugin.__class__.__name__}")
265
+
266
+ return self
267
+
268
+ def add_startup_hook(self, hook: Callable, priority: int = 50) -> "Wappa":
269
+ """
270
+ Add a startup hook to be executed during application startup.
271
+
272
+ This provides access to the underlying WappaBuilder's hook system.
273
+ Hooks are executed in priority order during app startup.
274
+
275
+ Args:
276
+ hook: Async callable that takes (app: FastAPI) -> None
277
+ priority: Execution priority (lower numbers run first)
278
+
279
+ Returns:
280
+ Self for method chaining
281
+
282
+ Example:
283
+ async def my_startup(app: FastAPI):
284
+ print("My service is starting!")
285
+
286
+ app = Wappa()
287
+ app.add_startup_hook(my_startup, priority=30)
288
+ app.set_event_handler(MyEventHandler())
289
+ app.run()
290
+ """
291
+ self._builder.add_startup_hook(hook, priority)
292
+
293
+ logger = get_app_logger()
294
+ hook_name = getattr(hook, "__name__", "anonymous_hook")
295
+ logger.debug(f"Startup hook added to Wappa: {hook_name} (priority: {priority})")
296
+
297
+ return self
298
+
299
+ def add_shutdown_hook(self, hook: Callable, priority: int = 50) -> "Wappa":
300
+ """
301
+ Add a shutdown hook to be executed during application shutdown.
302
+
303
+ This provides access to the underlying WappaBuilder's hook system.
304
+ Hooks are executed in reverse priority order during app shutdown.
305
+
306
+ Args:
307
+ hook: Async callable that takes (app: FastAPI) -> None
308
+ priority: Execution priority (higher numbers run first in shutdown)
309
+
310
+ Returns:
311
+ Self for method chaining
312
+
313
+ Example:
314
+ async def my_shutdown(app: FastAPI):
315
+ print("Cleaning up my service!")
316
+
317
+ app = Wappa()
318
+ app.add_shutdown_hook(my_shutdown, priority=30)
319
+ app.set_event_handler(MyEventHandler())
320
+ app.run()
321
+ """
322
+ self._builder.add_shutdown_hook(hook, priority)
323
+
324
+ logger = get_app_logger()
325
+ hook_name = getattr(hook, "__name__", "anonymous_hook")
326
+ logger.debug(
327
+ f"Shutdown hook added to Wappa: {hook_name} (priority: {priority})"
328
+ )
329
+
330
+ return self
331
+
332
+ def add_middleware(self, middleware_class: type, priority: int = 50, **kwargs) -> "Wappa":
333
+ """
334
+ Add middleware to the application with priority ordering.
335
+
336
+ This provides access to the underlying WappaBuilder's middleware system.
337
+ Priority determines execution order:
338
+ - Lower numbers run first (outer middleware)
339
+ - Higher numbers run last (inner middleware)
340
+ - Default priority is 50
341
+
342
+ Args:
343
+ middleware_class: Middleware class to add
344
+ priority: Execution priority (lower = outer, higher = inner)
345
+ **kwargs: Middleware configuration parameters
346
+
347
+ Returns:
348
+ Self for method chaining
349
+
350
+ Example:
351
+ from fastapi.middleware.cors import CORSMiddleware
352
+
353
+ app = Wappa(cache="redis")
354
+ app.add_middleware(CORSMiddleware, allow_origins=["*"], priority=30)
355
+ app.set_event_handler(MyHandler())
356
+ app.run()
357
+ """
358
+ self._builder.add_middleware(middleware_class, priority, **kwargs)
359
+
360
+ logger = get_app_logger()
361
+ logger.debug(
362
+ f"Middleware added to Wappa: {middleware_class.__name__} (priority: {priority})"
363
+ )
364
+
365
+ return self
366
+
367
+ def add_router(self, router, **kwargs) -> "Wappa":
368
+ """
369
+ Add a router to the application.
370
+
371
+ This provides access to the underlying WappaBuilder's router system.
372
+
373
+ Args:
374
+ router: FastAPI router to include
375
+ **kwargs: Arguments for app.include_router()
376
+
377
+ Returns:
378
+ Self for method chaining
379
+
380
+ Example:
381
+ from fastapi import APIRouter
382
+
383
+ custom_router = APIRouter()
384
+
385
+ app = Wappa(cache="redis")
386
+ app.add_router(custom_router, prefix="/api/v1", tags=["custom"])
387
+ app.set_event_handler(MyHandler())
388
+ app.run()
389
+ """
390
+ self._builder.add_router(router, **kwargs)
391
+
392
+ logger = get_app_logger()
393
+ router_name = getattr(router, "prefix", "router")
394
+ logger.debug(f"Router added to Wappa: {router_name} with config: {kwargs}")
395
+
396
+ return self
397
+
398
+ def configure(self, **overrides) -> "Wappa":
399
+ """
400
+ Override default FastAPI configuration.
401
+
402
+ This provides access to the underlying WappaBuilder's configuration system.
403
+
404
+ Args:
405
+ **overrides: FastAPI constructor arguments to override
406
+
407
+ Returns:
408
+ Self for method chaining
409
+
410
+ Example:
411
+ app = Wappa(cache="redis")
412
+ app.configure(
413
+ title="My WhatsApp Bot",
414
+ version="2.0.0",
415
+ description="Custom bot with advanced features"
416
+ )
417
+ app.set_event_handler(MyHandler())
418
+ app.run()
419
+ """
420
+ self._builder.configure(**overrides)
421
+
422
+ logger = get_app_logger()
423
+ logger.debug(f"FastAPI configuration overrides added: {list(overrides.keys())}")
424
+
425
+ return self
426
+
427
+ def run(self, host: str = "0.0.0.0", port: int | None = None, **kwargs) -> None:
428
+ """
429
+ Run the Wappa application using uvicorn.
430
+
431
+ Args:
432
+ host: Host to bind to
433
+ port: Port to bind to (defaults to settings.port)
434
+ **kwargs: Additional uvicorn configuration
435
+ """
436
+ # Use port from settings if not provided
437
+ if port is None:
438
+ port = settings.port
439
+
440
+ # Use settings.is_development to determine mode
441
+ dev_mode = settings.is_development
442
+
443
+ logger = get_app_logger()
444
+ logger.info(f"Starting Wappa v{settings.version} server on {host}:{port}")
445
+ logger.info(f"Mode: {'development' if dev_mode else 'production'}")
446
+
447
+ if dev_mode:
448
+ logger.info("🔄 Development mode: auto-reload enabled")
449
+ self._run_dev_mode(host, port, **kwargs)
450
+ else:
451
+ logger.info("🚀 Production mode: running directly")
452
+ self._run_production(host, port, **kwargs)
453
+
454
+ def _run_production(self, host: str, port: int, **kwargs) -> None:
455
+ """Run in production mode without auto-reload."""
456
+ logger = get_app_logger()
457
+
458
+ uvicorn_config = {
459
+ "host": host,
460
+ "port": port,
461
+ "reload": False,
462
+ "log_level": settings.log_level.lower(),
463
+ **kwargs,
464
+ }
465
+
466
+ logger.info("Starting production server with .asgi property...")
467
+ # Use the .asgi property which handles sync build + lifespan initialization
468
+ uvicorn.run(self.asgi, **uvicorn_config)
469
+
470
+ def _run_dev_mode(self, host: str, port: int, **kwargs) -> None:
471
+ """
472
+ Run in development mode with uvicorn auto-reload.
473
+
474
+ Uses uvicorn with import string for proper reload functionality.
475
+ Requires module-level 'app' variable containing Wappa instance.
476
+ """
477
+ import inspect
478
+
479
+ logger = get_app_logger()
480
+
481
+ # Get the calling module to build import string
482
+ frame = inspect.currentframe()
483
+ while frame and frame.f_globals.get("__name__") == __name__:
484
+ frame = frame.f_back
485
+
486
+ if not frame:
487
+ logger.error("❌ Cannot detect calling script for dev mode")
488
+ raise RuntimeError(
489
+ "Cannot locate calling script for dev mode.\n"
490
+ "Make sure you're running from a Python file, not a REPL."
491
+ )
492
+
493
+ module_name = frame.f_globals.get("__name__")
494
+ if module_name in (None, "__main__"):
495
+ # Try to guess from file path
496
+ file_path = frame.f_globals.get("__file__")
497
+ if not file_path:
498
+ raise RuntimeError("Dev mode requires running from a file, not a REPL.")
499
+
500
+ # Convert file path to module name (e.g., examples/main.py -> examples.main)
501
+ import os.path
502
+
503
+ module_name = (
504
+ os.path.splitext(os.path.relpath(file_path))[0]
505
+ .replace(os.sep, ".")
506
+ .lstrip(".")
507
+ )
508
+
509
+ # Build uvicorn command with import string
510
+ import_string = f"{module_name}:app.asgi"
511
+ cmd = [
512
+ sys.executable,
513
+ "-m",
514
+ "uvicorn",
515
+ import_string,
516
+ "--reload",
517
+ "--host",
518
+ host,
519
+ "--port",
520
+ str(port),
521
+ "--log-level",
522
+ settings.log_level.lower(),
523
+ ]
524
+
525
+ # Add additional kwargs
526
+ for key, value in kwargs.items():
527
+ if key != "reload": # Skip reload, we're handling it
528
+ cmd.extend([f"--{key.replace('_', '-')}", str(value)])
529
+
530
+ logger.info(f"🚀 Starting dev server: {' '.join(cmd)}")
531
+ logger.info(f"📡 Import string: {import_string}")
532
+
533
+ try:
534
+ subprocess.run(cmd, check=True)
535
+ except subprocess.CalledProcessError as e:
536
+ logger.error(f"❌ Uvicorn failed to start (exit code: {e.returncode})")
537
+ raise RuntimeError(
538
+ f"Development server failed to start.\n\n"
539
+ f"Common causes:\n"
540
+ f"• No module-level 'app' variable found in {module_name}\n"
541
+ f"• Port {port} already in use\n"
542
+ f"• Import errors in your script\n\n"
543
+ f"Make sure you have: app = Wappa(...) at module level."
544
+ ) from e
545
+ except KeyboardInterrupt:
546
+ logger.info("👋 Development server stopped by user")
@@ -0,0 +1,18 @@
1
+ """
2
+ Wappa Database Module
3
+
4
+ This module provides database abstraction and adapters for SQLModel/SQLAlchemy
5
+ async connections. It supports multiple database engines with a unified interface.
6
+ """
7
+
8
+ from .adapter import DatabaseAdapter
9
+ from .adapters.mysql_adapter import MySQLAdapter
10
+ from .adapters.postgresql_adapter import PostgreSQLAdapter
11
+ from .adapters.sqlite_adapter import SQLiteAdapter
12
+
13
+ __all__ = [
14
+ "DatabaseAdapter",
15
+ "PostgreSQLAdapter",
16
+ "SQLiteAdapter",
17
+ "MySQLAdapter",
18
+ ]
@@ -0,0 +1,107 @@
1
+ """
2
+ Database Adapter Protocol
3
+
4
+ Defines the interface for database adapters that work with SQLModel and
5
+ SQLAlchemy async engines. Each adapter handles database-specific connection
6
+ patterns, configuration, and schema management.
7
+ """
8
+
9
+ from collections.abc import Callable
10
+ from typing import TYPE_CHECKING, Any, AsyncContextManager, Protocol
11
+
12
+ if TYPE_CHECKING:
13
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
14
+ from sqlmodel import SQLModel
15
+
16
+
17
+ class DatabaseAdapter(Protocol):
18
+ """
19
+ Universal database adapter interface for SQLModel/SQLAlchemy async connections.
20
+
21
+ All database adapters must implement these methods to provide consistent
22
+ database integration across different engines (PostgreSQL, SQLite, MySQL).
23
+ """
24
+
25
+ async def create_engine(
26
+ self, connection_string: str, **kwargs: Any
27
+ ) -> "AsyncEngine":
28
+ """
29
+ Create an async SQLAlchemy engine for the database.
30
+
31
+ Args:
32
+ connection_string: Database connection URL
33
+ **kwargs: Engine-specific configuration options
34
+
35
+ Returns:
36
+ Configured AsyncEngine instance
37
+
38
+ Raises:
39
+ ConnectionError: If unable to create engine
40
+ """
41
+ ...
42
+
43
+ async def create_session_factory(
44
+ self, engine: "AsyncEngine"
45
+ ) -> Callable[[], AsyncContextManager["AsyncSession"]]:
46
+ """
47
+ Create a session factory for the database engine.
48
+
49
+ The returned factory creates async context managers that yield
50
+ AsyncSession instances for database operations.
51
+
52
+ Args:
53
+ engine: AsyncEngine instance
54
+
55
+ Returns:
56
+ Callable that returns AsyncSession context manager
57
+
58
+ Example:
59
+ session_factory = await adapter.create_session_factory(engine)
60
+ async with session_factory() as session:
61
+ # Use session for database operations
62
+ pass
63
+ """
64
+ ...
65
+
66
+ async def initialize_schema(
67
+ self, engine: "AsyncEngine", models: list[type["SQLModel"]] = None
68
+ ) -> None:
69
+ """
70
+ Initialize database schema from SQLModel definitions.
71
+
72
+ Creates all tables defined by the provided SQLModel classes.
73
+ Handles database-specific schema creation patterns.
74
+
75
+ Args:
76
+ engine: AsyncEngine instance
77
+ models: List of SQLModel classes to create tables for
78
+ If None, creates all tables from metadata
79
+
80
+ Raises:
81
+ DatabaseError: If schema creation fails
82
+ """
83
+ ...
84
+
85
+ async def health_check(self, engine: "AsyncEngine") -> bool:
86
+ """
87
+ Perform a health check on the database connection.
88
+
89
+ Args:
90
+ engine: AsyncEngine instance to check
91
+
92
+ Returns:
93
+ True if database is healthy and responsive
94
+ """
95
+ ...
96
+
97
+ async def get_connection_info(self, engine: "AsyncEngine") -> dict[str, Any]:
98
+ """
99
+ Get information about the database connection.
100
+
101
+ Args:
102
+ engine: AsyncEngine instance
103
+
104
+ Returns:
105
+ Dictionary with connection information (driver, version, etc.)
106
+ """
107
+ ...
@@ -0,0 +1,17 @@
1
+ """
2
+ Database Adapters Module
3
+
4
+ Contains specific database adapter implementations for SQLModel/SQLAlchemy
5
+ async engines. Each adapter handles database-specific connection patterns,
6
+ configuration, and optimization.
7
+ """
8
+
9
+ from .mysql_adapter import MySQLAdapter
10
+ from .postgresql_adapter import PostgreSQLAdapter
11
+ from .sqlite_adapter import SQLiteAdapter
12
+
13
+ __all__ = [
14
+ "PostgreSQLAdapter",
15
+ "SQLiteAdapter",
16
+ "MySQLAdapter",
17
+ ]