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
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rate Limit Plugin
|
|
3
|
+
|
|
4
|
+
Plugin for adding rate limiting middleware to Wappa applications.
|
|
5
|
+
Provides protection against abuse and DoS attacks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from ...core.logging.logger import get_app_logger
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from fastapi import FastAPI
|
|
14
|
+
|
|
15
|
+
from ...core.factory.wappa_builder import WappaBuilder
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RateLimitPlugin:
|
|
19
|
+
"""
|
|
20
|
+
Rate limiting middleware plugin for Wappa applications.
|
|
21
|
+
|
|
22
|
+
Provides request rate limiting to protect against abuse and DoS attacks.
|
|
23
|
+
Can be configured with different strategies and backends.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
# Basic rate limiting
|
|
27
|
+
rate_limit_plugin = RateLimitPlugin(
|
|
28
|
+
RateLimiterMiddleware,
|
|
29
|
+
max_requests=100,
|
|
30
|
+
window_seconds=60
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Redis-backed rate limiting
|
|
34
|
+
rate_limit_plugin = RateLimitPlugin(
|
|
35
|
+
RedisRateLimiterMiddleware,
|
|
36
|
+
max_requests=1000,
|
|
37
|
+
window_seconds=3600,
|
|
38
|
+
redis_url="redis://localhost:6379"
|
|
39
|
+
)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
rate_limit_middleware_class: type,
|
|
45
|
+
priority: int = 70, # Medium-high priority
|
|
46
|
+
**middleware_kwargs: Any,
|
|
47
|
+
):
|
|
48
|
+
"""
|
|
49
|
+
Initialize rate limit plugin.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
rate_limit_middleware_class: Rate limiting middleware class
|
|
53
|
+
priority: Middleware priority (lower runs first/outer)
|
|
54
|
+
**middleware_kwargs: Arguments for the middleware class
|
|
55
|
+
"""
|
|
56
|
+
self.rate_limit_middleware_class = rate_limit_middleware_class
|
|
57
|
+
self.priority = priority
|
|
58
|
+
self.middleware_kwargs = middleware_kwargs
|
|
59
|
+
|
|
60
|
+
async def configure(self, builder: "WappaBuilder") -> None:
|
|
61
|
+
"""
|
|
62
|
+
Configure rate limit plugin with WappaBuilder.
|
|
63
|
+
|
|
64
|
+
Adds the rate limiting middleware to the application.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
builder: WappaBuilder instance
|
|
68
|
+
"""
|
|
69
|
+
logger = get_app_logger()
|
|
70
|
+
|
|
71
|
+
# Add rate limiting middleware to builder
|
|
72
|
+
builder.add_middleware(
|
|
73
|
+
self.rate_limit_middleware_class,
|
|
74
|
+
priority=self.priority,
|
|
75
|
+
**self.middleware_kwargs,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
logger.debug(
|
|
79
|
+
f"RateLimitPlugin configured with {self.rate_limit_middleware_class.__name__} "
|
|
80
|
+
f"(priority: {self.priority})"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
async def startup(self, app: "FastAPI") -> None:
|
|
84
|
+
"""
|
|
85
|
+
Rate limit plugin startup.
|
|
86
|
+
|
|
87
|
+
Can be used for rate limiter backend initialization.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
app: FastAPI application instance
|
|
91
|
+
"""
|
|
92
|
+
logger = get_app_logger()
|
|
93
|
+
logger.debug(
|
|
94
|
+
f"RateLimitPlugin startup - {self.rate_limit_middleware_class.__name__}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
async def shutdown(self, app: "FastAPI") -> None:
|
|
98
|
+
"""
|
|
99
|
+
Rate limit plugin shutdown.
|
|
100
|
+
|
|
101
|
+
Can be used for cleaning up rate limiter resources.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
app: FastAPI application instance
|
|
105
|
+
"""
|
|
106
|
+
logger = get_app_logger()
|
|
107
|
+
logger.debug(
|
|
108
|
+
f"RateLimitPlugin shutdown - {self.rate_limit_middleware_class.__name__}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Convenience functions for common rate limiting patterns
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def create_memory_rate_limit_plugin(
|
|
116
|
+
max_requests: int = 100, window_seconds: int = 60, **kwargs: Any
|
|
117
|
+
) -> RateLimitPlugin:
|
|
118
|
+
"""
|
|
119
|
+
Create an in-memory rate limiting plugin.
|
|
120
|
+
|
|
121
|
+
Note: This is a convenience function. You'll need to provide
|
|
122
|
+
an actual rate limiting middleware implementation.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
max_requests: Maximum requests per window
|
|
126
|
+
window_seconds: Time window in seconds
|
|
127
|
+
**kwargs: Additional rate limiter arguments
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Configured RateLimitPlugin for in-memory rate limiting
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
# This is a placeholder - you'd import your actual rate limiting middleware
|
|
134
|
+
from your_rate_limit_library import MemoryRateLimiterMiddleware
|
|
135
|
+
|
|
136
|
+
return RateLimitPlugin(
|
|
137
|
+
MemoryRateLimiterMiddleware,
|
|
138
|
+
max_requests=max_requests,
|
|
139
|
+
window_seconds=window_seconds,
|
|
140
|
+
**kwargs,
|
|
141
|
+
)
|
|
142
|
+
except ImportError:
|
|
143
|
+
raise ImportError(
|
|
144
|
+
"Rate limiting middleware not found. Please implement or install a rate limiting middleware library."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def create_redis_rate_limit_plugin(
|
|
149
|
+
max_requests: int = 1000,
|
|
150
|
+
window_seconds: int = 3600,
|
|
151
|
+
redis_url: str = "redis://localhost:6379",
|
|
152
|
+
**kwargs: Any,
|
|
153
|
+
) -> RateLimitPlugin:
|
|
154
|
+
"""
|
|
155
|
+
Create a Redis-backed rate limiting plugin.
|
|
156
|
+
|
|
157
|
+
Note: This is a convenience function. You'll need to provide
|
|
158
|
+
an actual Redis rate limiting middleware implementation.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
max_requests: Maximum requests per window
|
|
162
|
+
window_seconds: Time window in seconds
|
|
163
|
+
redis_url: Redis connection URL
|
|
164
|
+
**kwargs: Additional rate limiter arguments
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Configured RateLimitPlugin for Redis-backed rate limiting
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
# This is a placeholder - you'd import your actual Redis rate limiting middleware
|
|
171
|
+
from your_rate_limit_library import RedisRateLimiterMiddleware
|
|
172
|
+
|
|
173
|
+
return RateLimitPlugin(
|
|
174
|
+
RedisRateLimiterMiddleware,
|
|
175
|
+
max_requests=max_requests,
|
|
176
|
+
window_seconds=window_seconds,
|
|
177
|
+
redis_url=redis_url,
|
|
178
|
+
**kwargs,
|
|
179
|
+
)
|
|
180
|
+
except ImportError:
|
|
181
|
+
raise ImportError(
|
|
182
|
+
"Redis rate limiting middleware not found. Please implement or install a Redis rate limiting middleware library."
|
|
183
|
+
)
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Redis Plugin
|
|
3
|
+
|
|
4
|
+
Plugin for integrating Redis caching functionality with the Wappa framework.
|
|
5
|
+
Uses the Wappa library's own RedisManager for clean dependency management.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Any
|
|
9
|
+
|
|
10
|
+
from ...core.logging.logger import get_app_logger
|
|
11
|
+
from ...persistence.redis.redis_manager import RedisManager
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from fastapi import FastAPI
|
|
15
|
+
|
|
16
|
+
from ...core.factory.wappa_builder import WappaBuilder
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RedisPlugin:
|
|
20
|
+
"""
|
|
21
|
+
Redis plugin for Wappa applications.
|
|
22
|
+
|
|
23
|
+
This plugin integrates Redis caching functionality using Wappa's own
|
|
24
|
+
RedisManager and ICacheFactory pattern. It automatically uses settings.redis_url
|
|
25
|
+
and provides clean dependency injection through the event handler cache_factory.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
# Basic usage (uses settings.redis_url automatically)
|
|
29
|
+
redis_plugin = RedisPlugin()
|
|
30
|
+
|
|
31
|
+
# With custom max connections
|
|
32
|
+
redis_plugin = RedisPlugin(max_connections=100)
|
|
33
|
+
|
|
34
|
+
builder.add_plugin(redis_plugin)
|
|
35
|
+
|
|
36
|
+
# Access in event handlers through ICacheFactory
|
|
37
|
+
user_cache = self.cache_factory.create_user_cache(tenant_id, user_id)
|
|
38
|
+
await user_cache.set('key', 'value')
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, max_connections: int | None = None, **redis_config: Any):
|
|
42
|
+
"""
|
|
43
|
+
Initialize Redis plugin.
|
|
44
|
+
|
|
45
|
+
Redis URL is automatically loaded from settings.redis_url.
|
|
46
|
+
The plugin uses hook-based architecture - Redis initialization
|
|
47
|
+
happens during the startup hook, not during plugin construction.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
max_connections: Max Redis connections (defaults to settings.redis_max_connections)
|
|
51
|
+
**redis_config: Additional Redis configuration options
|
|
52
|
+
"""
|
|
53
|
+
self.max_connections = max_connections
|
|
54
|
+
self.redis_config = redis_config
|
|
55
|
+
|
|
56
|
+
def configure(self, builder: "WappaBuilder") -> None:
|
|
57
|
+
"""
|
|
58
|
+
Configure Redis plugin with WappaBuilder using hook-based architecture.
|
|
59
|
+
|
|
60
|
+
Registers Redis initialization and cleanup as hooks with the builder's
|
|
61
|
+
unified lifespan management system. This ensures Redis starts after
|
|
62
|
+
core Wappa functionality (logging) and shuts down before core cleanup.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
builder: WappaBuilder instance to register hooks with
|
|
66
|
+
"""
|
|
67
|
+
# Register Redis lifecycle hooks with appropriate priorities
|
|
68
|
+
# Priority 20: After core startup (10) but before user hooks (50)
|
|
69
|
+
builder.add_startup_hook(self._redis_startup, priority=20)
|
|
70
|
+
builder.add_shutdown_hook(self._redis_shutdown, priority=20)
|
|
71
|
+
|
|
72
|
+
logger = get_app_logger()
|
|
73
|
+
logger.debug("🔧 RedisPlugin configured - registered startup/shutdown hooks")
|
|
74
|
+
|
|
75
|
+
async def startup(self, app: "FastAPI") -> None:
|
|
76
|
+
"""
|
|
77
|
+
Plugin startup method required by WappaPlugin protocol.
|
|
78
|
+
|
|
79
|
+
Delegates to _redis_startup hook method for actual implementation.
|
|
80
|
+
This maintains compatibility with both the plugin protocol and
|
|
81
|
+
the hook-based architecture.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
app: FastAPI application instance
|
|
85
|
+
"""
|
|
86
|
+
await self._redis_startup(app)
|
|
87
|
+
|
|
88
|
+
async def shutdown(self, app: "FastAPI") -> None:
|
|
89
|
+
"""
|
|
90
|
+
Plugin shutdown method required by WappaPlugin protocol.
|
|
91
|
+
|
|
92
|
+
Delegates to _redis_shutdown hook method for actual implementation.
|
|
93
|
+
This maintains compatibility with both the plugin protocol and
|
|
94
|
+
the hook-based architecture.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
app: FastAPI application instance
|
|
98
|
+
"""
|
|
99
|
+
await self._redis_shutdown(app)
|
|
100
|
+
|
|
101
|
+
async def _redis_startup(self, app: "FastAPI") -> None:
|
|
102
|
+
"""
|
|
103
|
+
Redis initialization hook - runs after core Wappa startup.
|
|
104
|
+
|
|
105
|
+
This hook is registered with priority 20, ensuring it runs after
|
|
106
|
+
core Wappa functionality (priority 10) has initialized logging
|
|
107
|
+
and other essential services.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
app: FastAPI application instance
|
|
111
|
+
"""
|
|
112
|
+
logger = get_app_logger()
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
# Import settings for explicit configuration
|
|
116
|
+
from ...core.config.settings import settings
|
|
117
|
+
|
|
118
|
+
# Use explicit settings - more readable than None
|
|
119
|
+
redis_url = settings.redis_url
|
|
120
|
+
max_conn = self.max_connections or settings.redis_max_connections
|
|
121
|
+
|
|
122
|
+
logger.info("=== REDIS CACHE INITIALIZATION ===")
|
|
123
|
+
logger.info(
|
|
124
|
+
f"🔴 Redis URL: {redis_url} (max_connections: {max_conn})"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# Initialize Redis pools with explicit settings
|
|
128
|
+
await RedisManager.initialize(
|
|
129
|
+
redis_url=redis_url, # Explicit, not None - more readable
|
|
130
|
+
max_connections=max_conn,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Store RedisManager reference in app state for cache factory access
|
|
134
|
+
app.state.redis_manager = RedisManager
|
|
135
|
+
|
|
136
|
+
# Get detailed health status for confirmation
|
|
137
|
+
health_status = await RedisManager.get_health_status()
|
|
138
|
+
healthy_pools = [
|
|
139
|
+
alias
|
|
140
|
+
for alias, status in health_status.get("pools", {}).items()
|
|
141
|
+
if status.get("status") == "healthy"
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
# Success confirmation with pool details
|
|
145
|
+
pool_info = RedisManager.get_pool_info()
|
|
146
|
+
logger.info(
|
|
147
|
+
f"✅ Redis startup completed! "
|
|
148
|
+
f"Healthy pools: {len(healthy_pools)}/{len(pool_info)} "
|
|
149
|
+
f"({', '.join(f'{alias}:db{db}' for alias, db in pool_info.items())})"
|
|
150
|
+
)
|
|
151
|
+
logger.info("===============================")
|
|
152
|
+
|
|
153
|
+
# Debug detailed pool information
|
|
154
|
+
logger.debug(f"Redis pool health details: {health_status}")
|
|
155
|
+
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logger.error(f"❌ Redis startup hook failed: {e}", exc_info=True)
|
|
158
|
+
raise RuntimeError(f"RedisPlugin startup hook failed: {e}") from e
|
|
159
|
+
|
|
160
|
+
async def _redis_shutdown(self, app: "FastAPI") -> None:
|
|
161
|
+
"""
|
|
162
|
+
Redis cleanup hook - runs before core Wappa shutdown.
|
|
163
|
+
|
|
164
|
+
This hook is registered with priority 20, ensuring it runs before
|
|
165
|
+
core Wappa cleanup (priority 90) to properly close Redis connections
|
|
166
|
+
while logging is still available.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
app: FastAPI application instance
|
|
170
|
+
"""
|
|
171
|
+
logger = get_app_logger()
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
# Clean up Redis connections if initialized
|
|
175
|
+
if hasattr(app.state, "redis_manager") and RedisManager.is_initialized():
|
|
176
|
+
logger.info("=== REDIS CACHE SHUTDOWN ===")
|
|
177
|
+
logger.debug("🔴 Cleaning up Redis connections...")
|
|
178
|
+
await RedisManager.cleanup()
|
|
179
|
+
logger.info("✅ Redis shutdown completed")
|
|
180
|
+
logger.info("==============================")
|
|
181
|
+
|
|
182
|
+
# Clean up app state
|
|
183
|
+
if hasattr(app.state, "redis_manager"):
|
|
184
|
+
del app.state.redis_manager
|
|
185
|
+
logger.debug("🧹 Redis manager removed from app state")
|
|
186
|
+
|
|
187
|
+
except Exception as e:
|
|
188
|
+
# Don't re-raise in shutdown - log and continue
|
|
189
|
+
logger.error(f"❌ Error during Redis shutdown hook: {e}", exc_info=True)
|
|
190
|
+
|
|
191
|
+
async def get_health_status(self, app: "FastAPI") -> dict[str, Any]:
|
|
192
|
+
"""
|
|
193
|
+
Get Redis health status for monitoring.
|
|
194
|
+
|
|
195
|
+
Uses the existing RedisManager.get_health_status() method
|
|
196
|
+
to provide consistent health reporting.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
app: FastAPI application instance
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Dictionary with Redis health information
|
|
203
|
+
"""
|
|
204
|
+
if not RedisManager.is_initialized():
|
|
205
|
+
return {
|
|
206
|
+
"healthy": False,
|
|
207
|
+
"error": "Redis manager not initialized",
|
|
208
|
+
"initialized": False,
|
|
209
|
+
"plugin": "RedisPlugin",
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
health_status = await RedisManager.get_health_status()
|
|
214
|
+
return {
|
|
215
|
+
"healthy": health_status.get("initialized", False),
|
|
216
|
+
"plugin": "RedisPlugin",
|
|
217
|
+
**health_status,
|
|
218
|
+
}
|
|
219
|
+
except Exception as e:
|
|
220
|
+
return {
|
|
221
|
+
"healthy": False,
|
|
222
|
+
"error": str(e),
|
|
223
|
+
"plugin": "RedisPlugin",
|
|
224
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wappa Core Plugin
|
|
3
|
+
|
|
4
|
+
This plugin encapsulates all traditional Wappa functionality, providing the foundation
|
|
5
|
+
for the Wappa framework through the plugin system. It includes logging, middleware,
|
|
6
|
+
routes, and lifespan management that was previously hardcoded in the Wappa class.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
import aiohttp
|
|
12
|
+
from fastapi import FastAPI
|
|
13
|
+
|
|
14
|
+
from wappa.api.middleware.error_handler import ErrorHandlerMiddleware
|
|
15
|
+
from wappa.api.middleware.owner import OwnerMiddleware
|
|
16
|
+
from wappa.api.middleware.request_logging import RequestLoggingMiddleware
|
|
17
|
+
from wappa.api.routes.health import router as health_router
|
|
18
|
+
from wappa.api.routes.whatsapp_combined import whatsapp_router
|
|
19
|
+
|
|
20
|
+
from ..config.settings import settings
|
|
21
|
+
from ..logging.logger import get_app_logger, setup_app_logging
|
|
22
|
+
from ..types import CacheType
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from ..factory.wappa_builder import WappaBuilder
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WappaCorePlugin:
|
|
29
|
+
"""
|
|
30
|
+
Core Wappa functionality as a plugin.
|
|
31
|
+
|
|
32
|
+
This plugin provides all the essential Wappa framework functionality:
|
|
33
|
+
- Application logging setup
|
|
34
|
+
- HTTP session management
|
|
35
|
+
- Core middleware stack (Owner, ErrorHandler, RequestLogging)
|
|
36
|
+
- Core routes (Health, WhatsApp combined)
|
|
37
|
+
- Webhook URL generation and logging
|
|
38
|
+
- Cache type configuration
|
|
39
|
+
|
|
40
|
+
By implementing core functionality as a plugin, we achieve a unified
|
|
41
|
+
architecture where all Wappa applications use the same plugin-based
|
|
42
|
+
foundation, whether they use simple Wappa() or advanced WappaBuilder.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, cache_type: CacheType = CacheType.MEMORY):
|
|
46
|
+
"""
|
|
47
|
+
Initialize Wappa core plugin.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
cache_type: Cache backend to use for the application
|
|
51
|
+
"""
|
|
52
|
+
self.cache_type = cache_type
|
|
53
|
+
|
|
54
|
+
def configure(self, builder: "WappaBuilder") -> None:
|
|
55
|
+
"""
|
|
56
|
+
Configure core Wappa functionality with the builder.
|
|
57
|
+
|
|
58
|
+
Registers:
|
|
59
|
+
- Core middleware with appropriate priorities
|
|
60
|
+
- Core routes (health, whatsapp)
|
|
61
|
+
- Core startup/shutdown hooks
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
builder: WappaBuilder instance to configure
|
|
65
|
+
"""
|
|
66
|
+
logger = get_app_logger()
|
|
67
|
+
logger.debug("🏗️ Configuring WappaCorePlugin...")
|
|
68
|
+
|
|
69
|
+
# Register core middleware with proper priorities
|
|
70
|
+
# Higher priority numbers run closer to routes (inner middleware)
|
|
71
|
+
builder.add_middleware(
|
|
72
|
+
OwnerMiddleware, priority=90
|
|
73
|
+
) # Outer - tenant extraction
|
|
74
|
+
builder.add_middleware(ErrorHandlerMiddleware, priority=80) # Error handling
|
|
75
|
+
builder.add_middleware(
|
|
76
|
+
RequestLoggingMiddleware, priority=70
|
|
77
|
+
) # Request logging (inner)
|
|
78
|
+
|
|
79
|
+
# Register core routes
|
|
80
|
+
builder.add_router(health_router)
|
|
81
|
+
builder.add_router(whatsapp_router)
|
|
82
|
+
|
|
83
|
+
# Register core lifespan hooks with high priority (runs first/last)
|
|
84
|
+
builder.add_startup_hook(self._core_startup, priority=10) # First to run
|
|
85
|
+
builder.add_shutdown_hook(self._core_shutdown, priority=90) # Last to run
|
|
86
|
+
|
|
87
|
+
logger.debug(
|
|
88
|
+
f"✅ WappaCorePlugin configured - cache_type: {self.cache_type.value}, "
|
|
89
|
+
f"middleware: 3, routes: 2, hooks: 2"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def startup(self, app: FastAPI) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Plugin startup method required by WappaPlugin protocol.
|
|
95
|
+
|
|
96
|
+
Delegates to _core_startup hook method for actual implementation.
|
|
97
|
+
This maintains compatibility with both the plugin protocol and
|
|
98
|
+
the hook-based architecture.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
app: FastAPI application instance
|
|
102
|
+
"""
|
|
103
|
+
await self._core_startup(app)
|
|
104
|
+
|
|
105
|
+
async def shutdown(self, app: FastAPI) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Plugin shutdown method required by WappaPlugin protocol.
|
|
108
|
+
|
|
109
|
+
Delegates to _core_shutdown hook method for actual implementation.
|
|
110
|
+
This maintains compatibility with both the plugin protocol and
|
|
111
|
+
the hook-based architecture.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
app: FastAPI application instance
|
|
115
|
+
"""
|
|
116
|
+
await self._core_shutdown(app)
|
|
117
|
+
|
|
118
|
+
async def _core_startup(self, app: FastAPI) -> None:
|
|
119
|
+
"""
|
|
120
|
+
Core Wappa startup functionality.
|
|
121
|
+
|
|
122
|
+
This hook runs first (priority 10) to establish the foundation that
|
|
123
|
+
other plugins and hooks can depend on:
|
|
124
|
+
- Logging system initialization
|
|
125
|
+
- HTTP session for connection pooling
|
|
126
|
+
- Cache type configuration
|
|
127
|
+
- Webhook URL generation and display
|
|
128
|
+
- Development mode configuration
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
app: FastAPI application instance
|
|
132
|
+
"""
|
|
133
|
+
logger = None
|
|
134
|
+
try:
|
|
135
|
+
# Initialize logging first - this is critical for all other operations
|
|
136
|
+
setup_app_logging()
|
|
137
|
+
logger = get_app_logger()
|
|
138
|
+
|
|
139
|
+
logger.info(f"🚀 Starting Wappa Framework v{settings.version}")
|
|
140
|
+
logger.info(f"📊 Environment: {settings.environment}")
|
|
141
|
+
logger.info(f"👤 Owner ID: {settings.owner_id}")
|
|
142
|
+
logger.info(f"📝 Log level: {settings.log_level}")
|
|
143
|
+
logger.info(f"💾 Cache type: {self.cache_type.value}")
|
|
144
|
+
|
|
145
|
+
if settings.is_development:
|
|
146
|
+
logger.info(f"🔧 Development mode - logs: {settings.log_dir}")
|
|
147
|
+
|
|
148
|
+
# Set cache type in app state for WebhookController detection
|
|
149
|
+
app.state.wappa_cache_type = self.cache_type.value
|
|
150
|
+
logger.debug(f"💾 Set app.state.wappa_cache_type = {self.cache_type.value}")
|
|
151
|
+
|
|
152
|
+
# Initialize HTTP session for connection pooling (correct scope for app lifecycle)
|
|
153
|
+
app.state.http_session = aiohttp.ClientSession()
|
|
154
|
+
logger.debug("🌐 HTTP session initialized for connection pooling")
|
|
155
|
+
|
|
156
|
+
# Log available endpoints
|
|
157
|
+
base_url = (
|
|
158
|
+
f"http://localhost:{settings.port}"
|
|
159
|
+
if settings.is_development
|
|
160
|
+
else "https://your-domain.com"
|
|
161
|
+
)
|
|
162
|
+
logger.info("=== AVAILABLE ENDPOINTS ===")
|
|
163
|
+
logger.info(f"🏥 Health Check: {base_url}/health")
|
|
164
|
+
logger.info(f"📊 Detailed Health: {base_url}/health/detailed")
|
|
165
|
+
logger.info(f"📱 WhatsApp API: {base_url}/api/whatsapp/...")
|
|
166
|
+
logger.info(
|
|
167
|
+
f"📖 API Documentation: {base_url}/docs"
|
|
168
|
+
if settings.is_development
|
|
169
|
+
else "📖 API docs disabled in production"
|
|
170
|
+
)
|
|
171
|
+
logger.info("============================")
|
|
172
|
+
|
|
173
|
+
# Generate and display WhatsApp webhook URL
|
|
174
|
+
await self._display_webhook_urls(logger, base_url)
|
|
175
|
+
|
|
176
|
+
logger.info("✅ Wappa core startup completed successfully")
|
|
177
|
+
|
|
178
|
+
except Exception as e:
|
|
179
|
+
if logger:
|
|
180
|
+
logger.error(f"❌ Error during Wappa core startup: {e}", exc_info=True)
|
|
181
|
+
else:
|
|
182
|
+
print(f"💥 Critical error during logging setup: {e}")
|
|
183
|
+
raise
|
|
184
|
+
|
|
185
|
+
async def _core_shutdown(self, app: FastAPI) -> None:
|
|
186
|
+
"""
|
|
187
|
+
Core Wappa shutdown functionality.
|
|
188
|
+
|
|
189
|
+
This hook runs last (priority 90) to clean up core resources after
|
|
190
|
+
all other plugins have shut down:
|
|
191
|
+
- Close HTTP session
|
|
192
|
+
- Final logging
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
app: FastAPI application instance
|
|
196
|
+
"""
|
|
197
|
+
logger = get_app_logger()
|
|
198
|
+
logger.info("🛑 Starting Wappa core shutdown...")
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
# Close HTTP session if it exists
|
|
202
|
+
if hasattr(app.state, "http_session"):
|
|
203
|
+
await app.state.http_session.close()
|
|
204
|
+
logger.debug("🌐 HTTP session closed")
|
|
205
|
+
|
|
206
|
+
# Clear cache type from app state
|
|
207
|
+
if hasattr(app.state, "wappa_cache_type"):
|
|
208
|
+
del app.state.wappa_cache_type
|
|
209
|
+
logger.debug("💾 Cache type cleared from app state")
|
|
210
|
+
|
|
211
|
+
logger.info("✅ Wappa core shutdown completed")
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
logger.error(f"❌ Error during Wappa core shutdown: {e}", exc_info=True)
|
|
215
|
+
|
|
216
|
+
async def _display_webhook_urls(self, logger, base_url: str) -> None:
|
|
217
|
+
"""
|
|
218
|
+
Generate and display WhatsApp webhook URLs for user convenience.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
logger: Logger instance
|
|
222
|
+
base_url: Base URL for the application
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
# Import here to avoid circular imports during startup
|
|
226
|
+
from ..events.webhook_factory import webhook_url_factory
|
|
227
|
+
|
|
228
|
+
whatsapp_webhook_url = webhook_url_factory.generate_whatsapp_webhook_url()
|
|
229
|
+
|
|
230
|
+
logger.info("=== WHATSAPP WEBHOOK URL ===")
|
|
231
|
+
logger.info(f"📍 Primary Webhook URL: {whatsapp_webhook_url}")
|
|
232
|
+
logger.info(" • Use this single URL in WhatsApp Business settings")
|
|
233
|
+
logger.info(" • Handles both verification (GET) and webhooks (POST)")
|
|
234
|
+
logger.info(" • Auto-configured with your WP_PHONE_ID from .env")
|
|
235
|
+
logger.info("=============================")
|
|
236
|
+
logger.info("")
|
|
237
|
+
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.warning(f"⚠️ Could not generate webhook URL: {e}")
|
|
240
|
+
|
|
241
|
+
def get_cache_type(self) -> CacheType:
|
|
242
|
+
"""
|
|
243
|
+
Get the configured cache type.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
CacheType enum value
|
|
247
|
+
"""
|
|
248
|
+
return self.cache_type
|
|
249
|
+
|
|
250
|
+
def set_cache_type(self, cache_type: CacheType) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Update the cache type configuration.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
cache_type: New cache type to use
|
|
256
|
+
|
|
257
|
+
Note:
|
|
258
|
+
This should be called before the application starts, as the
|
|
259
|
+
cache type is set in app state during startup.
|
|
260
|
+
"""
|
|
261
|
+
self.cache_type = cache_type
|