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,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Request context management using contextvars for automatic propagation.
|
|
3
|
+
|
|
4
|
+
This module provides automatic context propagation throughout the entire request lifecycle
|
|
5
|
+
without requiring manual parameter passing. The context is set once in middleware and
|
|
6
|
+
automatically available to all components.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from contextvars import ContextVar
|
|
10
|
+
|
|
11
|
+
# Context variables for automatic propagation
|
|
12
|
+
_owner_context: ContextVar[str | None] = ContextVar(
|
|
13
|
+
"owner_id", default=None
|
|
14
|
+
) # From middleware/configuration
|
|
15
|
+
_tenant_context: ContextVar[str | None] = ContextVar(
|
|
16
|
+
"tenant_id", default=None
|
|
17
|
+
) # From webhook JSON
|
|
18
|
+
_user_context: ContextVar[str | None] = ContextVar(
|
|
19
|
+
"user_id", default=None
|
|
20
|
+
) # From webhook JSON
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def set_request_context(
|
|
24
|
+
owner_id: str | None = None,
|
|
25
|
+
tenant_id: str | None = None,
|
|
26
|
+
user_id: str | None = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Set the request context for the current async context.
|
|
30
|
+
|
|
31
|
+
This should be called during request processing and will automatically
|
|
32
|
+
propagate to all subsequent function calls in the same request.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
owner_id: Owner identifier from configuration (WhatsApp Business Account owner)
|
|
36
|
+
tenant_id: Tenant identifier from webhook payload (runtime business context)
|
|
37
|
+
user_id: User identifier from webhook payload (WhatsApp phone number, etc.)
|
|
38
|
+
"""
|
|
39
|
+
if owner_id is not None:
|
|
40
|
+
_owner_context.set(owner_id)
|
|
41
|
+
if tenant_id is not None:
|
|
42
|
+
_tenant_context.set(tenant_id)
|
|
43
|
+
if user_id is not None:
|
|
44
|
+
_user_context.set(user_id)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_current_owner_context() -> str | None:
|
|
48
|
+
"""
|
|
49
|
+
Get the current owner ID from context variables.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Current owner ID (configuration context), or None if not set
|
|
53
|
+
"""
|
|
54
|
+
return _owner_context.get()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_current_tenant_context() -> str | None:
|
|
58
|
+
"""
|
|
59
|
+
Get the current tenant ID from context variables.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Current tenant ID (webhook business context), or None if not set
|
|
63
|
+
"""
|
|
64
|
+
return _tenant_context.get()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_current_user_context() -> str | None:
|
|
68
|
+
"""
|
|
69
|
+
Get the current user ID from context variables.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Current user ID (webhook user context), or None if not set
|
|
73
|
+
"""
|
|
74
|
+
return _user_context.get()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def clear_request_context() -> None:
|
|
78
|
+
"""
|
|
79
|
+
Clear the request context.
|
|
80
|
+
|
|
81
|
+
This is typically not needed as context is automatically isolated
|
|
82
|
+
per request, but can be useful for testing.
|
|
83
|
+
"""
|
|
84
|
+
_owner_context.set(None)
|
|
85
|
+
_tenant_context.set(None)
|
|
86
|
+
_user_context.set(None)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_context_info() -> dict[str, str | None]:
|
|
90
|
+
"""
|
|
91
|
+
Get current context information for debugging.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
Dictionary with current owner_id, tenant_id and user_id
|
|
95
|
+
"""
|
|
96
|
+
return {
|
|
97
|
+
"owner_id": get_current_owner_context(),
|
|
98
|
+
"tenant_id": get_current_tenant_context(),
|
|
99
|
+
"user_id": get_current_user_context(),
|
|
100
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom Rich-based logger with tenant and user context support for Wappa framework.
|
|
3
|
+
|
|
4
|
+
Based on mimeiapify.utils.logger but customized for multi-tenant webhook processing.
|
|
5
|
+
Provides context-aware logging with tenant and user information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.logging import RichHandler
|
|
16
|
+
from rich.theme import Theme
|
|
17
|
+
|
|
18
|
+
from wappa.core.config.settings import settings
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CompactFormatter(logging.Formatter):
|
|
22
|
+
"""Custom formatter that shortens long module names for better readability."""
|
|
23
|
+
|
|
24
|
+
def format(self, record):
|
|
25
|
+
# Shorten long module names for better readability
|
|
26
|
+
if record.name.startswith("wappa."):
|
|
27
|
+
# Convert wappa.core.events.event_dispatcher -> events.dispatcher
|
|
28
|
+
parts = record.name.split(".")
|
|
29
|
+
if len(parts) > 2:
|
|
30
|
+
# Keep last 2 parts for most modules
|
|
31
|
+
if "event_dispatcher" in record.name:
|
|
32
|
+
record.name = "events.dispatcher"
|
|
33
|
+
elif "default_handlers" in record.name:
|
|
34
|
+
record.name = "events.handlers"
|
|
35
|
+
elif "whatsapp" in record.name:
|
|
36
|
+
# For WhatsApp modules, keep whatsapp.component
|
|
37
|
+
relevant_parts = [
|
|
38
|
+
p
|
|
39
|
+
for p in parts
|
|
40
|
+
if p in ["whatsapp", "messenger", "handlers", "client"]
|
|
41
|
+
]
|
|
42
|
+
record.name = (
|
|
43
|
+
".".join(relevant_parts[-2:])
|
|
44
|
+
if len(relevant_parts) >= 2
|
|
45
|
+
else ".".join(relevant_parts)
|
|
46
|
+
)
|
|
47
|
+
else:
|
|
48
|
+
# Default: keep last 2 parts
|
|
49
|
+
record.name = ".".join(parts[-2:])
|
|
50
|
+
|
|
51
|
+
return super().format(record)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Rich theme for colored output
|
|
55
|
+
_theme = Theme(
|
|
56
|
+
{
|
|
57
|
+
"info": "cyan",
|
|
58
|
+
"warning": "yellow",
|
|
59
|
+
"error": "bold red",
|
|
60
|
+
"debug": "dim white",
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
_console = Console(theme=_theme)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ContextLogger:
|
|
67
|
+
"""
|
|
68
|
+
Logger wrapper that adds tenant and user context to messages.
|
|
69
|
+
|
|
70
|
+
Following the old app's successful pattern - adds context as message prefixes
|
|
71
|
+
instead of trying to modify the format string.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
logger: logging.Logger,
|
|
77
|
+
tenant_id: str | None = None,
|
|
78
|
+
user_id: str | None = None,
|
|
79
|
+
):
|
|
80
|
+
self.logger = logger
|
|
81
|
+
self.tenant_id = tenant_id or "---"
|
|
82
|
+
self.user_id = user_id or "---"
|
|
83
|
+
|
|
84
|
+
def _format_message(self, message: str) -> str:
|
|
85
|
+
"""Add context prefix to message."""
|
|
86
|
+
# Get fresh context variables on each log call for dynamic context
|
|
87
|
+
from .context import get_current_tenant_context, get_current_user_context
|
|
88
|
+
|
|
89
|
+
current_tenant = get_current_tenant_context() or self.tenant_id
|
|
90
|
+
current_user = get_current_user_context() or self.user_id
|
|
91
|
+
|
|
92
|
+
if current_tenant and current_tenant != "---":
|
|
93
|
+
if current_user and current_user != "---":
|
|
94
|
+
return f"[T:{current_tenant}][U:{current_user}] {message}"
|
|
95
|
+
else:
|
|
96
|
+
return f"[T:{current_tenant}] {message}"
|
|
97
|
+
elif current_user and current_user != "---":
|
|
98
|
+
return f"[U:{current_user}] {message}"
|
|
99
|
+
return message
|
|
100
|
+
|
|
101
|
+
def debug(self, message: str, *args, **kwargs) -> None:
|
|
102
|
+
"""Log debug message with context."""
|
|
103
|
+
self.logger.debug(self._format_message(message), *args, **kwargs)
|
|
104
|
+
|
|
105
|
+
def info(self, message: str, *args, **kwargs) -> None:
|
|
106
|
+
"""Log info message with context."""
|
|
107
|
+
self.logger.info(self._format_message(message), *args, **kwargs)
|
|
108
|
+
|
|
109
|
+
def warning(self, message: str, *args, **kwargs) -> None:
|
|
110
|
+
"""Log warning message with context."""
|
|
111
|
+
self.logger.warning(self._format_message(message), *args, **kwargs)
|
|
112
|
+
|
|
113
|
+
def error(self, message: str, *args, **kwargs) -> None:
|
|
114
|
+
"""Log error message with context."""
|
|
115
|
+
self.logger.error(self._format_message(message), *args, **kwargs)
|
|
116
|
+
|
|
117
|
+
def critical(self, message: str, *args, **kwargs) -> None:
|
|
118
|
+
"""Log critical message with context."""
|
|
119
|
+
self.logger.critical(self._format_message(message), *args, **kwargs)
|
|
120
|
+
|
|
121
|
+
def exception(self, message: str, *args, **kwargs) -> None:
|
|
122
|
+
"""Log exception message with context."""
|
|
123
|
+
self.logger.exception(self._format_message(message), *args, **kwargs)
|
|
124
|
+
|
|
125
|
+
def bind(self, **kwargs) -> ContextLogger:
|
|
126
|
+
"""
|
|
127
|
+
Create a new ContextLogger with additional or updated context.
|
|
128
|
+
|
|
129
|
+
This method follows the Loguru-style bind pattern, returning a new
|
|
130
|
+
logger instance with updated context rather than modifying the current one.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
**kwargs: Context fields to bind. Common fields:
|
|
134
|
+
- tenant_id: Override or set tenant identifier
|
|
135
|
+
- user_id: Override or set user identifier
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
New ContextLogger instance with updated context
|
|
139
|
+
|
|
140
|
+
Example:
|
|
141
|
+
# Add tenant context to existing logger
|
|
142
|
+
new_logger = logger.bind(tenant_id="12345")
|
|
143
|
+
|
|
144
|
+
# Update both tenant and user context
|
|
145
|
+
contextual_logger = logger.bind(tenant_id="12345", user_id="user_67890")
|
|
146
|
+
"""
|
|
147
|
+
# Extract context fields, using current values as defaults
|
|
148
|
+
new_tenant_id = kwargs.get("tenant_id", self.tenant_id)
|
|
149
|
+
new_user_id = kwargs.get("user_id", self.user_id)
|
|
150
|
+
|
|
151
|
+
# Return new ContextLogger instance with updated context
|
|
152
|
+
return ContextLogger(self.logger, tenant_id=new_tenant_id, user_id=new_user_id)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ContextFilter(logging.Filter):
|
|
156
|
+
"""
|
|
157
|
+
Logging filter that adds tenant and user context to log records.
|
|
158
|
+
|
|
159
|
+
NOTE: This is kept for potential future use but not used in the current
|
|
160
|
+
implementation to avoid the format string dependency issue.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def __init__(self, tenant_id: str | None = None, user_id: str | None = None):
|
|
164
|
+
super().__init__()
|
|
165
|
+
self.tenant_id = tenant_id or "---"
|
|
166
|
+
self.user_id = user_id or "---"
|
|
167
|
+
|
|
168
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
169
|
+
# Add context fields to record if not already present
|
|
170
|
+
if not hasattr(record, "tenant_id"):
|
|
171
|
+
record.tenant_id = self.tenant_id
|
|
172
|
+
if not hasattr(record, "user_id"):
|
|
173
|
+
record.user_id = self.user_id
|
|
174
|
+
return True
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def setup_logging(
|
|
178
|
+
*,
|
|
179
|
+
level: str = "INFO",
|
|
180
|
+
mode: str = "PROD",
|
|
181
|
+
log_dir: str | None = None,
|
|
182
|
+
console_fmt: str | None = None,
|
|
183
|
+
file_fmt: str | None = None,
|
|
184
|
+
) -> None:
|
|
185
|
+
"""
|
|
186
|
+
Initialize the root logger with Rich formatting.
|
|
187
|
+
|
|
188
|
+
Following the old app's successful pattern with simple formats that don't
|
|
189
|
+
require context fields. Context will be added via logger wrapper classes.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
level : str
|
|
194
|
+
Logging level ("DEBUG", "INFO", "WARNING", "ERROR")
|
|
195
|
+
mode : str
|
|
196
|
+
"DEV" creates daily log files + console; anything else → console only
|
|
197
|
+
log_dir : str, optional
|
|
198
|
+
Directory for log files (DEV mode only)
|
|
199
|
+
console_fmt : str, optional
|
|
200
|
+
Console format string (simple format without context fields)
|
|
201
|
+
file_fmt : str, optional
|
|
202
|
+
File format string (simple format without context fields)
|
|
203
|
+
"""
|
|
204
|
+
lvl = level.upper()
|
|
205
|
+
lvl = lvl if lvl in ("DEBUG", "INFO", "WARNING", "ERROR") else "INFO"
|
|
206
|
+
|
|
207
|
+
# Simple formats without context fields (like old app)
|
|
208
|
+
# RichHandler already shows level and time, so we only need module name and message
|
|
209
|
+
console_format = console_fmt or "[%(name)s] %(message)s"
|
|
210
|
+
file_format = file_fmt or "%(asctime)s | %(levelname)s | %(name)s | %(message)s"
|
|
211
|
+
|
|
212
|
+
# Rich console handler
|
|
213
|
+
rich_handler = RichHandler(
|
|
214
|
+
console=_console,
|
|
215
|
+
rich_tracebacks=True,
|
|
216
|
+
tracebacks_show_locals=True,
|
|
217
|
+
show_time=True,
|
|
218
|
+
show_level=True,
|
|
219
|
+
markup=False,
|
|
220
|
+
)
|
|
221
|
+
rich_handler.setFormatter(CompactFormatter(console_format))
|
|
222
|
+
|
|
223
|
+
handlers: list[logging.Handler] = [rich_handler]
|
|
224
|
+
|
|
225
|
+
# Optional file handler for DEV mode
|
|
226
|
+
if mode.upper() == "DEV" and log_dir:
|
|
227
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
228
|
+
logfile = os.path.join(log_dir, f"wappa_{datetime.now():%Y%m%d}.log")
|
|
229
|
+
file_handler = logging.FileHandler(logfile, encoding="utf-8")
|
|
230
|
+
file_handler.setFormatter(CompactFormatter(file_format))
|
|
231
|
+
handlers.append(file_handler)
|
|
232
|
+
_console.print(f"[green]DEV mode:[/] console + file → {logfile}")
|
|
233
|
+
else:
|
|
234
|
+
_console.print(f"Logging configured for mode '{mode}'. Console only.")
|
|
235
|
+
|
|
236
|
+
# Configure root logger with simple format
|
|
237
|
+
logging.basicConfig(level=lvl, handlers=handlers, force=True)
|
|
238
|
+
|
|
239
|
+
# Announce logging setup
|
|
240
|
+
setup_logger = logging.getLogger("WappaLoggerSetup")
|
|
241
|
+
setup_logger.info(f"Logging initialized ({lvl})")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def setup_app_logging() -> None:
|
|
245
|
+
"""
|
|
246
|
+
Initialize application logging for Wappa platform.
|
|
247
|
+
|
|
248
|
+
Called once during FastAPI application startup.
|
|
249
|
+
"""
|
|
250
|
+
setup_logging(
|
|
251
|
+
level=settings.log_level,
|
|
252
|
+
mode="DEV" if settings.is_development else "PROD",
|
|
253
|
+
log_dir=settings.log_dir if settings.is_development else None,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def get_logger(name: str) -> ContextLogger:
|
|
258
|
+
"""
|
|
259
|
+
Get a logger that automatically uses request context variables.
|
|
260
|
+
|
|
261
|
+
This is the recommended way to get loggers in most components as it
|
|
262
|
+
automatically picks up tenant_id and user_id from the current request context
|
|
263
|
+
without requiring manual parameter passing.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
name: Logger name (usually __name__)
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
ContextLogger instance with automatic context from context variables
|
|
270
|
+
"""
|
|
271
|
+
# Import here to avoid circular imports
|
|
272
|
+
from .context import get_current_tenant_context, get_current_user_context
|
|
273
|
+
|
|
274
|
+
# Use context variables for automatic context propagation
|
|
275
|
+
effective_tenant_id = get_current_tenant_context()
|
|
276
|
+
effective_user_id = get_current_user_context()
|
|
277
|
+
|
|
278
|
+
base_logger = logging.getLogger(name)
|
|
279
|
+
return ContextLogger(
|
|
280
|
+
base_logger, tenant_id=effective_tenant_id, user_id=effective_user_id
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def get_app_logger() -> ContextLogger:
|
|
285
|
+
"""
|
|
286
|
+
Get application logger for general app events (startup, shutdown, etc.).
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
ContextLogger instance with app-level context
|
|
290
|
+
"""
|
|
291
|
+
return get_logger("wappa.app")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def get_api_logger(name: str | None = None) -> ContextLogger:
|
|
295
|
+
"""
|
|
296
|
+
Get API logger for application endpoints and controllers.
|
|
297
|
+
|
|
298
|
+
Alias for get_app_logger() to maintain compatibility with existing code.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
name: Optional logger name (ignored for compatibility)
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
ContextLogger instance with API-level context
|
|
305
|
+
"""
|
|
306
|
+
return get_logger(name or "wappa.api")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def get_webhook_logger(name: str, tenant_id: str, user_id: str) -> ContextLogger:
|
|
310
|
+
"""
|
|
311
|
+
Get a logger specifically configured for webhook processing.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
name: Logger name (usually __name__)
|
|
315
|
+
tenant_id: Tenant ID from webhook path
|
|
316
|
+
user_id: User ID from webhook payload (WAID, etc.)
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
ContextLogger with webhook context
|
|
320
|
+
"""
|
|
321
|
+
base_logger = logging.getLogger(name)
|
|
322
|
+
return ContextLogger(base_logger, tenant_id=tenant_id, user_id=user_id)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def update_logger_context(
|
|
326
|
+
context_logger: ContextLogger,
|
|
327
|
+
tenant_id: str | None = None,
|
|
328
|
+
user_id: str | None = None,
|
|
329
|
+
) -> None:
|
|
330
|
+
"""
|
|
331
|
+
Update the context of an existing ContextLogger.
|
|
332
|
+
|
|
333
|
+
Useful when tenant/user context becomes available during request processing.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
context_logger: ContextLogger instance to update
|
|
337
|
+
tenant_id: New tenant ID
|
|
338
|
+
user_id: New user ID
|
|
339
|
+
"""
|
|
340
|
+
if tenant_id is not None:
|
|
341
|
+
context_logger.tenant_id = tenant_id
|
|
342
|
+
if user_id is not None:
|
|
343
|
+
context_logger.user_id = user_id
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wappa Core Plugins Module
|
|
3
|
+
|
|
4
|
+
This module contains all the core plugins that extend Wappa functionality:
|
|
5
|
+
- WappaCorePlugin: Essential Wappa framework functionality (logging, middleware, routes)
|
|
6
|
+
- Database plugins for SQLModel/SQLAlchemy integration
|
|
7
|
+
- Redis plugin for caching and session management
|
|
8
|
+
- Middleware plugins (CORS, Auth, Rate Limiting)
|
|
9
|
+
- Webhook plugins for payment providers and custom endpoints
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .auth_plugin import AuthPlugin
|
|
13
|
+
from .cors_plugin import CORSPlugin
|
|
14
|
+
from .custom_middleware_plugin import CustomMiddlewarePlugin
|
|
15
|
+
from .database_plugin import DatabasePlugin
|
|
16
|
+
from .rate_limit_plugin import RateLimitPlugin
|
|
17
|
+
from .redis_plugin import RedisPlugin
|
|
18
|
+
from .wappa_core_plugin import WappaCorePlugin
|
|
19
|
+
from .webhook_plugin import WebhookPlugin
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
# Core Framework
|
|
23
|
+
"WappaCorePlugin",
|
|
24
|
+
# Core Infrastructure
|
|
25
|
+
"DatabasePlugin",
|
|
26
|
+
"RedisPlugin",
|
|
27
|
+
# Middleware
|
|
28
|
+
"CORSPlugin",
|
|
29
|
+
"AuthPlugin",
|
|
30
|
+
"RateLimitPlugin",
|
|
31
|
+
"CustomMiddlewarePlugin",
|
|
32
|
+
# Integrations
|
|
33
|
+
"WebhookPlugin",
|
|
34
|
+
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auth Plugin
|
|
3
|
+
|
|
4
|
+
Plugin for adding authentication middleware to Wappa applications.
|
|
5
|
+
Provides a flexible wrapper for various authentication backends.
|
|
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 AuthPlugin:
|
|
19
|
+
"""
|
|
20
|
+
Authentication middleware plugin for Wappa applications.
|
|
21
|
+
|
|
22
|
+
Provides a flexible wrapper for authentication middleware, supporting
|
|
23
|
+
various authentication backends like JWT, OAuth, API keys, etc.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
# JWT authentication
|
|
27
|
+
auth_plugin = AuthPlugin(
|
|
28
|
+
JWTMiddleware,
|
|
29
|
+
secret_key="your-secret-key",
|
|
30
|
+
algorithm="HS256"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# OAuth authentication
|
|
34
|
+
auth_plugin = AuthPlugin(
|
|
35
|
+
OAuthMiddleware,
|
|
36
|
+
client_id="your-client-id",
|
|
37
|
+
client_secret="your-client-secret"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Custom authentication
|
|
41
|
+
auth_plugin = AuthPlugin(
|
|
42
|
+
CustomAuthMiddleware,
|
|
43
|
+
api_key_header="X-API-Key"
|
|
44
|
+
)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
auth_middleware_class: type,
|
|
50
|
+
priority: int = 80, # High priority - runs early but after CORS
|
|
51
|
+
**middleware_kwargs: Any,
|
|
52
|
+
):
|
|
53
|
+
"""
|
|
54
|
+
Initialize authentication plugin.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
auth_middleware_class: Authentication middleware class
|
|
58
|
+
priority: Middleware priority (lower runs first/outer)
|
|
59
|
+
**middleware_kwargs: Arguments for the middleware class
|
|
60
|
+
"""
|
|
61
|
+
self.auth_middleware_class = auth_middleware_class
|
|
62
|
+
self.priority = priority
|
|
63
|
+
self.middleware_kwargs = middleware_kwargs
|
|
64
|
+
|
|
65
|
+
async def configure(self, builder: "WappaBuilder") -> None:
|
|
66
|
+
"""
|
|
67
|
+
Configure authentication plugin with WappaBuilder.
|
|
68
|
+
|
|
69
|
+
Adds the authentication middleware to the application.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
builder: WappaBuilder instance
|
|
73
|
+
"""
|
|
74
|
+
logger = get_app_logger()
|
|
75
|
+
|
|
76
|
+
# Add authentication middleware to builder
|
|
77
|
+
builder.add_middleware(
|
|
78
|
+
self.auth_middleware_class, priority=self.priority, **self.middleware_kwargs
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
logger.debug(
|
|
82
|
+
f"AuthPlugin configured with {self.auth_middleware_class.__name__} "
|
|
83
|
+
f"(priority: {self.priority})"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
async def startup(self, app: "FastAPI") -> None:
|
|
87
|
+
"""
|
|
88
|
+
Authentication plugin startup.
|
|
89
|
+
|
|
90
|
+
Can be used for authentication backend initialization,
|
|
91
|
+
key validation, etc.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
app: FastAPI application instance
|
|
95
|
+
"""
|
|
96
|
+
logger = get_app_logger()
|
|
97
|
+
logger.debug(f"AuthPlugin startup - {self.auth_middleware_class.__name__}")
|
|
98
|
+
|
|
99
|
+
async def shutdown(self, app: "FastAPI") -> None:
|
|
100
|
+
"""
|
|
101
|
+
Authentication plugin shutdown.
|
|
102
|
+
|
|
103
|
+
Can be used for cleaning up authentication resources.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
app: FastAPI application instance
|
|
107
|
+
"""
|
|
108
|
+
logger = get_app_logger()
|
|
109
|
+
logger.debug(f"AuthPlugin shutdown - {self.auth_middleware_class.__name__}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Convenience functions for common authentication patterns
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def create_jwt_auth_plugin(
|
|
116
|
+
secret_key: str, algorithm: str = "HS256", **kwargs: Any
|
|
117
|
+
) -> AuthPlugin:
|
|
118
|
+
"""
|
|
119
|
+
Create a JWT authentication plugin.
|
|
120
|
+
|
|
121
|
+
Note: This is a convenience function. You'll need to provide
|
|
122
|
+
an actual JWT middleware implementation.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
secret_key: JWT secret key
|
|
126
|
+
algorithm: JWT algorithm
|
|
127
|
+
**kwargs: Additional JWT middleware arguments
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Configured AuthPlugin for JWT authentication
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
# This is a placeholder - you'd import your actual JWT middleware
|
|
134
|
+
from your_auth_library import JWTMiddleware
|
|
135
|
+
|
|
136
|
+
return AuthPlugin(
|
|
137
|
+
JWTMiddleware, secret_key=secret_key, algorithm=algorithm, **kwargs
|
|
138
|
+
)
|
|
139
|
+
except ImportError:
|
|
140
|
+
raise ImportError(
|
|
141
|
+
"JWT middleware not found. Please implement or install a JWT middleware library."
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def create_api_key_auth_plugin(
|
|
146
|
+
api_key_header: str = "X-API-Key", **kwargs: Any
|
|
147
|
+
) -> AuthPlugin:
|
|
148
|
+
"""
|
|
149
|
+
Create an API key authentication plugin.
|
|
150
|
+
|
|
151
|
+
Note: This is a convenience function. You'll need to provide
|
|
152
|
+
an actual API key middleware implementation.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
api_key_header: Header name for API key
|
|
156
|
+
**kwargs: Additional API key middleware arguments
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Configured AuthPlugin for API key authentication
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
# This is a placeholder - you'd import your actual API key middleware
|
|
163
|
+
from your_auth_library import APIKeyMiddleware
|
|
164
|
+
|
|
165
|
+
return AuthPlugin(APIKeyMiddleware, api_key_header=api_key_header, **kwargs)
|
|
166
|
+
except ImportError:
|
|
167
|
+
raise ImportError(
|
|
168
|
+
"API key middleware not found. Please implement or install an API key middleware library."
|
|
169
|
+
)
|