wappa 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of wappa might be problematic. Click here for more details.

Files changed (211) hide show
  1. wappa/__init__.py +85 -0
  2. wappa/api/__init__.py +1 -0
  3. wappa/api/controllers/__init__.py +10 -0
  4. wappa/api/controllers/webhook_controller.py +441 -0
  5. wappa/api/dependencies/__init__.py +15 -0
  6. wappa/api/dependencies/whatsapp_dependencies.py +220 -0
  7. wappa/api/dependencies/whatsapp_media_dependencies.py +26 -0
  8. wappa/api/middleware/__init__.py +7 -0
  9. wappa/api/middleware/error_handler.py +158 -0
  10. wappa/api/middleware/owner.py +99 -0
  11. wappa/api/middleware/request_logging.py +184 -0
  12. wappa/api/routes/__init__.py +6 -0
  13. wappa/api/routes/health.py +102 -0
  14. wappa/api/routes/webhooks.py +211 -0
  15. wappa/api/routes/whatsapp/__init__.py +15 -0
  16. wappa/api/routes/whatsapp/whatsapp_interactive.py +429 -0
  17. wappa/api/routes/whatsapp/whatsapp_media.py +440 -0
  18. wappa/api/routes/whatsapp/whatsapp_messages.py +195 -0
  19. wappa/api/routes/whatsapp/whatsapp_specialized.py +516 -0
  20. wappa/api/routes/whatsapp/whatsapp_templates.py +431 -0
  21. wappa/api/routes/whatsapp_combined.py +35 -0
  22. wappa/cli/__init__.py +9 -0
  23. wappa/cli/main.py +199 -0
  24. wappa/core/__init__.py +6 -0
  25. wappa/core/config/__init__.py +5 -0
  26. wappa/core/config/settings.py +161 -0
  27. wappa/core/events/__init__.py +41 -0
  28. wappa/core/events/default_handlers.py +642 -0
  29. wappa/core/events/event_dispatcher.py +244 -0
  30. wappa/core/events/event_handler.py +247 -0
  31. wappa/core/events/webhook_factory.py +219 -0
  32. wappa/core/factory/__init__.py +15 -0
  33. wappa/core/factory/plugin.py +68 -0
  34. wappa/core/factory/wappa_builder.py +326 -0
  35. wappa/core/logging/__init__.py +5 -0
  36. wappa/core/logging/context.py +100 -0
  37. wappa/core/logging/logger.py +343 -0
  38. wappa/core/plugins/__init__.py +34 -0
  39. wappa/core/plugins/auth_plugin.py +169 -0
  40. wappa/core/plugins/cors_plugin.py +128 -0
  41. wappa/core/plugins/custom_middleware_plugin.py +182 -0
  42. wappa/core/plugins/database_plugin.py +235 -0
  43. wappa/core/plugins/rate_limit_plugin.py +183 -0
  44. wappa/core/plugins/redis_plugin.py +224 -0
  45. wappa/core/plugins/wappa_core_plugin.py +261 -0
  46. wappa/core/plugins/webhook_plugin.py +253 -0
  47. wappa/core/types.py +108 -0
  48. wappa/core/wappa_app.py +546 -0
  49. wappa/database/__init__.py +18 -0
  50. wappa/database/adapter.py +107 -0
  51. wappa/database/adapters/__init__.py +17 -0
  52. wappa/database/adapters/mysql_adapter.py +187 -0
  53. wappa/database/adapters/postgresql_adapter.py +169 -0
  54. wappa/database/adapters/sqlite_adapter.py +174 -0
  55. wappa/domain/__init__.py +28 -0
  56. wappa/domain/builders/__init__.py +5 -0
  57. wappa/domain/builders/message_builder.py +189 -0
  58. wappa/domain/entities/__init__.py +5 -0
  59. wappa/domain/enums/messenger_platform.py +123 -0
  60. wappa/domain/factories/__init__.py +6 -0
  61. wappa/domain/factories/media_factory.py +450 -0
  62. wappa/domain/factories/message_factory.py +497 -0
  63. wappa/domain/factories/messenger_factory.py +244 -0
  64. wappa/domain/interfaces/__init__.py +32 -0
  65. wappa/domain/interfaces/base_repository.py +94 -0
  66. wappa/domain/interfaces/cache_factory.py +85 -0
  67. wappa/domain/interfaces/cache_interface.py +199 -0
  68. wappa/domain/interfaces/expiry_repository.py +68 -0
  69. wappa/domain/interfaces/media_interface.py +311 -0
  70. wappa/domain/interfaces/messaging_interface.py +523 -0
  71. wappa/domain/interfaces/pubsub_repository.py +151 -0
  72. wappa/domain/interfaces/repository_factory.py +108 -0
  73. wappa/domain/interfaces/shared_state_repository.py +122 -0
  74. wappa/domain/interfaces/state_repository.py +123 -0
  75. wappa/domain/interfaces/tables_repository.py +215 -0
  76. wappa/domain/interfaces/user_repository.py +114 -0
  77. wappa/domain/interfaces/webhooks/__init__.py +1 -0
  78. wappa/domain/models/media_result.py +110 -0
  79. wappa/domain/models/platforms/__init__.py +15 -0
  80. wappa/domain/models/platforms/platform_config.py +104 -0
  81. wappa/domain/services/__init__.py +11 -0
  82. wappa/domain/services/tenant_credentials_service.py +56 -0
  83. wappa/messaging/__init__.py +7 -0
  84. wappa/messaging/whatsapp/__init__.py +1 -0
  85. wappa/messaging/whatsapp/client/__init__.py +5 -0
  86. wappa/messaging/whatsapp/client/whatsapp_client.py +417 -0
  87. wappa/messaging/whatsapp/handlers/__init__.py +13 -0
  88. wappa/messaging/whatsapp/handlers/whatsapp_interactive_handler.py +653 -0
  89. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +579 -0
  90. wappa/messaging/whatsapp/handlers/whatsapp_specialized_handler.py +434 -0
  91. wappa/messaging/whatsapp/handlers/whatsapp_template_handler.py +416 -0
  92. wappa/messaging/whatsapp/messenger/__init__.py +5 -0
  93. wappa/messaging/whatsapp/messenger/whatsapp_messenger.py +904 -0
  94. wappa/messaging/whatsapp/models/__init__.py +61 -0
  95. wappa/messaging/whatsapp/models/basic_models.py +65 -0
  96. wappa/messaging/whatsapp/models/interactive_models.py +287 -0
  97. wappa/messaging/whatsapp/models/media_models.py +215 -0
  98. wappa/messaging/whatsapp/models/specialized_models.py +304 -0
  99. wappa/messaging/whatsapp/models/template_models.py +261 -0
  100. wappa/persistence/cache_factory.py +93 -0
  101. wappa/persistence/json/__init__.py +14 -0
  102. wappa/persistence/json/cache_adapters.py +271 -0
  103. wappa/persistence/json/handlers/__init__.py +1 -0
  104. wappa/persistence/json/handlers/state_handler.py +250 -0
  105. wappa/persistence/json/handlers/table_handler.py +263 -0
  106. wappa/persistence/json/handlers/user_handler.py +213 -0
  107. wappa/persistence/json/handlers/utils/__init__.py +1 -0
  108. wappa/persistence/json/handlers/utils/file_manager.py +153 -0
  109. wappa/persistence/json/handlers/utils/key_factory.py +11 -0
  110. wappa/persistence/json/handlers/utils/serialization.py +121 -0
  111. wappa/persistence/json/json_cache_factory.py +76 -0
  112. wappa/persistence/json/storage_manager.py +285 -0
  113. wappa/persistence/memory/__init__.py +14 -0
  114. wappa/persistence/memory/cache_adapters.py +271 -0
  115. wappa/persistence/memory/handlers/__init__.py +1 -0
  116. wappa/persistence/memory/handlers/state_handler.py +250 -0
  117. wappa/persistence/memory/handlers/table_handler.py +280 -0
  118. wappa/persistence/memory/handlers/user_handler.py +213 -0
  119. wappa/persistence/memory/handlers/utils/__init__.py +1 -0
  120. wappa/persistence/memory/handlers/utils/key_factory.py +11 -0
  121. wappa/persistence/memory/handlers/utils/memory_store.py +317 -0
  122. wappa/persistence/memory/handlers/utils/ttl_manager.py +235 -0
  123. wappa/persistence/memory/memory_cache_factory.py +76 -0
  124. wappa/persistence/memory/storage_manager.py +235 -0
  125. wappa/persistence/redis/README.md +699 -0
  126. wappa/persistence/redis/__init__.py +11 -0
  127. wappa/persistence/redis/cache_adapters.py +285 -0
  128. wappa/persistence/redis/ops.py +880 -0
  129. wappa/persistence/redis/redis_cache_factory.py +71 -0
  130. wappa/persistence/redis/redis_client.py +231 -0
  131. wappa/persistence/redis/redis_handler/__init__.py +26 -0
  132. wappa/persistence/redis/redis_handler/state_handler.py +176 -0
  133. wappa/persistence/redis/redis_handler/table.py +158 -0
  134. wappa/persistence/redis/redis_handler/user.py +138 -0
  135. wappa/persistence/redis/redis_handler/utils/__init__.py +12 -0
  136. wappa/persistence/redis/redis_handler/utils/key_factory.py +32 -0
  137. wappa/persistence/redis/redis_handler/utils/serde.py +146 -0
  138. wappa/persistence/redis/redis_handler/utils/tenant_cache.py +268 -0
  139. wappa/persistence/redis/redis_manager.py +189 -0
  140. wappa/processors/__init__.py +6 -0
  141. wappa/processors/base_processor.py +262 -0
  142. wappa/processors/factory.py +550 -0
  143. wappa/processors/whatsapp_processor.py +810 -0
  144. wappa/schemas/__init__.py +6 -0
  145. wappa/schemas/core/__init__.py +71 -0
  146. wappa/schemas/core/base_message.py +499 -0
  147. wappa/schemas/core/base_status.py +322 -0
  148. wappa/schemas/core/base_webhook.py +312 -0
  149. wappa/schemas/core/types.py +253 -0
  150. wappa/schemas/core/webhook_interfaces/__init__.py +48 -0
  151. wappa/schemas/core/webhook_interfaces/base_components.py +293 -0
  152. wappa/schemas/core/webhook_interfaces/universal_webhooks.py +348 -0
  153. wappa/schemas/factory.py +754 -0
  154. wappa/schemas/webhooks/__init__.py +3 -0
  155. wappa/schemas/whatsapp/__init__.py +6 -0
  156. wappa/schemas/whatsapp/base_models.py +285 -0
  157. wappa/schemas/whatsapp/message_types/__init__.py +93 -0
  158. wappa/schemas/whatsapp/message_types/audio.py +350 -0
  159. wappa/schemas/whatsapp/message_types/button.py +267 -0
  160. wappa/schemas/whatsapp/message_types/contact.py +464 -0
  161. wappa/schemas/whatsapp/message_types/document.py +421 -0
  162. wappa/schemas/whatsapp/message_types/errors.py +195 -0
  163. wappa/schemas/whatsapp/message_types/image.py +424 -0
  164. wappa/schemas/whatsapp/message_types/interactive.py +430 -0
  165. wappa/schemas/whatsapp/message_types/location.py +416 -0
  166. wappa/schemas/whatsapp/message_types/order.py +372 -0
  167. wappa/schemas/whatsapp/message_types/reaction.py +271 -0
  168. wappa/schemas/whatsapp/message_types/sticker.py +328 -0
  169. wappa/schemas/whatsapp/message_types/system.py +317 -0
  170. wappa/schemas/whatsapp/message_types/text.py +411 -0
  171. wappa/schemas/whatsapp/message_types/unsupported.py +273 -0
  172. wappa/schemas/whatsapp/message_types/video.py +344 -0
  173. wappa/schemas/whatsapp/status_models.py +479 -0
  174. wappa/schemas/whatsapp/validators.py +454 -0
  175. wappa/schemas/whatsapp/webhook_container.py +438 -0
  176. wappa/webhooks/__init__.py +17 -0
  177. wappa/webhooks/core/__init__.py +71 -0
  178. wappa/webhooks/core/base_message.py +499 -0
  179. wappa/webhooks/core/base_status.py +322 -0
  180. wappa/webhooks/core/base_webhook.py +312 -0
  181. wappa/webhooks/core/types.py +253 -0
  182. wappa/webhooks/core/webhook_interfaces/__init__.py +48 -0
  183. wappa/webhooks/core/webhook_interfaces/base_components.py +293 -0
  184. wappa/webhooks/core/webhook_interfaces/universal_webhooks.py +441 -0
  185. wappa/webhooks/factory.py +754 -0
  186. wappa/webhooks/whatsapp/__init__.py +6 -0
  187. wappa/webhooks/whatsapp/base_models.py +285 -0
  188. wappa/webhooks/whatsapp/message_types/__init__.py +93 -0
  189. wappa/webhooks/whatsapp/message_types/audio.py +350 -0
  190. wappa/webhooks/whatsapp/message_types/button.py +267 -0
  191. wappa/webhooks/whatsapp/message_types/contact.py +464 -0
  192. wappa/webhooks/whatsapp/message_types/document.py +421 -0
  193. wappa/webhooks/whatsapp/message_types/errors.py +195 -0
  194. wappa/webhooks/whatsapp/message_types/image.py +424 -0
  195. wappa/webhooks/whatsapp/message_types/interactive.py +430 -0
  196. wappa/webhooks/whatsapp/message_types/location.py +416 -0
  197. wappa/webhooks/whatsapp/message_types/order.py +372 -0
  198. wappa/webhooks/whatsapp/message_types/reaction.py +271 -0
  199. wappa/webhooks/whatsapp/message_types/sticker.py +328 -0
  200. wappa/webhooks/whatsapp/message_types/system.py +317 -0
  201. wappa/webhooks/whatsapp/message_types/text.py +411 -0
  202. wappa/webhooks/whatsapp/message_types/unsupported.py +273 -0
  203. wappa/webhooks/whatsapp/message_types/video.py +344 -0
  204. wappa/webhooks/whatsapp/status_models.py +479 -0
  205. wappa/webhooks/whatsapp/validators.py +454 -0
  206. wappa/webhooks/whatsapp/webhook_container.py +438 -0
  207. wappa-0.1.0.dist-info/METADATA +269 -0
  208. wappa-0.1.0.dist-info/RECORD +211 -0
  209. wappa-0.1.0.dist-info/WHEEL +4 -0
  210. wappa-0.1.0.dist-info/entry_points.txt +2 -0
  211. wappa-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,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
+ )