wappa 0.1.8__py3-none-any.whl → 0.1.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of wappa might be problematic. Click here for more details.
- wappa/__init__.py +4 -5
- wappa/api/controllers/webhook_controller.py +5 -2
- wappa/api/dependencies/__init__.py +0 -5
- wappa/api/middleware/error_handler.py +4 -4
- wappa/api/middleware/owner.py +11 -5
- wappa/api/routes/webhooks.py +2 -2
- wappa/cli/__init__.py +1 -1
- wappa/cli/examples/init/.env.example +33 -0
- wappa/cli/examples/init/app/__init__.py +0 -0
- wappa/cli/examples/init/app/main.py +9 -0
- wappa/cli/examples/init/app/master_event.py +10 -0
- wappa/cli/examples/json_cache_example/.env.example +33 -0
- wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/main.py +247 -0
- wappa/cli/examples/json_cache_example/app/master_event.py +455 -0
- wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +256 -0
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +192 -0
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +256 -0
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +187 -0
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +272 -0
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +239 -0
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +174 -0
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +251 -0
- wappa/cli/examples/openai_transcript/.gitignore +63 -4
- wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
- wappa/cli/examples/openai_transcript/app/main.py +9 -0
- wappa/cli/examples/openai_transcript/app/master_event.py +62 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +89 -0
- wappa/cli/examples/redis_cache_example/.env.example +33 -0
- wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
- wappa/cli/examples/redis_cache_example/app/main.py +246 -0
- wappa/cli/examples/redis_cache_example/app/master_event.py +455 -0
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +256 -0
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +192 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +256 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +187 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +272 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +239 -0
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +174 -0
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +251 -0
- wappa/cli/examples/simple_echo_example/.env.example +33 -0
- wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
- wappa/cli/examples/simple_echo_example/app/main.py +191 -0
- wappa/cli/examples/simple_echo_example/app/master_event.py +230 -0
- wappa/cli/examples/wappa_full_example/.env.example +33 -0
- wappa/cli/examples/wappa_full_example/.gitignore +63 -4
- wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
- wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +492 -0
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +559 -0
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +514 -0
- wappa/cli/examples/wappa_full_example/app/main.py +269 -0
- wappa/cli/examples/wappa_full_example/app/master_event.py +504 -0
- wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
- wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
- wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/models/state_models.py +434 -0
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +303 -0
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +327 -0
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +502 -0
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +516 -0
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +337 -0
- wappa/cli/main.py +14 -5
- wappa/core/__init__.py +18 -23
- wappa/core/config/settings.py +7 -5
- wappa/core/events/default_handlers.py +1 -1
- wappa/core/factory/wappa_builder.py +38 -25
- wappa/core/plugins/redis_plugin.py +1 -3
- wappa/core/plugins/wappa_core_plugin.py +7 -6
- wappa/core/types.py +12 -12
- wappa/core/wappa_app.py +10 -8
- wappa/database/__init__.py +3 -4
- wappa/domain/enums/messenger_platform.py +1 -2
- wappa/domain/factories/media_factory.py +5 -20
- wappa/domain/factories/message_factory.py +5 -20
- wappa/domain/factories/messenger_factory.py +2 -4
- wappa/domain/interfaces/cache_interface.py +7 -7
- wappa/domain/interfaces/media_interface.py +2 -5
- wappa/domain/models/media_result.py +1 -3
- wappa/domain/models/platforms/platform_config.py +1 -3
- wappa/messaging/__init__.py +9 -12
- wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
- wappa/models/__init__.py +27 -35
- wappa/persistence/__init__.py +12 -15
- wappa/persistence/cache_factory.py +0 -1
- wappa/persistence/json/__init__.py +1 -1
- wappa/persistence/json/cache_adapters.py +37 -25
- wappa/persistence/json/handlers/state_handler.py +60 -52
- wappa/persistence/json/handlers/table_handler.py +51 -49
- wappa/persistence/json/handlers/user_handler.py +71 -55
- wappa/persistence/json/handlers/utils/file_manager.py +42 -39
- wappa/persistence/json/handlers/utils/key_factory.py +1 -1
- wappa/persistence/json/handlers/utils/serialization.py +13 -11
- wappa/persistence/json/json_cache_factory.py +4 -8
- wappa/persistence/json/storage_manager.py +66 -79
- wappa/persistence/memory/__init__.py +1 -1
- wappa/persistence/memory/cache_adapters.py +37 -25
- wappa/persistence/memory/handlers/state_handler.py +62 -52
- wappa/persistence/memory/handlers/table_handler.py +59 -53
- wappa/persistence/memory/handlers/user_handler.py +75 -55
- wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
- wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
- wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
- wappa/persistence/memory/memory_cache_factory.py +3 -7
- wappa/persistence/memory/storage_manager.py +52 -62
- wappa/persistence/redis/cache_adapters.py +27 -21
- wappa/persistence/redis/ops.py +11 -11
- wappa/persistence/redis/redis_client.py +4 -6
- wappa/persistence/redis/redis_manager.py +12 -4
- wappa/processors/factory.py +5 -5
- wappa/schemas/factory.py +2 -5
- wappa/schemas/whatsapp/message_types/errors.py +3 -12
- wappa/schemas/whatsapp/validators.py +3 -3
- wappa/webhooks/__init__.py +17 -18
- wappa/webhooks/factory.py +3 -5
- wappa/webhooks/whatsapp/__init__.py +10 -13
- wappa/webhooks/whatsapp/message_types/audio.py +0 -4
- wappa/webhooks/whatsapp/message_types/document.py +1 -9
- wappa/webhooks/whatsapp/message_types/errors.py +3 -12
- wappa/webhooks/whatsapp/message_types/location.py +1 -21
- wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
- wappa/webhooks/whatsapp/message_types/text.py +0 -6
- wappa/webhooks/whatsapp/message_types/video.py +1 -20
- wappa/webhooks/whatsapp/status_models.py +2 -2
- wappa/webhooks/whatsapp/validators.py +3 -3
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/RECORD +144 -80
- wappa/cli/examples/init/pyproject.toml +0 -7
- wappa/cli/examples/simple_echo_example/.python-version +0 -1
- wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -103,10 +103,10 @@ class WappaBuilder:
|
|
|
103
103
|
def add_startup_hook(self, hook: Callable, priority: int = 50) -> "WappaBuilder":
|
|
104
104
|
"""
|
|
105
105
|
Add a startup hook to unified lifespan management.
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
Hooks are executed during application startup in priority order.
|
|
108
108
|
Lower priority numbers execute first.
|
|
109
|
-
|
|
109
|
+
|
|
110
110
|
Priority Guidelines:
|
|
111
111
|
- 10: Core system initialization (logging, sessions)
|
|
112
112
|
- 20: Infrastructure (databases, caches, external services)
|
|
@@ -120,11 +120,11 @@ class WappaBuilder:
|
|
|
120
120
|
|
|
121
121
|
Returns:
|
|
122
122
|
Self for method chaining
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
Example:
|
|
125
125
|
async def my_startup(app: FastAPI):
|
|
126
126
|
print("Starting my service")
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
builder.add_startup_hook(my_startup, priority=30)
|
|
129
129
|
"""
|
|
130
130
|
self.startup_hooks.append((hook, priority))
|
|
@@ -133,10 +133,10 @@ class WappaBuilder:
|
|
|
133
133
|
def add_shutdown_hook(self, hook: Callable, priority: int = 50) -> "WappaBuilder":
|
|
134
134
|
"""
|
|
135
135
|
Add a shutdown hook to unified lifespan management.
|
|
136
|
-
|
|
136
|
+
|
|
137
137
|
Hooks are executed during application shutdown in reverse priority order.
|
|
138
138
|
Higher priority numbers execute first during shutdown.
|
|
139
|
-
|
|
139
|
+
|
|
140
140
|
Priority Guidelines:
|
|
141
141
|
- 90: Core system cleanup (sessions, logging) - runs last
|
|
142
142
|
- 70: Application services cleanup
|
|
@@ -146,16 +146,16 @@ class WappaBuilder:
|
|
|
146
146
|
- 10: Early cleanup
|
|
147
147
|
|
|
148
148
|
Args:
|
|
149
|
-
hook: Async callable that takes (app: FastAPI) -> None
|
|
149
|
+
hook: Async callable that takes (app: FastAPI) -> None
|
|
150
150
|
priority: Execution priority (higher = runs first in shutdown)
|
|
151
151
|
|
|
152
152
|
Returns:
|
|
153
153
|
Self for method chaining
|
|
154
|
-
|
|
154
|
+
|
|
155
155
|
Example:
|
|
156
156
|
async def my_shutdown(app: FastAPI):
|
|
157
157
|
print("Cleaning up my service")
|
|
158
|
-
|
|
158
|
+
|
|
159
159
|
builder.add_shutdown_hook(my_shutdown, priority=30)
|
|
160
160
|
"""
|
|
161
161
|
self.shutdown_hooks.append((hook, priority))
|
|
@@ -183,7 +183,7 @@ class WappaBuilder:
|
|
|
183
183
|
2. Create FastAPI app with lifespan and config
|
|
184
184
|
3. Add all middleware via app.add_middleware()
|
|
185
185
|
4. Include all routers via app.include_router()
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
Only async operations happen in the lifespan (startup/shutdown hooks).
|
|
188
188
|
|
|
189
189
|
Returns:
|
|
@@ -191,22 +191,21 @@ class WappaBuilder:
|
|
|
191
191
|
"""
|
|
192
192
|
logger = get_app_logger()
|
|
193
193
|
logger.debug(f"🏗️ Building FastAPI app with {len(self.plugins)} plugins")
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
# Step 1: Configure plugins (sync setup only - middleware/router registration)
|
|
196
196
|
if self.plugins:
|
|
197
197
|
logger.debug(f"⚙️ Configuring {len(self.plugins)} plugins synchronously...")
|
|
198
198
|
for plugin in self.plugins:
|
|
199
199
|
plugin.configure(self) # Synchronous configuration
|
|
200
|
-
|
|
200
|
+
|
|
201
201
|
logger.info(
|
|
202
202
|
f"✅ Plugin configuration complete - registered {len(self.middlewares)} middlewares, "
|
|
203
203
|
f"{len(self.routers)} routers, {len(self.startup_hooks)} startup hooks, "
|
|
204
204
|
f"{len(self.shutdown_hooks)} shutdown hooks"
|
|
205
205
|
)
|
|
206
|
-
|
|
206
|
+
|
|
207
207
|
# Create unified lifespan (only for async startup/shutdown hooks)
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
|
|
210
209
|
@asynccontextmanager
|
|
211
210
|
async def unified_lifespan(app: FastAPI):
|
|
212
211
|
try:
|
|
@@ -241,9 +240,11 @@ class WappaBuilder:
|
|
|
241
240
|
sorted_middlewares = sorted(self.middlewares, key=lambda x: x[2], reverse=True)
|
|
242
241
|
for middleware_class, kwargs, priority in sorted_middlewares:
|
|
243
242
|
app.add_middleware(middleware_class, **kwargs)
|
|
244
|
-
logger.debug(
|
|
243
|
+
logger.debug(
|
|
244
|
+
f"Added middleware {middleware_class.__name__} (priority: {priority})"
|
|
245
|
+
)
|
|
245
246
|
|
|
246
|
-
# Step 4: Include all routers via app.include_router()
|
|
247
|
+
# Step 4: Include all routers via app.include_router()
|
|
247
248
|
for router, kwargs in self.routers:
|
|
248
249
|
app.include_router(router, **kwargs)
|
|
249
250
|
logger.debug(f"Included router with config: {kwargs}")
|
|
@@ -258,7 +259,7 @@ class WappaBuilder:
|
|
|
258
259
|
async def _execute_all_startup_hooks(self, app: FastAPI) -> None:
|
|
259
260
|
"""
|
|
260
261
|
Execute all startup hooks in unified priority order.
|
|
261
|
-
|
|
262
|
+
|
|
262
263
|
This replaces the old plugin-specific startup execution with a unified
|
|
263
264
|
approach where all hooks (from plugins and user code) are executed
|
|
264
265
|
in a single priority-ordered sequence.
|
|
@@ -276,17 +277,23 @@ class WappaBuilder:
|
|
|
276
277
|
logger.debug("No startup hooks registered")
|
|
277
278
|
return
|
|
278
279
|
|
|
279
|
-
logger.debug(
|
|
280
|
+
logger.debug(
|
|
281
|
+
f"Executing {len(sorted_hooks)} startup hooks in priority order..."
|
|
282
|
+
)
|
|
280
283
|
|
|
281
284
|
for hook, priority in sorted_hooks:
|
|
282
285
|
hook_name = getattr(hook, "__name__", "anonymous_hook")
|
|
283
|
-
logger.debug(
|
|
286
|
+
logger.debug(
|
|
287
|
+
f"⚡ Executing startup hook: {hook_name} (priority: {priority})"
|
|
288
|
+
)
|
|
284
289
|
|
|
285
290
|
try:
|
|
286
291
|
await hook(app)
|
|
287
292
|
logger.debug(f"✅ Startup hook {hook_name} completed")
|
|
288
293
|
except Exception as e:
|
|
289
|
-
logger.error(
|
|
294
|
+
logger.error(
|
|
295
|
+
f"❌ Startup hook {hook_name} failed: {e}", exc_info=True
|
|
296
|
+
)
|
|
290
297
|
raise # Re-raise to fail fast
|
|
291
298
|
|
|
292
299
|
except Exception as e:
|
|
@@ -296,7 +303,7 @@ class WappaBuilder:
|
|
|
296
303
|
async def _execute_all_shutdown_hooks(self, app: FastAPI) -> None:
|
|
297
304
|
"""
|
|
298
305
|
Execute all shutdown hooks in reverse priority order.
|
|
299
|
-
|
|
306
|
+
|
|
300
307
|
This replaces the old plugin-specific shutdown execution with a unified
|
|
301
308
|
approach where all hooks are executed in reverse priority order,
|
|
302
309
|
with error isolation to prevent shutdown cascading failures.
|
|
@@ -313,14 +320,20 @@ class WappaBuilder:
|
|
|
313
320
|
logger.debug("No shutdown hooks registered")
|
|
314
321
|
return
|
|
315
322
|
|
|
316
|
-
logger.debug(
|
|
323
|
+
logger.debug(
|
|
324
|
+
f"Executing {len(sorted_hooks)} shutdown hooks in reverse priority order..."
|
|
325
|
+
)
|
|
317
326
|
|
|
318
327
|
for hook, priority in sorted_hooks:
|
|
319
328
|
hook_name = getattr(hook, "__name__", "anonymous_hook")
|
|
320
329
|
try:
|
|
321
|
-
logger.debug(
|
|
330
|
+
logger.debug(
|
|
331
|
+
f"🛑 Executing shutdown hook: {hook_name} (priority: {priority})"
|
|
332
|
+
)
|
|
322
333
|
await hook(app)
|
|
323
334
|
logger.debug(f"✅ Shutdown hook {hook_name} completed")
|
|
324
335
|
except Exception as e:
|
|
325
336
|
# Don't re-raise in shutdown - log and continue with other hooks
|
|
326
|
-
logger.error(
|
|
337
|
+
logger.error(
|
|
338
|
+
f"❌ Error in shutdown hook {hook_name}: {e}", exc_info=True
|
|
339
|
+
)
|
|
@@ -120,9 +120,7 @@ class RedisPlugin:
|
|
|
120
120
|
max_conn = self.max_connections or settings.redis_max_connections
|
|
121
121
|
|
|
122
122
|
logger.info("=== REDIS CACHE INITIALIZATION ===")
|
|
123
|
-
logger.info(
|
|
124
|
-
f"🔴 Redis URL: {redis_url} (max_connections: {max_conn})"
|
|
125
|
-
)
|
|
123
|
+
logger.info(f"🔴 Redis URL: {redis_url} (max_connections: {max_conn})")
|
|
126
124
|
|
|
127
125
|
# Initialize Redis pools with explicit settings
|
|
128
126
|
await RedisManager.initialize(
|
|
@@ -152,16 +152,17 @@ class WappaCorePlugin:
|
|
|
152
152
|
# Create persistent HTTP session with optimized connection pooling
|
|
153
153
|
logger.info("🌐 Creating persistent HTTP session...")
|
|
154
154
|
connector = aiohttp.TCPConnector(
|
|
155
|
-
limit=100,
|
|
156
|
-
keepalive_timeout=30,
|
|
157
|
-
enable_cleanup_closed=True
|
|
155
|
+
limit=100, # Max connections
|
|
156
|
+
keepalive_timeout=30, # Keep alive timeout
|
|
157
|
+
enable_cleanup_closed=True, # Auto cleanup closed connections
|
|
158
158
|
)
|
|
159
159
|
session = aiohttp.ClientSession(
|
|
160
|
-
connector=connector,
|
|
161
|
-
timeout=aiohttp.ClientTimeout(total=30)
|
|
160
|
+
connector=connector, timeout=aiohttp.ClientTimeout(total=30)
|
|
162
161
|
)
|
|
163
162
|
app.state.http_session = session
|
|
164
|
-
logger.info(
|
|
163
|
+
logger.info(
|
|
164
|
+
"✅ Persistent HTTP session created - connections: 100, keepalive: 30s"
|
|
165
|
+
)
|
|
165
166
|
|
|
166
167
|
# Log available endpoints
|
|
167
168
|
base_url = (
|
wappa/core/types.py
CHANGED
|
@@ -11,7 +11,7 @@ from typing import Literal
|
|
|
11
11
|
class CacheType(Enum):
|
|
12
12
|
"""
|
|
13
13
|
Supported cache types for Wappa applications.
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
This enum defines the cache backends that Wappa can use for state management,
|
|
16
16
|
user data caching, and message logging.
|
|
17
17
|
"""
|
|
@@ -43,20 +43,20 @@ Example:
|
|
|
43
43
|
def validate_cache_type(cache_type: str) -> CacheType:
|
|
44
44
|
"""
|
|
45
45
|
Validate and convert a cache type string to CacheType enum.
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
Args:
|
|
48
48
|
cache_type: String representation of cache type
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
Returns:
|
|
51
51
|
Validated CacheType enum value
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
Raises:
|
|
54
54
|
ValueError: If cache_type is not supported
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
Example:
|
|
57
57
|
>>> validate_cache_type("redis")
|
|
58
58
|
CacheType.REDIS
|
|
59
|
-
|
|
59
|
+
|
|
60
60
|
>>> validate_cache_type("invalid")
|
|
61
61
|
ValueError: Unsupported cache type: invalid. Supported types: memory, redis, json
|
|
62
62
|
"""
|
|
@@ -73,10 +73,10 @@ def validate_cache_type(cache_type: str) -> CacheType:
|
|
|
73
73
|
def get_supported_cache_types() -> list[str]:
|
|
74
74
|
"""
|
|
75
75
|
Get list of all supported cache type strings.
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
Returns:
|
|
78
78
|
List of supported cache type strings
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
Example:
|
|
81
81
|
>>> get_supported_cache_types()
|
|
82
82
|
['memory', 'redis', 'json']
|
|
@@ -87,17 +87,17 @@ def get_supported_cache_types() -> list[str]:
|
|
|
87
87
|
def is_cache_type_supported(cache_type: str) -> bool:
|
|
88
88
|
"""
|
|
89
89
|
Check if a cache type string is supported.
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
Args:
|
|
92
92
|
cache_type: String to check
|
|
93
|
-
|
|
93
|
+
|
|
94
94
|
Returns:
|
|
95
95
|
True if cache type is supported, False otherwise
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
Example:
|
|
98
98
|
>>> is_cache_type_supported("redis")
|
|
99
99
|
True
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
>>> is_cache_type_supported("invalid")
|
|
102
102
|
False
|
|
103
103
|
"""
|
wappa/core/wappa_app.py
CHANGED
|
@@ -167,7 +167,7 @@ class Wappa:
|
|
|
167
167
|
Return a FastAPI ASGI application, building synchronously if needed.
|
|
168
168
|
|
|
169
169
|
This property enables uvicorn reload compatibility by providing a synchronous
|
|
170
|
-
way to access the FastAPI app. Plugin configuration is deferred to lifespan
|
|
170
|
+
way to access the FastAPI app. Plugin configuration is deferred to lifespan
|
|
171
171
|
hooks to maintain async initialization while keeping this property sync.
|
|
172
172
|
|
|
173
173
|
Returns:
|
|
@@ -205,7 +205,7 @@ class Wappa:
|
|
|
205
205
|
redoc_url="/redoc" if settings.is_development else None,
|
|
206
206
|
)
|
|
207
207
|
|
|
208
|
-
# Use WappaBuilder.build() - creates app with lifespan,
|
|
208
|
+
# Use WappaBuilder.build() - creates app with lifespan,
|
|
209
209
|
# defers plugin configuration to startup hooks
|
|
210
210
|
app = self._builder.build()
|
|
211
211
|
|
|
@@ -329,10 +329,12 @@ class Wappa:
|
|
|
329
329
|
|
|
330
330
|
return self
|
|
331
331
|
|
|
332
|
-
def add_middleware(
|
|
332
|
+
def add_middleware(
|
|
333
|
+
self, middleware_class: type, priority: int = 50, **kwargs
|
|
334
|
+
) -> "Wappa":
|
|
333
335
|
"""
|
|
334
336
|
Add middleware to the application with priority ordering.
|
|
335
|
-
|
|
337
|
+
|
|
336
338
|
This provides access to the underlying WappaBuilder's middleware system.
|
|
337
339
|
Priority determines execution order:
|
|
338
340
|
- Lower numbers run first (outer middleware)
|
|
@@ -349,7 +351,7 @@ class Wappa:
|
|
|
349
351
|
|
|
350
352
|
Example:
|
|
351
353
|
from fastapi.middleware.cors import CORSMiddleware
|
|
352
|
-
|
|
354
|
+
|
|
353
355
|
app = Wappa(cache="redis")
|
|
354
356
|
app.add_middleware(CORSMiddleware, allow_origins=["*"], priority=30)
|
|
355
357
|
app.set_event_handler(MyHandler())
|
|
@@ -379,9 +381,9 @@ class Wappa:
|
|
|
379
381
|
|
|
380
382
|
Example:
|
|
381
383
|
from fastapi import APIRouter
|
|
382
|
-
|
|
384
|
+
|
|
383
385
|
custom_router = APIRouter()
|
|
384
|
-
|
|
386
|
+
|
|
385
387
|
app = Wappa(cache="redis")
|
|
386
388
|
app.add_router(custom_router, prefix="/api/v1", tags=["custom"])
|
|
387
389
|
app.set_event_handler(MyHandler())
|
|
@@ -411,7 +413,7 @@ class Wappa:
|
|
|
411
413
|
app = Wappa(cache="redis")
|
|
412
414
|
app.configure(
|
|
413
415
|
title="My WhatsApp Bot",
|
|
414
|
-
version="2.0.0",
|
|
416
|
+
version="2.0.0",
|
|
415
417
|
description="Custom bot with advanced features"
|
|
416
418
|
)
|
|
417
419
|
app.set_event_handler(MyHandler())
|
wappa/database/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Wappa Database Components
|
|
3
3
|
|
|
4
|
-
Provides database abstraction and adapters for SQLModel/SQLAlchemy async
|
|
4
|
+
Provides database abstraction and adapters for SQLModel/SQLAlchemy async
|
|
5
5
|
connections. Supports multiple database engines with a unified interface.
|
|
6
6
|
|
|
7
7
|
Clean Architecture: Infrastructure layer database adapters.
|
|
@@ -9,7 +9,7 @@ Clean Architecture: Infrastructure layer database adapters.
|
|
|
9
9
|
Usage:
|
|
10
10
|
# Base adapter
|
|
11
11
|
from wappa.database import DatabaseAdapter
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
# Specific database adapters
|
|
14
14
|
from wappa.database import PostgreSQLAdapter, MySQLAdapter, SQLiteAdapter
|
|
15
15
|
"""
|
|
@@ -22,9 +22,8 @@ from .adapters.sqlite_adapter import SQLiteAdapter
|
|
|
22
22
|
__all__ = [
|
|
23
23
|
# Base Adapter
|
|
24
24
|
"DatabaseAdapter",
|
|
25
|
-
|
|
26
25
|
# Database Adapters (Clean Architecture: Infrastructure implementations)
|
|
27
26
|
"PostgreSQLAdapter",
|
|
28
|
-
"MySQLAdapter",
|
|
27
|
+
"MySQLAdapter",
|
|
29
28
|
"SQLiteAdapter",
|
|
30
29
|
]
|
|
@@ -36,8 +36,7 @@ def create_messenger_platform_enum() -> type[Enum]:
|
|
|
36
36
|
|
|
37
37
|
# Create enum members dynamically based on enabled platforms
|
|
38
38
|
enum_members = {
|
|
39
|
-
platform_name.upper(): platform_name
|
|
40
|
-
for platform_name in enabled_platforms.keys()
|
|
39
|
+
platform_name.upper(): platform_name for platform_name in enabled_platforms
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
# Create the enum class
|
|
@@ -195,10 +195,7 @@ class WhatsAppMediaFactory(MediaFactory):
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
# Create media object based on reference type
|
|
198
|
-
if is_url:
|
|
199
|
-
media_obj = {"link": media_reference}
|
|
200
|
-
else:
|
|
201
|
-
media_obj = {"id": media_reference}
|
|
198
|
+
media_obj = {"link": media_reference} if is_url else {"id": media_reference}
|
|
202
199
|
|
|
203
200
|
# Add optional caption
|
|
204
201
|
if caption:
|
|
@@ -232,10 +229,7 @@ class WhatsAppMediaFactory(MediaFactory):
|
|
|
232
229
|
}
|
|
233
230
|
|
|
234
231
|
# Create media object based on reference type
|
|
235
|
-
if is_url:
|
|
236
|
-
media_obj = {"link": media_reference}
|
|
237
|
-
else:
|
|
238
|
-
media_obj = {"id": media_reference}
|
|
232
|
+
media_obj = {"link": media_reference} if is_url else {"id": media_reference}
|
|
239
233
|
|
|
240
234
|
# Add optional caption
|
|
241
235
|
if caption:
|
|
@@ -269,10 +263,7 @@ class WhatsAppMediaFactory(MediaFactory):
|
|
|
269
263
|
}
|
|
270
264
|
|
|
271
265
|
# Create media object based on reference type
|
|
272
|
-
if is_url:
|
|
273
|
-
media_obj = {"link": media_reference}
|
|
274
|
-
else:
|
|
275
|
-
media_obj = {"id": media_reference}
|
|
266
|
+
media_obj = {"link": media_reference} if is_url else {"id": media_reference}
|
|
276
267
|
|
|
277
268
|
payload["audio"] = media_obj
|
|
278
269
|
|
|
@@ -302,10 +293,7 @@ class WhatsAppMediaFactory(MediaFactory):
|
|
|
302
293
|
}
|
|
303
294
|
|
|
304
295
|
# Create media object based on reference type
|
|
305
|
-
if is_url:
|
|
306
|
-
media_obj = {"link": media_reference}
|
|
307
|
-
else:
|
|
308
|
-
media_obj = {"id": media_reference}
|
|
296
|
+
media_obj = {"link": media_reference} if is_url else {"id": media_reference}
|
|
309
297
|
|
|
310
298
|
# Add optional filename
|
|
311
299
|
if filename:
|
|
@@ -339,10 +327,7 @@ class WhatsAppMediaFactory(MediaFactory):
|
|
|
339
327
|
}
|
|
340
328
|
|
|
341
329
|
# Create media object based on reference type
|
|
342
|
-
if is_url:
|
|
343
|
-
media_obj = {"link": media_reference}
|
|
344
|
-
else:
|
|
345
|
-
media_obj = {"id": media_reference}
|
|
330
|
+
media_obj = {"link": media_reference} if is_url else {"id": media_reference}
|
|
346
331
|
|
|
347
332
|
payload["sticker"] = media_obj
|
|
348
333
|
|
|
@@ -279,10 +279,7 @@ class WhatsAppMessageFactory(MessageFactory):
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
# Create media object based on reference type
|
|
282
|
-
if is_url:
|
|
283
|
-
media_obj = {"link": media_reference}
|
|
284
|
-
else:
|
|
285
|
-
media_obj = {"id": media_reference}
|
|
282
|
+
media_obj = {"link": media_reference} if is_url else {"id": media_reference}
|
|
286
283
|
|
|
287
284
|
# Add optional caption
|
|
288
285
|
if caption:
|
|
@@ -313,10 +310,7 @@ class WhatsAppMessageFactory(MessageFactory):
|
|
|
313
310
|
}
|
|
314
311
|
|
|
315
312
|
# Create media object based on reference type
|
|
316
|
-
if is_url:
|
|
317
|
-
media_obj = {"link": media_reference}
|
|
318
|
-
else:
|
|
319
|
-
media_obj = {"id": media_reference}
|
|
313
|
+
media_obj = {"link": media_reference} if is_url else {"id": media_reference}
|
|
320
314
|
|
|
321
315
|
# Add optional caption
|
|
322
316
|
if caption:
|
|
@@ -346,10 +340,7 @@ class WhatsAppMessageFactory(MessageFactory):
|
|
|
346
340
|
}
|
|
347
341
|
|
|
348
342
|
# Create media object based on reference type
|
|
349
|
-
if is_url:
|
|
350
|
-
media_obj = {"link": media_reference}
|
|
351
|
-
else:
|
|
352
|
-
media_obj = {"id": media_reference}
|
|
343
|
+
media_obj = {"link": media_reference} if is_url else {"id": media_reference}
|
|
353
344
|
|
|
354
345
|
payload["audio"] = media_obj
|
|
355
346
|
|
|
@@ -376,10 +367,7 @@ class WhatsAppMessageFactory(MessageFactory):
|
|
|
376
367
|
}
|
|
377
368
|
|
|
378
369
|
# Create media object based on reference type
|
|
379
|
-
if is_url:
|
|
380
|
-
media_obj = {"link": media_reference}
|
|
381
|
-
else:
|
|
382
|
-
media_obj = {"id": media_reference}
|
|
370
|
+
media_obj = {"link": media_reference} if is_url else {"id": media_reference}
|
|
383
371
|
|
|
384
372
|
# Add optional filename
|
|
385
373
|
if filename:
|
|
@@ -409,10 +397,7 @@ class WhatsAppMessageFactory(MessageFactory):
|
|
|
409
397
|
}
|
|
410
398
|
|
|
411
399
|
# Create media object based on reference type
|
|
412
|
-
if is_url:
|
|
413
|
-
media_obj = {"link": media_reference}
|
|
414
|
-
else:
|
|
415
|
-
media_obj = {"id": media_reference}
|
|
400
|
+
media_obj = {"link": media_reference} if is_url else {"id": media_reference}
|
|
416
401
|
|
|
417
402
|
payload["sticker"] = media_obj
|
|
418
403
|
|
|
@@ -210,7 +210,7 @@ class MessengerFactory:
|
|
|
210
210
|
# Clear all messengers for a platform
|
|
211
211
|
to_remove = [
|
|
212
212
|
key
|
|
213
|
-
for key in self._messenger_cache
|
|
213
|
+
for key in self._messenger_cache
|
|
214
214
|
if key.startswith(f"{platform.value}:")
|
|
215
215
|
]
|
|
216
216
|
for key in to_remove:
|
|
@@ -219,9 +219,7 @@ class MessengerFactory:
|
|
|
219
219
|
elif tenant_id:
|
|
220
220
|
# Clear all messengers for a tenant
|
|
221
221
|
to_remove = [
|
|
222
|
-
key
|
|
223
|
-
for key in self._messenger_cache.keys()
|
|
224
|
-
if key.endswith(f":{tenant_id}")
|
|
222
|
+
key for key in self._messenger_cache if key.endswith(f":{tenant_id}")
|
|
225
223
|
]
|
|
226
224
|
for key in to_remove:
|
|
227
225
|
del self._messenger_cache[key]
|
|
@@ -169,31 +169,31 @@ class ICache(ABC):
|
|
|
169
169
|
True if successful, False otherwise
|
|
170
170
|
"""
|
|
171
171
|
pass
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
@staticmethod
|
|
174
174
|
def create_table_key(table_name: str, pkid: str) -> str:
|
|
175
175
|
"""
|
|
176
176
|
Create a properly formatted table cache key.
|
|
177
|
-
|
|
177
|
+
|
|
178
178
|
This is a static utility method available on all cache implementations
|
|
179
179
|
to ensure consistent key formatting across cache backends.
|
|
180
|
-
|
|
180
|
+
|
|
181
181
|
Args:
|
|
182
182
|
table_name: Name of the table (e.g., "user_profiles", "message_logs")
|
|
183
183
|
pkid: Primary key ID (e.g., user_id, message_id)
|
|
184
|
-
|
|
184
|
+
|
|
185
185
|
Returns:
|
|
186
186
|
Formatted key string for use with cache methods
|
|
187
|
-
|
|
187
|
+
|
|
188
188
|
Example:
|
|
189
189
|
key = ICache.create_table_key("user_profiles", "12345")
|
|
190
190
|
# Returns: "user_profiles:12345"
|
|
191
191
|
"""
|
|
192
192
|
if not table_name or not pkid:
|
|
193
193
|
raise ValueError("Both table_name and pkid must be provided and non-empty")
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
# Sanitize inputs to avoid conflicts
|
|
196
196
|
safe_table_name = str(table_name).replace(":", "_")
|
|
197
197
|
safe_pkid = str(pkid).replace(":", "_")
|
|
198
|
-
|
|
198
|
+
|
|
199
199
|
return f"{safe_table_name}:{safe_pkid}"
|
|
@@ -14,9 +14,8 @@ and WhatsApp Cloud API 2025 specifications for the 4 core endpoints:
|
|
|
14
14
|
|
|
15
15
|
from abc import ABC, abstractmethod
|
|
16
16
|
from collections.abc import AsyncIterator
|
|
17
|
-
from contextlib import asynccontextmanager
|
|
18
17
|
from pathlib import Path
|
|
19
|
-
from typing import
|
|
18
|
+
from typing import AsyncContextManager, BinaryIO
|
|
20
19
|
|
|
21
20
|
from wappa.domain.models.media_result import (
|
|
22
21
|
MediaDeleteResult,
|
|
@@ -257,9 +256,7 @@ class IMediaHandler(ABC):
|
|
|
257
256
|
pass
|
|
258
257
|
|
|
259
258
|
@abstractmethod
|
|
260
|
-
async def get_media_as_bytes(
|
|
261
|
-
self, media_id: str
|
|
262
|
-
) -> MediaDownloadResult:
|
|
259
|
+
async def get_media_as_bytes(self, media_id: str) -> MediaDownloadResult:
|
|
263
260
|
"""
|
|
264
261
|
Download media as bytes without creating any files.
|
|
265
262
|
|
|
@@ -6,10 +6,8 @@ messaging platforms, providing consistent response structures while
|
|
|
6
6
|
maintaining compatibility with platform-specific response formats.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
import os
|
|
10
9
|
from datetime import datetime
|
|
11
10
|
from pathlib import Path
|
|
12
|
-
from typing import AsyncContextManager, Optional
|
|
13
11
|
|
|
14
12
|
from pydantic import BaseModel, Field
|
|
15
13
|
|
|
@@ -125,7 +123,7 @@ class MediaDownloadResult(BaseModel):
|
|
|
125
123
|
|
|
126
124
|
def mark_as_temp_file(self, cleanup_on_exit: bool = True):
|
|
127
125
|
"""Mark this result as containing a temporary file for cleanup.
|
|
128
|
-
|
|
126
|
+
|
|
129
127
|
Args:
|
|
130
128
|
cleanup_on_exit: Whether to automatically delete the temp file when context exits
|
|
131
129
|
"""
|
|
@@ -51,9 +51,7 @@ class PlatformCredentials(BaseModel):
|
|
|
51
51
|
def is_configured(self) -> bool:
|
|
52
52
|
"""Check if minimum required credentials are present."""
|
|
53
53
|
# For WhatsApp, we need access_token and phone_id
|
|
54
|
-
|
|
55
|
-
return True
|
|
56
|
-
return False
|
|
54
|
+
return bool(self.access_token and self.phone_id)
|
|
57
55
|
|
|
58
56
|
|
|
59
57
|
class PlatformLimits(BaseModel):
|
wappa/messaging/__init__.py
CHANGED
|
@@ -9,11 +9,11 @@ Clean Architecture: Application services and infrastructure implementations.
|
|
|
9
9
|
Usage (User Request: Quick access to WhatsApp messaging components):
|
|
10
10
|
# Core messaging interface
|
|
11
11
|
from wappa.messaging import IMessenger
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
# WhatsApp client and messenger
|
|
14
14
|
from wappa.messaging.whatsapp import WhatsAppClient, WhatsAppMessenger
|
|
15
|
-
|
|
16
|
-
# WhatsApp specialized handlers
|
|
15
|
+
|
|
16
|
+
# WhatsApp specialized handlers
|
|
17
17
|
from wappa.messaging.whatsapp import (
|
|
18
18
|
WhatsAppMediaHandler,
|
|
19
19
|
WhatsAppInteractiveHandler,
|
|
@@ -26,32 +26,29 @@ Usage (User Request: Quick access to WhatsApp messaging components):
|
|
|
26
26
|
from wappa.domain.interfaces.messaging_interface import IMessenger
|
|
27
27
|
|
|
28
28
|
# WhatsApp Client & Messenger (User Request: Quick access)
|
|
29
|
-
from .whatsapp.client import WhatsAppClient,
|
|
30
|
-
from .whatsapp.messenger import WhatsAppMessenger
|
|
29
|
+
from .whatsapp.client import WhatsAppClient, WhatsAppFormDataBuilder, WhatsAppUrlBuilder
|
|
31
30
|
|
|
32
31
|
# WhatsApp Specialized Handlers (User Request: Quick access)
|
|
33
32
|
from .whatsapp.handlers import (
|
|
33
|
+
WhatsAppInteractiveHandler,
|
|
34
34
|
WhatsAppMediaHandler,
|
|
35
|
-
WhatsAppInteractiveHandler,
|
|
36
|
-
WhatsAppTemplateHandler,
|
|
37
35
|
WhatsAppSpecializedHandler,
|
|
36
|
+
WhatsAppTemplateHandler,
|
|
38
37
|
)
|
|
38
|
+
from .whatsapp.messenger import WhatsAppMessenger
|
|
39
39
|
|
|
40
40
|
__all__ = [
|
|
41
41
|
# Core Interface
|
|
42
42
|
"IMessenger",
|
|
43
|
-
|
|
44
43
|
# WhatsApp Client & Utilities
|
|
45
44
|
"WhatsAppClient",
|
|
46
45
|
"WhatsAppUrlBuilder",
|
|
47
|
-
"WhatsAppFormDataBuilder",
|
|
48
|
-
|
|
46
|
+
"WhatsAppFormDataBuilder",
|
|
49
47
|
# WhatsApp Messenger
|
|
50
48
|
"WhatsAppMessenger",
|
|
51
|
-
|
|
52
49
|
# WhatsApp Handlers (User Request: Clean access to all handlers)
|
|
53
50
|
"WhatsAppMediaHandler",
|
|
54
51
|
"WhatsAppInteractiveHandler",
|
|
55
|
-
"WhatsAppTemplateHandler",
|
|
52
|
+
"WhatsAppTemplateHandler",
|
|
56
53
|
"WhatsAppSpecializedHandler",
|
|
57
54
|
]
|