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,219 @@
1
+ """
2
+ Webhook URL Factory for generating platform-specific webhook URLs.
3
+
4
+ Implements the Factory pattern to provide clean, consistent webhook URL generation
5
+ for different messaging platforms with tenant-aware routing.
6
+ """
7
+
8
+ from enum import Enum
9
+
10
+ from wappa.core.config.settings import settings
11
+ from wappa.schemas.core.types import PlatformType
12
+
13
+
14
+ class WebhookEndpointType(Enum):
15
+ """Types of webhook endpoints that can be generated."""
16
+
17
+ WEBHOOK = "webhook" # Main webhook processing endpoint
18
+ VERIFY = "verify" # Webhook verification endpoint
19
+ STATUS = "status" # Webhook status check endpoint
20
+
21
+
22
+ class WebhookURLFactory:
23
+ """
24
+ Factory for generating platform-specific webhook URLs.
25
+
26
+ Provides consistent URL generation for different messaging platforms
27
+ with support for tenant-aware routing and different endpoint types.
28
+
29
+ Implements the Factory pattern with Builder pattern elements for
30
+ flexible URL construction.
31
+ """
32
+
33
+ def __init__(self, base_url: str | None = None):
34
+ """
35
+ Initialize the webhook URL factory.
36
+
37
+ Args:
38
+ base_url: Base URL for webhook generation. If None, will be determined
39
+ from settings or environment.
40
+ """
41
+ self.base_url = base_url or self._determine_base_url()
42
+
43
+ def _determine_base_url(self) -> str:
44
+ """
45
+ Determine the base URL for webhook generation.
46
+
47
+ Returns:
48
+ Base URL for webhook endpoints
49
+ """
50
+ # In development, use localhost
51
+ if settings.is_development:
52
+ return f"http://localhost:{settings.port}"
53
+
54
+ # In production, this would be configured via environment
55
+ # For now, use a placeholder that should be configured
56
+ webhook_base_url = getattr(settings, "webhook_base_url", None)
57
+ if webhook_base_url:
58
+ return webhook_base_url
59
+
60
+ # Default fallback (should be configured in production)
61
+ return "https://your-domain.com"
62
+
63
+ def generate_webhook_url(
64
+ self,
65
+ platform: PlatformType,
66
+ tenant_id: str,
67
+ endpoint_type: WebhookEndpointType = WebhookEndpointType.WEBHOOK,
68
+ ) -> str:
69
+ """
70
+ Generate a webhook URL for a specific platform and tenant.
71
+
72
+ Args:
73
+ platform: The messaging platform (WhatsApp, Telegram, etc.)
74
+ tenant_id: The tenant identifier (phone_number_id for WhatsApp)
75
+ endpoint_type: Type of webhook endpoint to generate
76
+
77
+ Returns:
78
+ Complete webhook URL for the platform and tenant
79
+
80
+ Example:
81
+ >>> factory = WebhookURLFactory()
82
+ >>> factory.generate_webhook_url(PlatformType.WHATSAPP, "123456789")
83
+ "https://your-domain.com/webhook/messenger/123456789/whatsapp"
84
+ """
85
+ platform_name = platform.value.lower()
86
+
87
+ if endpoint_type == WebhookEndpointType.VERIFY:
88
+ return f"{self.base_url}/webhook/messenger/{platform_name}/verify"
89
+ elif endpoint_type == WebhookEndpointType.STATUS:
90
+ return (
91
+ f"{self.base_url}/webhook/messenger/{tenant_id}/{platform_name}/status"
92
+ )
93
+ else: # WEBHOOK (default)
94
+ return f"{self.base_url}/webhook/messenger/{tenant_id}/{platform_name}"
95
+
96
+ def generate_whatsapp_webhook_url(self) -> str:
97
+ """
98
+ Generate a WhatsApp-specific webhook URL.
99
+
100
+ Convenience method for WhatsApp webhook generation that automatically
101
+ uses the tenant_id from settings (WP_PHONE_ID from .env).
102
+
103
+ Returns:
104
+ Complete WhatsApp webhook URL using configured phone number ID
105
+ """
106
+ return self.generate_webhook_url(PlatformType.WHATSAPP, settings.owner_id)
107
+
108
+ def generate_whatsapp_verify_url(self) -> str:
109
+ """
110
+ Generate a WhatsApp webhook verification URL.
111
+
112
+ Returns:
113
+ WhatsApp webhook verification URL
114
+ """
115
+ return self.generate_webhook_url(
116
+ PlatformType.WHATSAPP, "", WebhookEndpointType.VERIFY
117
+ )
118
+
119
+ def get_supported_platforms(self) -> dict[str, dict[str, str]]:
120
+ """
121
+ Get all supported platforms and their webhook URL patterns.
122
+
123
+ Returns:
124
+ Dictionary mapping platform names to their URL patterns
125
+ """
126
+ patterns = {}
127
+
128
+ for platform in PlatformType:
129
+ platform_name = platform.value.lower()
130
+ patterns[platform_name] = {
131
+ "webhook_pattern": f"/webhook/messenger/{{tenant_id}}/{platform_name}",
132
+ "verify_pattern": f"/webhook/messenger/{platform_name}/verify",
133
+ "status_pattern": f"/webhook/messenger/{{tenant_id}}/{platform_name}/status",
134
+ "example_webhook": self.generate_webhook_url(platform, "TENANT_ID"),
135
+ "example_verify": self.generate_webhook_url(
136
+ platform, "", WebhookEndpointType.VERIFY
137
+ ),
138
+ }
139
+
140
+ return patterns
141
+
142
+ def validate_webhook_url(
143
+ self, url: str, platform: PlatformType, tenant_id: str
144
+ ) -> bool:
145
+ """
146
+ Validate if a URL matches the expected webhook pattern for a platform.
147
+
148
+ Args:
149
+ url: URL to validate
150
+ platform: Expected platform
151
+ tenant_id: Expected tenant ID
152
+
153
+ Returns:
154
+ True if URL matches the expected pattern
155
+ """
156
+ expected_url = self.generate_webhook_url(platform, tenant_id)
157
+ return url == expected_url
158
+
159
+ def extract_platform_from_url(self, webhook_path: str) -> PlatformType | None:
160
+ """
161
+ Extract platform type from a webhook URL path.
162
+
163
+ Args:
164
+ webhook_path: Webhook URL path (e.g., "/webhook/messenger/123/whatsapp")
165
+
166
+ Returns:
167
+ PlatformType if found, None otherwise
168
+ """
169
+ # Expected pattern: /webhook/messenger/{tenant_id}/{platform}
170
+ path_parts = webhook_path.strip("/").split("/")
171
+
172
+ if (
173
+ len(path_parts) >= 4
174
+ and path_parts[0] == "webhook"
175
+ and path_parts[1] == "messenger"
176
+ ):
177
+ platform_name = path_parts[3] # platform is at index 3
178
+ try:
179
+ return PlatformType(platform_name.lower())
180
+ except ValueError:
181
+ return None
182
+
183
+ return None
184
+
185
+ def extract_tenant_from_url(self, webhook_path: str) -> str | None:
186
+ """
187
+ Extract tenant ID from a webhook URL path.
188
+
189
+ Args:
190
+ webhook_path: Webhook URL path (e.g., "/webhook/messenger/123/whatsapp")
191
+
192
+ Returns:
193
+ Tenant ID if found, None otherwise
194
+ """
195
+ # Expected pattern: /webhook/messenger/{tenant_id}/{platform}
196
+ path_parts = webhook_path.strip("/").split("/")
197
+
198
+ if (
199
+ len(path_parts) >= 4
200
+ and path_parts[0] == "webhook"
201
+ and path_parts[1] == "messenger"
202
+ ):
203
+ return path_parts[2] # tenant_id is at index 2
204
+
205
+ return None
206
+
207
+
208
+ # Global factory instance
209
+ webhook_url_factory = WebhookURLFactory()
210
+
211
+
212
+ def get_webhook_url_factory() -> WebhookURLFactory:
213
+ """
214
+ Get the global webhook URL factory instance.
215
+
216
+ Returns:
217
+ WebhookURLFactory instance
218
+ """
219
+ return webhook_url_factory
@@ -0,0 +1,15 @@
1
+ """
2
+ Wappa Factory Module
3
+
4
+ This module provides the plugin-based factory system for building extensible
5
+ Wappa applications. It includes the WappaBuilder class and plugin interfaces
6
+ that allow users to extend FastAPI functionality without modifying core code.
7
+ """
8
+
9
+ from .plugin import WappaPlugin
10
+ from .wappa_builder import WappaBuilder
11
+
12
+ __all__ = [
13
+ "WappaBuilder",
14
+ "WappaPlugin",
15
+ ]
@@ -0,0 +1,68 @@
1
+ """
2
+ Wappa Plugin Protocol
3
+
4
+ Defines the interface that all Wappa plugins must implement for consistent
5
+ integration with the WappaBuilder factory system.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Protocol
9
+
10
+ if TYPE_CHECKING:
11
+ from fastapi import FastAPI
12
+
13
+ from .wappa_builder import WappaBuilder
14
+
15
+
16
+ class WappaPlugin(Protocol):
17
+ """
18
+ Universal plugin interface for extending Wappa functionality.
19
+
20
+ All plugins must implement these three lifecycle methods:
21
+ 1. configure: Called during WappaBuilder setup to register middleware/routes
22
+ 2. startup: Called during FastAPI application startup
23
+ 3. shutdown: Called during FastAPI application shutdown
24
+
25
+ This protocol ensures consistent plugin behavior and proper resource management.
26
+ """
27
+
28
+ def configure(self, builder: "WappaBuilder") -> None:
29
+ """
30
+ Configure the plugin with the WappaBuilder.
31
+
32
+ This method is called during the build phase, before the FastAPI app
33
+ is created. Use this to register middleware, routes, and other components
34
+ that need to be set up before the app starts.
35
+
36
+ This method is synchronous because it only registers components with the builder.
37
+ Actual async initialization should be done in the startup() method.
38
+
39
+ Args:
40
+ builder: WappaBuilder instance to configure
41
+ """
42
+ ...
43
+
44
+ async def startup(self, app: "FastAPI") -> None:
45
+ """
46
+ Execute plugin startup logic.
47
+
48
+ This method is called during FastAPI application startup. Use this
49
+ for initializing connections, setting up resources, health checks,
50
+ and any other startup tasks.
51
+
52
+ Args:
53
+ app: FastAPI application instance
54
+ """
55
+ ...
56
+
57
+ async def shutdown(self, app: "FastAPI") -> None:
58
+ """
59
+ Execute plugin cleanup logic.
60
+
61
+ This method is called during FastAPI application shutdown. Use this
62
+ for closing connections, cleaning up resources, and any other
63
+ shutdown tasks. Should be the reverse of startup operations.
64
+
65
+ Args:
66
+ app: FastAPI application instance
67
+ """
68
+ ...
@@ -0,0 +1,326 @@
1
+ """
2
+ WappaBuilder - Extensible FastAPI Application Factory
3
+
4
+ This module provides the WappaBuilder class that enables users to create
5
+ highly customized FastAPI applications using a plugin-based architecture.
6
+ """
7
+
8
+ from collections.abc import Callable
9
+ from contextlib import asynccontextmanager
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ from fastapi import FastAPI
13
+
14
+ from ..logging.logger import get_app_logger
15
+
16
+ if TYPE_CHECKING:
17
+ from .plugin import WappaPlugin
18
+
19
+
20
+ class WappaBuilder:
21
+ """
22
+ Fluent builder for creating extensible Wappa applications.
23
+
24
+ The WappaBuilder provides a plugin-based architecture that allows users
25
+ to extend FastAPI functionality without modifying core code. It supports:
26
+
27
+ - Plugin system with lifecycle management
28
+ - Priority-based middleware ordering
29
+ - Configurable startup/shutdown hooks
30
+ - Seamless integration with existing Wappa class
31
+
32
+ Example:
33
+ builder = WappaBuilder()
34
+ app = await (builder
35
+ .add_plugin(DatabasePlugin("postgresql://...", PostgreSQLAdapter()))
36
+ .add_plugin(RedisPlugin())
37
+ .add_middleware(CORSMiddleware, allow_origins=["*"])
38
+ .configure(title="My Wappa App")
39
+ .build())
40
+ """
41
+
42
+ def __init__(self):
43
+ """Initialize WappaBuilder with empty configuration."""
44
+ self.plugins: list[WappaPlugin] = []
45
+ self.middlewares: list[tuple[type, dict, int]] = [] # (class, kwargs, priority)
46
+ self.routers: list[tuple[Any, dict]] = [] # (router, include_kwargs)
47
+ self.startup_hooks: list[tuple[Callable, int]] = [] # (hook, priority)
48
+ self.shutdown_hooks: list[tuple[Callable, int]] = [] # (hook, priority)
49
+ self.config_overrides: dict[str, Any] = {}
50
+
51
+ def add_plugin(self, plugin: "WappaPlugin") -> "WappaBuilder":
52
+ """
53
+ Add a plugin to extend functionality.
54
+
55
+ Plugins provide a clean way to add complex functionality like database
56
+ connections, Redis caching, authentication, etc.
57
+
58
+ Args:
59
+ plugin: WappaPlugin instance to add
60
+
61
+ Returns:
62
+ Self for method chaining
63
+ """
64
+ self.plugins.append(plugin)
65
+ return self
66
+
67
+ def add_middleware(
68
+ self, middleware_class: type, priority: int = 50, **kwargs: Any
69
+ ) -> "WappaBuilder":
70
+ """
71
+ Add middleware to the application with priority ordering.
72
+
73
+ Priority determines execution order:
74
+ - Lower numbers run first (outer middleware)
75
+ - Higher numbers run last (inner middleware)
76
+ - Default priority is 50
77
+
78
+ Args:
79
+ middleware_class: Middleware class to add
80
+ priority: Execution priority (lower = outer, higher = inner)
81
+ **kwargs: Middleware configuration parameters
82
+
83
+ Returns:
84
+ Self for method chaining
85
+ """
86
+ self.middlewares.append((middleware_class, kwargs, priority))
87
+ return self
88
+
89
+ def add_router(self, router: Any, **kwargs: Any) -> "WappaBuilder":
90
+ """
91
+ Add a router to the application.
92
+
93
+ Args:
94
+ router: FastAPI router to include
95
+ **kwargs: Arguments for app.include_router()
96
+
97
+ Returns:
98
+ Self for method chaining
99
+ """
100
+ self.routers.append((router, kwargs))
101
+ return self
102
+
103
+ def add_startup_hook(self, hook: Callable, priority: int = 50) -> "WappaBuilder":
104
+ """
105
+ Add a startup hook to unified lifespan management.
106
+
107
+ Hooks are executed during application startup in priority order.
108
+ Lower priority numbers execute first.
109
+
110
+ Priority Guidelines:
111
+ - 10: Core system initialization (logging, sessions)
112
+ - 20: Infrastructure (databases, caches, external services)
113
+ - 30: Application services
114
+ - 50: User hooks (default)
115
+ - 70+: Late initialization hooks
116
+
117
+ Args:
118
+ hook: Async callable that takes (app: FastAPI) -> None
119
+ priority: Execution priority (lower = runs first)
120
+
121
+ Returns:
122
+ Self for method chaining
123
+
124
+ Example:
125
+ async def my_startup(app: FastAPI):
126
+ print("Starting my service")
127
+
128
+ builder.add_startup_hook(my_startup, priority=30)
129
+ """
130
+ self.startup_hooks.append((hook, priority))
131
+ return self
132
+
133
+ def add_shutdown_hook(self, hook: Callable, priority: int = 50) -> "WappaBuilder":
134
+ """
135
+ Add a shutdown hook to unified lifespan management.
136
+
137
+ Hooks are executed during application shutdown in reverse priority order.
138
+ Higher priority numbers execute first during shutdown.
139
+
140
+ Priority Guidelines:
141
+ - 90: Core system cleanup (sessions, logging) - runs last
142
+ - 70: Application services cleanup
143
+ - 50: User hooks (default)
144
+ - 30: Application services
145
+ - 20: Infrastructure cleanup (databases, caches)
146
+ - 10: Early cleanup
147
+
148
+ Args:
149
+ hook: Async callable that takes (app: FastAPI) -> None
150
+ priority: Execution priority (higher = runs first in shutdown)
151
+
152
+ Returns:
153
+ Self for method chaining
154
+
155
+ Example:
156
+ async def my_shutdown(app: FastAPI):
157
+ print("Cleaning up my service")
158
+
159
+ builder.add_shutdown_hook(my_shutdown, priority=30)
160
+ """
161
+ self.shutdown_hooks.append((hook, priority))
162
+ return self
163
+
164
+ def configure(self, **overrides: Any) -> "WappaBuilder":
165
+ """
166
+ Override default FastAPI configuration.
167
+
168
+ Args:
169
+ **overrides: FastAPI constructor arguments to override
170
+
171
+ Returns:
172
+ Self for method chaining
173
+ """
174
+ self.config_overrides.update(overrides)
175
+ return self
176
+
177
+ def build(self) -> FastAPI:
178
+ """
179
+ Build the configured FastAPI application following proper FastAPI initialization pattern.
180
+
181
+ This method follows the exact pattern you specified:
182
+ 1. Configure plugins (sync setup only)
183
+ 2. Create FastAPI app with lifespan and config
184
+ 3. Add all middleware via app.add_middleware()
185
+ 4. Include all routers via app.include_router()
186
+
187
+ Only async operations happen in the lifespan (startup/shutdown hooks).
188
+
189
+ Returns:
190
+ FastAPI application with properly configured plugins
191
+ """
192
+ logger = get_app_logger()
193
+ logger.debug(f"🏗️ Building FastAPI app with {len(self.plugins)} plugins")
194
+
195
+ # Step 1: Configure plugins (sync setup only - middleware/router registration)
196
+ if self.plugins:
197
+ logger.debug(f"⚙️ Configuring {len(self.plugins)} plugins synchronously...")
198
+ for plugin in self.plugins:
199
+ plugin.configure(self) # Synchronous configuration
200
+
201
+ logger.info(
202
+ f"✅ Plugin configuration complete - registered {len(self.middlewares)} middlewares, "
203
+ f"{len(self.routers)} routers, {len(self.startup_hooks)} startup hooks, "
204
+ f"{len(self.shutdown_hooks)} shutdown hooks"
205
+ )
206
+
207
+ # Create unified lifespan (only for async startup/shutdown hooks)
208
+ from contextlib import asynccontextmanager
209
+
210
+ @asynccontextmanager
211
+ async def unified_lifespan(app: FastAPI):
212
+ try:
213
+ # Startup phase - execute async startup hooks only
214
+ logger.debug("🚀 Starting unified lifespan startup phase...")
215
+ await self._execute_all_startup_hooks(app)
216
+ logger.info("✅ All startup hooks completed successfully")
217
+ yield
218
+ except Exception as e:
219
+ logger.error(f"❌ Error during startup phase: {e}", exc_info=True)
220
+ raise
221
+ finally:
222
+ # Shutdown phase - execute async shutdown hooks
223
+ logger.debug("🛑 Starting unified lifespan shutdown phase...")
224
+ await self._execute_all_shutdown_hooks(app)
225
+ logger.info("✅ All shutdown hooks completed")
226
+
227
+ # Step 2: Create FastAPI app with lifespan and config
228
+ default_config = {
229
+ "title": "Wappa Application",
230
+ "description": "WhatsApp Business application built with Wappa framework",
231
+ "version": "1.0.0",
232
+ "lifespan": unified_lifespan,
233
+ }
234
+ default_config.update(self.config_overrides)
235
+
236
+ app = FastAPI(**default_config)
237
+ logger.debug(f"Created FastAPI app: {default_config['title']}")
238
+
239
+ # Step 3: Add all middleware via app.add_middleware()
240
+ # Sort by priority (reverse order because FastAPI adds middleware in reverse)
241
+ sorted_middlewares = sorted(self.middlewares, key=lambda x: x[2], reverse=True)
242
+ for middleware_class, kwargs, priority in sorted_middlewares:
243
+ app.add_middleware(middleware_class, **kwargs)
244
+ logger.debug(f"Added middleware {middleware_class.__name__} (priority: {priority})")
245
+
246
+ # Step 4: Include all routers via app.include_router()
247
+ for router, kwargs in self.routers:
248
+ app.include_router(router, **kwargs)
249
+ logger.debug(f"Included router with config: {kwargs}")
250
+
251
+ logger.info(
252
+ f"🎉 WappaBuilder created FastAPI app: {len(self.plugins)} plugins, "
253
+ f"{len(self.middlewares)} middlewares, {len(self.routers)} routers"
254
+ )
255
+
256
+ return app
257
+
258
+ async def _execute_all_startup_hooks(self, app: FastAPI) -> None:
259
+ """
260
+ Execute all startup hooks in unified priority order.
261
+
262
+ This replaces the old plugin-specific startup execution with a unified
263
+ approach where all hooks (from plugins and user code) are executed
264
+ in a single priority-ordered sequence.
265
+
266
+ Args:
267
+ app: FastAPI application instance
268
+ """
269
+ logger = get_app_logger()
270
+
271
+ try:
272
+ # Execute all startup hooks in priority order (10, 20, 30, ...)
273
+ sorted_hooks = sorted(self.startup_hooks, key=lambda x: x[1])
274
+
275
+ if not sorted_hooks:
276
+ logger.debug("No startup hooks registered")
277
+ return
278
+
279
+ logger.debug(f"Executing {len(sorted_hooks)} startup hooks in priority order...")
280
+
281
+ for hook, priority in sorted_hooks:
282
+ hook_name = getattr(hook, "__name__", "anonymous_hook")
283
+ logger.debug(f"⚡ Executing startup hook: {hook_name} (priority: {priority})")
284
+
285
+ try:
286
+ await hook(app)
287
+ logger.debug(f"✅ Startup hook {hook_name} completed")
288
+ except Exception as e:
289
+ logger.error(f"❌ Startup hook {hook_name} failed: {e}", exc_info=True)
290
+ raise # Re-raise to fail fast
291
+
292
+ except Exception as e:
293
+ logger.error(f"❌ Startup sequence failed: {e}", exc_info=True)
294
+ raise
295
+
296
+ async def _execute_all_shutdown_hooks(self, app: FastAPI) -> None:
297
+ """
298
+ Execute all shutdown hooks in reverse priority order.
299
+
300
+ This replaces the old plugin-specific shutdown execution with a unified
301
+ approach where all hooks are executed in reverse priority order,
302
+ with error isolation to prevent shutdown cascading failures.
303
+
304
+ Args:
305
+ app: FastAPI application instance
306
+ """
307
+ logger = get_app_logger()
308
+
309
+ # Execute shutdown hooks in reverse priority order (90, 70, 50, 30, 20, 10)
310
+ sorted_hooks = sorted(self.shutdown_hooks, key=lambda x: x[1], reverse=True)
311
+
312
+ if not sorted_hooks:
313
+ logger.debug("No shutdown hooks registered")
314
+ return
315
+
316
+ logger.debug(f"Executing {len(sorted_hooks)} shutdown hooks in reverse priority order...")
317
+
318
+ for hook, priority in sorted_hooks:
319
+ hook_name = getattr(hook, "__name__", "anonymous_hook")
320
+ try:
321
+ logger.debug(f"🛑 Executing shutdown hook: {hook_name} (priority: {priority})")
322
+ await hook(app)
323
+ logger.debug(f"✅ Shutdown hook {hook_name} completed")
324
+ except Exception as e:
325
+ # Don't re-raise in shutdown - log and continue with other hooks
326
+ logger.error(f"❌ Error in shutdown hook {hook_name}: {e}", exc_info=True)
@@ -0,0 +1,5 @@
1
+ """Logging module for Wappa framework."""
2
+
3
+ from .logger import get_app_logger, get_logger, setup_app_logging
4
+
5
+ __all__ = ["get_app_logger", "get_logger", "setup_app_logging"]