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,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WhatsApp messaging dependency injection.
|
|
3
|
+
|
|
4
|
+
Provides dependency injection for WhatsApp messaging services including
|
|
5
|
+
factory pattern, client management, and messenger implementations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from fastapi import Depends, Request
|
|
9
|
+
|
|
10
|
+
# Note: Using context-based tenant access instead of request-based
|
|
11
|
+
from wappa.core.logging.logger import get_logger
|
|
12
|
+
from wappa.domain.builders.message_builder import MessageBuilder
|
|
13
|
+
from wappa.domain.factories.message_factory import (
|
|
14
|
+
MessageFactory,
|
|
15
|
+
WhatsAppMessageFactory,
|
|
16
|
+
)
|
|
17
|
+
from wappa.domain.interfaces.messaging_interface import IMessenger
|
|
18
|
+
from wappa.domain.services.tenant_credentials_service import TenantCredentialsService
|
|
19
|
+
from wappa.messaging.whatsapp.client.whatsapp_client import WhatsAppClient
|
|
20
|
+
from wappa.messaging.whatsapp.handlers.whatsapp_interactive_handler import (
|
|
21
|
+
WhatsAppInteractiveHandler,
|
|
22
|
+
)
|
|
23
|
+
from wappa.messaging.whatsapp.handlers.whatsapp_media_handler import (
|
|
24
|
+
WhatsAppMediaHandler,
|
|
25
|
+
)
|
|
26
|
+
from wappa.messaging.whatsapp.handlers.whatsapp_specialized_handler import (
|
|
27
|
+
WhatsAppSpecializedHandler,
|
|
28
|
+
)
|
|
29
|
+
from wappa.messaging.whatsapp.handlers.whatsapp_template_handler import (
|
|
30
|
+
WhatsAppTemplateHandler,
|
|
31
|
+
)
|
|
32
|
+
from wappa.messaging.whatsapp.messenger.whatsapp_messenger import WhatsAppMessenger
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def get_whatsapp_message_factory() -> MessageFactory:
|
|
36
|
+
"""Get WhatsApp message factory.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
MessageFactory implementation for WhatsApp platform
|
|
40
|
+
"""
|
|
41
|
+
return WhatsAppMessageFactory()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def get_whatsapp_client(request: Request) -> WhatsAppClient:
|
|
45
|
+
"""Get configured WhatsApp client with tenant-specific credentials.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
request: FastAPI request object containing HTTP session
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Configured WhatsApp client with persistent session and tenant credentials
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
ValueError: If tenant credentials are invalid
|
|
55
|
+
"""
|
|
56
|
+
from wappa.core.logging.context import get_current_tenant_context
|
|
57
|
+
|
|
58
|
+
# Get persistent HTTP session from app state (created in main.py lifespan)
|
|
59
|
+
session = request.app.state.http_session
|
|
60
|
+
|
|
61
|
+
# Get tenant ID from context (set by webhook processing)
|
|
62
|
+
tenant_id = get_current_tenant_context()
|
|
63
|
+
if not tenant_id:
|
|
64
|
+
raise ValueError("No tenant context available - webhook processing required")
|
|
65
|
+
|
|
66
|
+
# Get tenant-specific access token (future: from database)
|
|
67
|
+
access_token = TenantCredentialsService.get_whatsapp_access_token(tenant_id)
|
|
68
|
+
|
|
69
|
+
# Validate tenant
|
|
70
|
+
if not TenantCredentialsService.validate_tenant(tenant_id):
|
|
71
|
+
raise ValueError(f"Invalid or inactive tenant: {tenant_id}")
|
|
72
|
+
|
|
73
|
+
# Create tenant-aware logger
|
|
74
|
+
logger = get_logger(__name__)
|
|
75
|
+
|
|
76
|
+
# Create WhatsApp client with dependency injection
|
|
77
|
+
client = WhatsAppClient(
|
|
78
|
+
session=session,
|
|
79
|
+
access_token=access_token,
|
|
80
|
+
phone_number_id=tenant_id, # tenant_id IS the phone_number_id
|
|
81
|
+
logger=logger,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return client
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
async def get_whatsapp_media_handler(
|
|
88
|
+
client: WhatsAppClient = Depends(get_whatsapp_client),
|
|
89
|
+
) -> WhatsAppMediaHandler:
|
|
90
|
+
"""Get configured WhatsApp media handler with tenant-specific context.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
client: Configured WhatsApp client with persistent session
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Configured WhatsApp media handler for upload/download operations
|
|
97
|
+
"""
|
|
98
|
+
from wappa.core.logging.context import get_current_tenant_context
|
|
99
|
+
|
|
100
|
+
tenant_id = get_current_tenant_context()
|
|
101
|
+
if not tenant_id:
|
|
102
|
+
raise ValueError("No tenant context available")
|
|
103
|
+
return WhatsAppMediaHandler(client=client, tenant_id=tenant_id)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def get_whatsapp_interactive_handler(
|
|
107
|
+
client: WhatsAppClient = Depends(get_whatsapp_client),
|
|
108
|
+
) -> WhatsAppInteractiveHandler:
|
|
109
|
+
"""Get configured WhatsApp interactive handler with tenant-specific context.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
client: Configured WhatsApp client with persistent session
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Configured WhatsApp interactive handler for button/list/CTA operations
|
|
116
|
+
"""
|
|
117
|
+
from wappa.core.logging.context import get_current_tenant_context
|
|
118
|
+
|
|
119
|
+
tenant_id = get_current_tenant_context()
|
|
120
|
+
if not tenant_id:
|
|
121
|
+
raise ValueError("No tenant context available")
|
|
122
|
+
return WhatsAppInteractiveHandler(client=client, tenant_id=tenant_id)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def get_whatsapp_template_handler(
|
|
126
|
+
client: WhatsAppClient = Depends(get_whatsapp_client),
|
|
127
|
+
) -> WhatsAppTemplateHandler:
|
|
128
|
+
"""Get configured WhatsApp template handler with tenant-specific context.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
client: Configured WhatsApp client with persistent session
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Configured WhatsApp template handler for business template operations
|
|
135
|
+
"""
|
|
136
|
+
from wappa.core.logging.context import get_current_tenant_context
|
|
137
|
+
|
|
138
|
+
tenant_id = get_current_tenant_context()
|
|
139
|
+
if not tenant_id:
|
|
140
|
+
raise ValueError("No tenant context available")
|
|
141
|
+
return WhatsAppTemplateHandler(client=client, tenant_id=tenant_id)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async def get_whatsapp_specialized_handler(
|
|
145
|
+
client: WhatsAppClient = Depends(get_whatsapp_client),
|
|
146
|
+
) -> WhatsAppSpecializedHandler:
|
|
147
|
+
"""Get configured WhatsApp specialized handler with tenant-specific context.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
client: Configured WhatsApp client with persistent session
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
Configured WhatsApp specialized handler for contact and location operations
|
|
154
|
+
"""
|
|
155
|
+
from wappa.core.logging.context import get_current_tenant_context
|
|
156
|
+
|
|
157
|
+
tenant_id = get_current_tenant_context()
|
|
158
|
+
if not tenant_id:
|
|
159
|
+
raise ValueError("No tenant context available")
|
|
160
|
+
return WhatsAppSpecializedHandler(client=client, tenant_id=tenant_id)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
async def get_whatsapp_messenger(
|
|
164
|
+
client: WhatsAppClient = Depends(get_whatsapp_client),
|
|
165
|
+
media_handler: WhatsAppMediaHandler = Depends(get_whatsapp_media_handler),
|
|
166
|
+
interactive_handler: WhatsAppInteractiveHandler = Depends(
|
|
167
|
+
get_whatsapp_interactive_handler
|
|
168
|
+
),
|
|
169
|
+
template_handler: WhatsAppTemplateHandler = Depends(get_whatsapp_template_handler),
|
|
170
|
+
specialized_handler: WhatsAppSpecializedHandler = Depends(
|
|
171
|
+
get_whatsapp_specialized_handler
|
|
172
|
+
),
|
|
173
|
+
) -> IMessenger:
|
|
174
|
+
"""Get unified WhatsApp messenger implementation with complete functionality.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
client: Configured WhatsApp client
|
|
178
|
+
media_handler: Configured media handler for upload operations
|
|
179
|
+
interactive_handler: Configured interactive handler for button/list/CTA operations
|
|
180
|
+
template_handler: Configured template handler for business template operations
|
|
181
|
+
specialized_handler: Configured specialized handler for contact/location operations
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Complete IMessenger implementation for WhatsApp messaging (text + media + interactive + template + specialized)
|
|
185
|
+
"""
|
|
186
|
+
from wappa.core.logging.context import get_current_tenant_context
|
|
187
|
+
|
|
188
|
+
tenant_id = get_current_tenant_context()
|
|
189
|
+
if not tenant_id:
|
|
190
|
+
raise ValueError("No tenant context available")
|
|
191
|
+
|
|
192
|
+
return WhatsAppMessenger(
|
|
193
|
+
client=client,
|
|
194
|
+
media_handler=media_handler,
|
|
195
|
+
interactive_handler=interactive_handler,
|
|
196
|
+
template_handler=template_handler,
|
|
197
|
+
specialized_handler=specialized_handler,
|
|
198
|
+
tenant_id=tenant_id,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def get_message_builder(
|
|
203
|
+
factory: MessageFactory = Depends(get_whatsapp_message_factory),
|
|
204
|
+
) -> MessageBuilder:
|
|
205
|
+
"""Get message builder for fluent message construction.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
factory: Message factory for creating platform-specific payloads
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
MessageBuilder instance for fluent message construction
|
|
212
|
+
|
|
213
|
+
Note: The recipient should be set when using the builder
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
# Return a builder factory function since recipient is set per message
|
|
217
|
+
def create_builder(recipient: str) -> MessageBuilder:
|
|
218
|
+
return MessageBuilder(factory, recipient)
|
|
219
|
+
|
|
220
|
+
return create_builder
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WhatsApp media messaging dependency injection.
|
|
3
|
+
|
|
4
|
+
Provides dependency injection for WhatsApp media services including
|
|
5
|
+
media handlers, media messengers, and media factories.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from wappa.domain.factories.media_factory import MediaFactory, WhatsAppMediaFactory
|
|
9
|
+
|
|
10
|
+
# WhatsAppMediaMessenger removed - using unified WhatsAppMessenger instead
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def get_whatsapp_media_factory() -> MediaFactory:
|
|
14
|
+
"""Get WhatsApp media factory.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
MediaFactory implementation for WhatsApp platform
|
|
18
|
+
"""
|
|
19
|
+
return WhatsAppMediaFactory()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# get_whatsapp_media_handler moved to whatsapp_dependencies.py to eliminate duplication
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# WhatsAppMediaMessenger dependency removed - using unified WhatsAppMessenger from whatsapp_dependencies.py instead
|
|
26
|
+
# This eliminates DRY violation and architectural redundancy
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Middleware module for Wappa API."""
|
|
2
|
+
|
|
3
|
+
from .error_handler import ErrorHandlerMiddleware
|
|
4
|
+
from .owner import OwnerMiddleware
|
|
5
|
+
from .request_logging import RequestLoggingMiddleware
|
|
6
|
+
|
|
7
|
+
__all__ = ["ErrorHandlerMiddleware", "RequestLoggingMiddleware", "OwnerMiddleware"]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Global error handling middleware with tenant and context awareness.
|
|
3
|
+
|
|
4
|
+
Provides structured error responses and comprehensive logging for Wappa framework.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import traceback
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from fastapi import HTTPException, Request
|
|
11
|
+
from fastapi.responses import JSONResponse
|
|
12
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
13
|
+
from starlette.responses import Response
|
|
14
|
+
|
|
15
|
+
from wappa.core.logging.logger import get_logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ErrorHandlerMiddleware(BaseHTTPMiddleware):
|
|
19
|
+
"""
|
|
20
|
+
Global error handling middleware with tenant-aware logging.
|
|
21
|
+
|
|
22
|
+
Catches all unhandled exceptions and provides structured error responses
|
|
23
|
+
while maintaining security by not exposing internal details in production.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
async def dispatch(self, request: Request, call_next) -> Response:
|
|
27
|
+
"""Process request with comprehensive error handling."""
|
|
28
|
+
try:
|
|
29
|
+
response = await call_next(request)
|
|
30
|
+
return response
|
|
31
|
+
|
|
32
|
+
except HTTPException as http_exc:
|
|
33
|
+
# HTTP exceptions are handled by FastAPI, but we log them with context
|
|
34
|
+
await self._log_http_exception(request, http_exc)
|
|
35
|
+
raise # Re-raise to let FastAPI handle the response
|
|
36
|
+
|
|
37
|
+
except Exception as exc:
|
|
38
|
+
# Handle unexpected exceptions
|
|
39
|
+
return await self._handle_unexpected_exception(request, exc)
|
|
40
|
+
|
|
41
|
+
async def _log_http_exception(self, request: Request, exc: HTTPException) -> None:
|
|
42
|
+
"""Log HTTP exceptions with tenant context."""
|
|
43
|
+
tenant_id = getattr(request.state, "tenant_id", "unknown")
|
|
44
|
+
logger = get_logger(__name__)
|
|
45
|
+
|
|
46
|
+
# Extract user context from request if available
|
|
47
|
+
user_id = getattr(request.state, "user_id", "unknown")
|
|
48
|
+
|
|
49
|
+
logger.warning(
|
|
50
|
+
f"HTTP {exc.status_code} - {request.method} {request.url.path} - "
|
|
51
|
+
f"Detail: {exc.detail}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
async def _handle_unexpected_exception(
|
|
55
|
+
self, request: Request, exc: Exception
|
|
56
|
+
) -> JSONResponse:
|
|
57
|
+
"""Handle unexpected exceptions with proper logging and response."""
|
|
58
|
+
tenant_id = getattr(request.state, "tenant_id", "unknown")
|
|
59
|
+
user_id = getattr(request.state, "user_id", "unknown")
|
|
60
|
+
|
|
61
|
+
# Get context-aware logger
|
|
62
|
+
logger = get_logger(__name__)
|
|
63
|
+
|
|
64
|
+
# Log the full exception with context
|
|
65
|
+
logger.error(
|
|
66
|
+
f"Unhandled exception in {request.method} {request.url.path}: {exc}",
|
|
67
|
+
exc_info=True,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Determine error response based on environment
|
|
71
|
+
if self._is_webhook_endpoint(request.url.path):
|
|
72
|
+
# Webhook endpoints need specific error handling
|
|
73
|
+
return await self._create_webhook_error_response(exc)
|
|
74
|
+
else:
|
|
75
|
+
# Regular API endpoints
|
|
76
|
+
return await self._create_api_error_response(exc)
|
|
77
|
+
|
|
78
|
+
def _is_webhook_endpoint(self, path: str) -> bool:
|
|
79
|
+
"""Check if the request is to a webhook endpoint."""
|
|
80
|
+
return path.startswith("/webhook/")
|
|
81
|
+
|
|
82
|
+
async def _create_webhook_error_response(self, exc: Exception) -> JSONResponse:
|
|
83
|
+
"""
|
|
84
|
+
Create error response for webhook endpoints.
|
|
85
|
+
|
|
86
|
+
Webhook providers expect specific response formats and status codes.
|
|
87
|
+
"""
|
|
88
|
+
error_response = {
|
|
89
|
+
"status": "error",
|
|
90
|
+
"message": "Webhook processing failed",
|
|
91
|
+
"type": "webhook_error",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# In development, add more details
|
|
95
|
+
from wappa.core.config.settings import settings
|
|
96
|
+
|
|
97
|
+
if settings.is_development:
|
|
98
|
+
error_response["debug"] = {
|
|
99
|
+
"exception_type": type(exc).__name__,
|
|
100
|
+
"exception_message": str(exc),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return JSONResponse(status_code=500, content=error_response)
|
|
104
|
+
|
|
105
|
+
async def _create_api_error_response(self, exc: Exception) -> JSONResponse:
|
|
106
|
+
"""Create error response for regular API endpoints."""
|
|
107
|
+
from wappa.core.config.settings import settings
|
|
108
|
+
|
|
109
|
+
# Base error response
|
|
110
|
+
error_response: dict[str, Any] = {
|
|
111
|
+
"detail": "Internal server error",
|
|
112
|
+
"type": "internal_error",
|
|
113
|
+
"timestamp": self._get_current_timestamp(),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Add development-specific debugging information
|
|
117
|
+
if settings.is_development:
|
|
118
|
+
error_response["debug"] = {
|
|
119
|
+
"exception_type": type(exc).__name__,
|
|
120
|
+
"exception_message": str(exc),
|
|
121
|
+
"traceback": traceback.format_exc().split("\n"),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return JSONResponse(status_code=500, content=error_response)
|
|
125
|
+
|
|
126
|
+
def _get_current_timestamp(self) -> float:
|
|
127
|
+
"""Get current timestamp for error responses."""
|
|
128
|
+
import time
|
|
129
|
+
|
|
130
|
+
return time.time()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class ValidationErrorHandler:
|
|
134
|
+
"""
|
|
135
|
+
Custom handler for Pydantic validation errors.
|
|
136
|
+
|
|
137
|
+
Provides more user-friendly validation error messages.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def format_validation_error(exc: any) -> dict[str, Any]:
|
|
142
|
+
"""Format Pydantic validation errors for API responses."""
|
|
143
|
+
errors = []
|
|
144
|
+
|
|
145
|
+
for error in exc.errors():
|
|
146
|
+
errors.append(
|
|
147
|
+
{
|
|
148
|
+
"field": " -> ".join(str(loc) for loc in error["loc"]),
|
|
149
|
+
"message": error["msg"],
|
|
150
|
+
"type": error["type"],
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
"detail": "Validation failed",
|
|
156
|
+
"type": "validation_error",
|
|
157
|
+
"errors": errors,
|
|
158
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Owner middleware for extracting owner ID from webhook URLs.
|
|
3
|
+
|
|
4
|
+
Simple middleware that extracts the owner_id from webhook URL paths and sets it in context.
|
|
5
|
+
This replaces the over-complicated tenant middleware with a focused single-purpose solution.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from fastapi import HTTPException, Request
|
|
9
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
10
|
+
from starlette.responses import Response
|
|
11
|
+
|
|
12
|
+
from wappa.core.config.settings import settings
|
|
13
|
+
from wappa.core.logging.context import set_request_context
|
|
14
|
+
from wappa.core.logging.logger import get_logger
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class OwnerMiddleware(BaseHTTPMiddleware):
|
|
20
|
+
"""
|
|
21
|
+
Middleware to extract owner_id from webhook URLs and set in context.
|
|
22
|
+
|
|
23
|
+
URL Pattern: /webhook/messenger/{owner_id}/whatsapp
|
|
24
|
+
Purpose: Extract owner_id and set it in the context system.
|
|
25
|
+
|
|
26
|
+
That's it. Nothing more.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
async def dispatch(self, request: Request, call_next) -> Response:
|
|
30
|
+
"""Extract owner_id from URL path and set in context."""
|
|
31
|
+
owner_id = None
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
# ENHANCED DEBUGGING: Log all request details
|
|
35
|
+
logger.debug(f"🔍 OwnerMiddleware processing: {request.method} {request.url.path}")
|
|
36
|
+
|
|
37
|
+
# Extract owner_id from webhook URL pattern: /webhook/messenger/{owner_id}/{platform}
|
|
38
|
+
if request.url.path.startswith("/webhook/"):
|
|
39
|
+
logger.debug(f"🎯 Webhook request detected: {request.url.path}")
|
|
40
|
+
path_parts = request.url.path.strip("/").split("/")
|
|
41
|
+
logger.debug(f"📋 Path parts: {path_parts} (length: {len(path_parts)})")
|
|
42
|
+
|
|
43
|
+
if len(path_parts) >= 4:
|
|
44
|
+
# path_parts = ["webhook", "messenger", "owner_id", "platform"]
|
|
45
|
+
owner_id = path_parts[2]
|
|
46
|
+
logger.debug(f"🔑 Extracted owner_id from URL: '{owner_id}'")
|
|
47
|
+
|
|
48
|
+
# Validate basic format
|
|
49
|
+
if self._is_valid_owner_id(owner_id):
|
|
50
|
+
# Set owner_id context from URL
|
|
51
|
+
set_request_context(owner_id=owner_id)
|
|
52
|
+
logger.debug(f"✅ Owner ID context set successfully: {owner_id}")
|
|
53
|
+
else:
|
|
54
|
+
logger.error(f"❌ Invalid owner ID format: {owner_id}")
|
|
55
|
+
raise HTTPException(
|
|
56
|
+
status_code=400, detail=f"Invalid owner ID: {owner_id}"
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
logger.warning(f"⚠️ Webhook URL does not have enough parts: {path_parts}")
|
|
60
|
+
|
|
61
|
+
# For non-webhook endpoints, use default owner from settings
|
|
62
|
+
elif not self._is_public_endpoint(request.url.path):
|
|
63
|
+
default_owner = settings.owner_id
|
|
64
|
+
set_request_context(owner_id=default_owner)
|
|
65
|
+
logger.debug(f"Using default owner ID: {default_owner}")
|
|
66
|
+
|
|
67
|
+
# Process request
|
|
68
|
+
response = await call_next(request)
|
|
69
|
+
return response
|
|
70
|
+
|
|
71
|
+
except HTTPException:
|
|
72
|
+
raise
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Error in owner middleware: {e}", exc_info=True)
|
|
75
|
+
raise HTTPException(status_code=500, detail="Internal server error") from e
|
|
76
|
+
|
|
77
|
+
def _is_valid_owner_id(self, owner_id: str) -> bool:
|
|
78
|
+
"""Validate owner ID format."""
|
|
79
|
+
if not owner_id or not isinstance(owner_id, str):
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
# Basic format validation - alphanumeric and underscores only
|
|
83
|
+
if not owner_id.replace("_", "").replace("-", "").isalnum():
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
# Length validation
|
|
87
|
+
return not (len(owner_id) < 3 or len(owner_id) > 50)
|
|
88
|
+
|
|
89
|
+
def _is_public_endpoint(self, path: str) -> bool:
|
|
90
|
+
"""Check if endpoint is public and doesn't require owner context."""
|
|
91
|
+
public_paths = [
|
|
92
|
+
"/",
|
|
93
|
+
"/health",
|
|
94
|
+
"/health/detailed",
|
|
95
|
+
"/docs",
|
|
96
|
+
"/redoc",
|
|
97
|
+
"/openapi.json",
|
|
98
|
+
]
|
|
99
|
+
return any(path.startswith(p) for p in public_paths)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Request and response logging middleware with tenant and user context.
|
|
3
|
+
|
|
4
|
+
Provides comprehensive logging for monitoring, debugging, and audit trails for Wappa framework.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from fastapi import Request
|
|
11
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
12
|
+
from starlette.responses import Response
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
|
16
|
+
"""
|
|
17
|
+
Middleware for logging HTTP requests and responses with context.
|
|
18
|
+
|
|
19
|
+
Features:
|
|
20
|
+
- Tenant-aware logging
|
|
21
|
+
- Request/response timing
|
|
22
|
+
- Privacy-conscious logging (excludes sensitive data)
|
|
23
|
+
- Structured log format for monitoring
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, app, log_requests: bool = True, log_responses: bool = True):
|
|
27
|
+
super().__init__(app)
|
|
28
|
+
self.log_requests = log_requests
|
|
29
|
+
self.log_responses = log_responses
|
|
30
|
+
# Sensitive headers to exclude from logs
|
|
31
|
+
self.sensitive_headers = {
|
|
32
|
+
"authorization",
|
|
33
|
+
"x-api-key",
|
|
34
|
+
"cookie",
|
|
35
|
+
"set-cookie",
|
|
36
|
+
"x-access-token",
|
|
37
|
+
"x-auth-token",
|
|
38
|
+
"x-whatsapp-hub-signature",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async def dispatch(self, request: Request, call_next) -> Response:
|
|
42
|
+
"""Process request with comprehensive logging."""
|
|
43
|
+
# Start timing
|
|
44
|
+
start_time = time.time()
|
|
45
|
+
|
|
46
|
+
# Get logger with context (context is set by OwnerMiddleware and processors)
|
|
47
|
+
from wappa.core.logging.logger import get_logger
|
|
48
|
+
|
|
49
|
+
logger = get_logger(__name__)
|
|
50
|
+
|
|
51
|
+
# Log incoming request
|
|
52
|
+
if self.log_requests and not self._should_skip_logging(request.url.path):
|
|
53
|
+
await self._log_request(request, logger)
|
|
54
|
+
|
|
55
|
+
# Process request
|
|
56
|
+
response = await call_next(request)
|
|
57
|
+
|
|
58
|
+
# Calculate processing time
|
|
59
|
+
process_time = time.time() - start_time
|
|
60
|
+
|
|
61
|
+
# Add processing time to response headers (in development)
|
|
62
|
+
from wappa.core.config.settings import settings
|
|
63
|
+
|
|
64
|
+
if settings.is_development:
|
|
65
|
+
response.headers["X-Process-Time"] = str(round(process_time * 1000, 2))
|
|
66
|
+
|
|
67
|
+
# Log response
|
|
68
|
+
if self.log_responses and not self._should_skip_logging(request.url.path):
|
|
69
|
+
await self._log_response(request, response, process_time, logger)
|
|
70
|
+
|
|
71
|
+
return response
|
|
72
|
+
|
|
73
|
+
def _extract_user_id(self, request: Request) -> str:
|
|
74
|
+
"""
|
|
75
|
+
Extract user ID from request context.
|
|
76
|
+
|
|
77
|
+
This will be populated by webhook processing or API authentication.
|
|
78
|
+
For now, returns 'unknown' as we haven't implemented user extraction yet.
|
|
79
|
+
"""
|
|
80
|
+
return getattr(request.state, "user_id", "unknown")
|
|
81
|
+
|
|
82
|
+
def _should_skip_logging(self, path: str) -> bool:
|
|
83
|
+
"""Check if we should skip logging for this path."""
|
|
84
|
+
# Skip logging for health checks and static assets to reduce noise
|
|
85
|
+
skip_paths = ["/health", "/docs", "/redoc", "/openapi.json", "/favicon.ico"]
|
|
86
|
+
return any(path.startswith(skip_path) for skip_path in skip_paths)
|
|
87
|
+
|
|
88
|
+
async def _log_request(self, request: Request, logger) -> None:
|
|
89
|
+
"""Log incoming request with sanitized information."""
|
|
90
|
+
# Safely read body for logging (only for small payloads)
|
|
91
|
+
body_info = await self._get_request_body_info(request)
|
|
92
|
+
|
|
93
|
+
# Sanitized headers (exclude sensitive ones)
|
|
94
|
+
safe_headers = {
|
|
95
|
+
k: v
|
|
96
|
+
for k, v in request.headers.items()
|
|
97
|
+
if k.lower() not in self.sensitive_headers
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
log_data = {
|
|
101
|
+
"method": request.method,
|
|
102
|
+
"url": str(request.url),
|
|
103
|
+
"path": request.url.path,
|
|
104
|
+
"query_params": dict(request.query_params),
|
|
105
|
+
"headers": safe_headers,
|
|
106
|
+
"client_host": request.client.host if request.client else "unknown",
|
|
107
|
+
"user_agent": request.headers.get("user-agent", "unknown"),
|
|
108
|
+
**body_info,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
logger.info(
|
|
112
|
+
f"Incoming {request.method} {request.url.path}", extra={"request": log_data}
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
async def _log_response(
|
|
116
|
+
self, request: Request, response: Response, process_time: float, logger
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Log response with timing and status information."""
|
|
119
|
+
# Determine log level based on status code
|
|
120
|
+
status_code = response.status_code
|
|
121
|
+
|
|
122
|
+
if status_code >= 500:
|
|
123
|
+
log_level = "error"
|
|
124
|
+
elif status_code >= 400:
|
|
125
|
+
log_level = "warning"
|
|
126
|
+
else:
|
|
127
|
+
log_level = "info"
|
|
128
|
+
|
|
129
|
+
log_data = {
|
|
130
|
+
"status_code": status_code,
|
|
131
|
+
"process_time_ms": round(process_time * 1000, 2),
|
|
132
|
+
"content_length": response.headers.get("content-length", "unknown"),
|
|
133
|
+
"content_type": response.headers.get("content-type", "unknown"),
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
message = (
|
|
137
|
+
f"Response {status_code} for {request.method} {request.url.path} "
|
|
138
|
+
f"({log_data['process_time_ms']}ms)"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Log with appropriate level
|
|
142
|
+
getattr(logger, log_level)(message, extra={"response": log_data})
|
|
143
|
+
|
|
144
|
+
async def _get_request_body_info(self, request: Request) -> dict[str, Any]:
|
|
145
|
+
"""
|
|
146
|
+
Get safe information about request body.
|
|
147
|
+
|
|
148
|
+
Returns body size and type information without logging sensitive content.
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
# Only read body for non-streaming requests and if small enough
|
|
152
|
+
content_length = request.headers.get("content-length")
|
|
153
|
+
content_type = request.headers.get("content-type", "unknown")
|
|
154
|
+
|
|
155
|
+
body_info = {"content_type": content_type, "content_length": content_length}
|
|
156
|
+
|
|
157
|
+
# For webhook endpoints, we might want to log structure but not content
|
|
158
|
+
if request.url.path.startswith("/webhook/"):
|
|
159
|
+
body_info["is_webhook"] = True
|
|
160
|
+
# Don't log webhook payload content for privacy/security
|
|
161
|
+
body_info["body_logged"] = False
|
|
162
|
+
else:
|
|
163
|
+
# For API endpoints, we could log small bodies in development
|
|
164
|
+
from wappa.core.config.settings import settings
|
|
165
|
+
|
|
166
|
+
if (
|
|
167
|
+
settings.is_development
|
|
168
|
+
and content_length
|
|
169
|
+
and int(content_length) < 1000
|
|
170
|
+
): # Only log small payloads
|
|
171
|
+
# Read body (this consumes the stream, so we need to be careful)
|
|
172
|
+
# For now, just log that we could log it
|
|
173
|
+
body_info["body_loggable"] = True
|
|
174
|
+
else:
|
|
175
|
+
body_info["body_logged"] = False
|
|
176
|
+
|
|
177
|
+
return body_info
|
|
178
|
+
|
|
179
|
+
except Exception as e:
|
|
180
|
+
# If we can't read body info, just return basic info
|
|
181
|
+
return {
|
|
182
|
+
"content_type": request.headers.get("content-type", "unknown"),
|
|
183
|
+
"body_read_error": str(e),
|
|
184
|
+
}
|