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