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,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base status abstractions for platform-agnostic status handling.
|
|
3
|
+
|
|
4
|
+
This module defines the abstract base classes for message status updates
|
|
5
|
+
that provide consistent interfaces regardless of the messaging platform.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
13
|
+
|
|
14
|
+
from .types import MessageStatus, PlatformType, UniversalMessageData
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseMessageStatus(BaseModel, ABC):
|
|
18
|
+
"""
|
|
19
|
+
Platform-agnostic message status base class.
|
|
20
|
+
|
|
21
|
+
Represents delivery status updates for sent messages across all platforms.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
model_config = ConfigDict(
|
|
25
|
+
extra="forbid", str_strip_whitespace=True, validate_assignment=True
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Universal fields
|
|
29
|
+
processed_at: datetime = Field(
|
|
30
|
+
default_factory=datetime.utcnow,
|
|
31
|
+
description="When the status update was processed by our system",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def platform(self) -> PlatformType:
|
|
37
|
+
"""Get the platform this status update came from."""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def message_id(self) -> str:
|
|
43
|
+
"""Get the ID of the message this status refers to."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def status(self) -> MessageStatus:
|
|
49
|
+
"""Get the universal message status."""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def recipient_id(self) -> str:
|
|
55
|
+
"""Get the recipient's universal identifier."""
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def timestamp(self) -> int:
|
|
61
|
+
"""Get the status timestamp as Unix timestamp."""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def conversation_id(self) -> str:
|
|
67
|
+
"""Get the conversation/chat identifier."""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def is_delivered(self) -> bool:
|
|
72
|
+
"""Check if the message was delivered."""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def is_read(self) -> bool:
|
|
77
|
+
"""Check if the message was read."""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def is_failed(self) -> bool:
|
|
82
|
+
"""Check if the message delivery failed."""
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
@abstractmethod
|
|
86
|
+
def get_error_info(self) -> dict[str, Any] | None:
|
|
87
|
+
"""
|
|
88
|
+
Get error information if the message failed.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Dictionary with error details, or None if no error.
|
|
92
|
+
"""
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
@abstractmethod
|
|
96
|
+
def get_delivery_info(self) -> dict[str, Any]:
|
|
97
|
+
"""
|
|
98
|
+
Get detailed delivery information.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Dictionary with platform-specific delivery details.
|
|
102
|
+
"""
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def to_universal_dict(self) -> UniversalMessageData:
|
|
107
|
+
"""
|
|
108
|
+
Convert to platform-agnostic dictionary representation.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dictionary with standardized status data structure.
|
|
112
|
+
"""
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
@abstractmethod
|
|
116
|
+
def get_platform_data(self) -> dict[str, Any]:
|
|
117
|
+
"""
|
|
118
|
+
Get platform-specific data for advanced processing.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Dictionary with platform-specific status fields.
|
|
122
|
+
"""
|
|
123
|
+
pass
|
|
124
|
+
|
|
125
|
+
def get_status_summary(self) -> dict[str, Any]:
|
|
126
|
+
"""
|
|
127
|
+
Get a summary of the status update for logging and analytics.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Dictionary with key status information.
|
|
131
|
+
"""
|
|
132
|
+
return {
|
|
133
|
+
"message_id": self.message_id,
|
|
134
|
+
"platform": self.platform.value,
|
|
135
|
+
"status": self.status.value,
|
|
136
|
+
"recipient_id": self.recipient_id,
|
|
137
|
+
"conversation_id": self.conversation_id,
|
|
138
|
+
"timestamp": self.timestamp,
|
|
139
|
+
"processed_at": self.processed_at.isoformat(),
|
|
140
|
+
"is_delivered": self.is_delivered(),
|
|
141
|
+
"is_read": self.is_read(),
|
|
142
|
+
"is_failed": self.is_failed(),
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
@abstractmethod
|
|
147
|
+
def from_platform_data(cls, data: dict[str, Any], **kwargs) -> "BaseMessageStatus":
|
|
148
|
+
"""
|
|
149
|
+
Create status instance from platform-specific data.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
data: Raw status data from platform webhook
|
|
153
|
+
**kwargs: Additional platform-specific parameters
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Validated status instance
|
|
157
|
+
"""
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class BaseConversationInfo(BaseModel, ABC):
|
|
162
|
+
"""
|
|
163
|
+
Platform-agnostic conversation information base class.
|
|
164
|
+
|
|
165
|
+
Contains metadata about the conversation where the message was sent.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
@abstractmethod
|
|
172
|
+
def conversation_id(self) -> str:
|
|
173
|
+
"""Get the conversation identifier."""
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
@abstractmethod
|
|
178
|
+
def conversation_type(self) -> str:
|
|
179
|
+
"""Get the conversation type (business, personal, etc.)."""
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
@abstractmethod
|
|
184
|
+
def platform(self) -> PlatformType:
|
|
185
|
+
"""Get the platform this conversation belongs to."""
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
@abstractmethod
|
|
189
|
+
def to_universal_dict(self) -> dict[str, Any]:
|
|
190
|
+
"""Convert to platform-agnostic dictionary representation."""
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class BasePricingInfo(BaseModel, ABC):
|
|
195
|
+
"""
|
|
196
|
+
Platform-agnostic pricing information base class.
|
|
197
|
+
|
|
198
|
+
Contains cost information for sent messages where applicable.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
@abstractmethod
|
|
205
|
+
def billable(self) -> bool:
|
|
206
|
+
"""Check if this message was billable."""
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
@abstractmethod
|
|
211
|
+
def pricing_model(self) -> str:
|
|
212
|
+
"""Get the pricing model used."""
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def platform(self) -> PlatformType:
|
|
218
|
+
"""Get the platform this pricing belongs to."""
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
@abstractmethod
|
|
222
|
+
def get_cost_info(self) -> dict[str, Any]:
|
|
223
|
+
"""
|
|
224
|
+
Get detailed cost information.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Dictionary with cost details (amount, currency, etc.).
|
|
228
|
+
"""
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
@abstractmethod
|
|
232
|
+
def to_universal_dict(self) -> dict[str, Any]:
|
|
233
|
+
"""Convert to platform-agnostic dictionary representation."""
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class BaseStatusWebhook(BaseModel, ABC):
|
|
238
|
+
"""
|
|
239
|
+
Platform-agnostic status webhook base class.
|
|
240
|
+
|
|
241
|
+
Represents a webhook specifically containing status updates.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
model_config = ConfigDict(
|
|
245
|
+
extra="forbid", str_strip_whitespace=True, validate_assignment=True
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Universal fields
|
|
249
|
+
received_at: datetime = Field(
|
|
250
|
+
default_factory=datetime.utcnow,
|
|
251
|
+
description="When the status webhook was received",
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
@property
|
|
255
|
+
@abstractmethod
|
|
256
|
+
def platform(self) -> PlatformType:
|
|
257
|
+
"""Get the platform this webhook came from."""
|
|
258
|
+
pass
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
@abstractmethod
|
|
262
|
+
def business_id(self) -> str:
|
|
263
|
+
"""Get the business/account identifier."""
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
@abstractmethod
|
|
268
|
+
def source_id(self) -> str:
|
|
269
|
+
"""Get the webhook source identifier."""
|
|
270
|
+
pass
|
|
271
|
+
|
|
272
|
+
@abstractmethod
|
|
273
|
+
def get_raw_statuses(self) -> list[dict[str, Any]]:
|
|
274
|
+
"""
|
|
275
|
+
Get raw status data for parsing.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
List of raw status dictionaries.
|
|
279
|
+
"""
|
|
280
|
+
pass
|
|
281
|
+
|
|
282
|
+
@abstractmethod
|
|
283
|
+
def get_status_count(self) -> int:
|
|
284
|
+
"""Get the number of status updates in this webhook."""
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
@abstractmethod
|
|
288
|
+
def to_universal_dict(self) -> dict[str, Any]:
|
|
289
|
+
"""Convert to platform-agnostic dictionary representation."""
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
def get_webhook_summary(self) -> dict[str, Any]:
|
|
293
|
+
"""
|
|
294
|
+
Get a summary of the status webhook for logging.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Dictionary with key webhook information.
|
|
298
|
+
"""
|
|
299
|
+
return {
|
|
300
|
+
"platform": self.platform.value,
|
|
301
|
+
"business_id": self.business_id,
|
|
302
|
+
"source_id": self.source_id,
|
|
303
|
+
"received_at": self.received_at.isoformat(),
|
|
304
|
+
"status_count": self.get_status_count(),
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
@classmethod
|
|
308
|
+
@abstractmethod
|
|
309
|
+
def from_platform_payload(
|
|
310
|
+
cls, payload: dict[str, Any], **kwargs
|
|
311
|
+
) -> "BaseStatusWebhook":
|
|
312
|
+
"""
|
|
313
|
+
Create status webhook instance from platform-specific payload.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
payload: Raw webhook payload from the platform
|
|
317
|
+
**kwargs: Additional platform-specific parameters
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
Validated status webhook instance
|
|
321
|
+
"""
|
|
322
|
+
pass
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base webhook abstraction for platform-agnostic webhook handling.
|
|
3
|
+
|
|
4
|
+
This module defines the abstract base classes that all platform-specific
|
|
5
|
+
webhook implementations must inherit from to ensure consistent interfaces.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from abc import ABC, abstractmethod
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
13
|
+
|
|
14
|
+
from .types import PlatformType, WebhookType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseContact(BaseModel, ABC):
|
|
18
|
+
"""
|
|
19
|
+
Platform-agnostic contact information base class.
|
|
20
|
+
|
|
21
|
+
All platform-specific contact models must inherit from this class
|
|
22
|
+
to provide a consistent interface for contact data.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(
|
|
26
|
+
extra="forbid", str_strip_whitespace=True, validate_assignment=True
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def user_id(self) -> str:
|
|
32
|
+
"""Get the universal user identifier."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def display_name(self) -> str | None:
|
|
38
|
+
"""Get the user's display name if available."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def platform(self) -> PlatformType:
|
|
44
|
+
"""Get the platform this contact belongs to."""
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def to_universal_dict(self) -> dict[str, Any]:
|
|
49
|
+
"""Convert to platform-agnostic dictionary representation."""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BaseWebhookMetadata(BaseModel, ABC):
|
|
54
|
+
"""
|
|
55
|
+
Platform-agnostic webhook metadata base class.
|
|
56
|
+
|
|
57
|
+
Contains platform-specific metadata about the webhook source.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
model_config = ConfigDict(
|
|
61
|
+
extra="forbid", str_strip_whitespace=True, validate_assignment=True
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def business_id(self) -> str:
|
|
67
|
+
"""Get the business/bot identifier."""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
@abstractmethod
|
|
72
|
+
def webhook_source_id(self) -> str:
|
|
73
|
+
"""Get the webhook source identifier (phone number, bot token, etc.)."""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def platform(self) -> PlatformType:
|
|
79
|
+
"""Get the platform type."""
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
def to_universal_dict(self) -> dict[str, Any]:
|
|
84
|
+
"""Convert to platform-agnostic dictionary representation."""
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class BaseWebhook(BaseModel, ABC):
|
|
89
|
+
"""
|
|
90
|
+
Platform-agnostic webhook base class.
|
|
91
|
+
|
|
92
|
+
All platform-specific webhook models must inherit from this class
|
|
93
|
+
to provide a consistent interface for webhook processing.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
model_config = ConfigDict(
|
|
97
|
+
extra="forbid", str_strip_whitespace=True, validate_assignment=True
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Universal fields that all webhooks should have
|
|
101
|
+
received_at: datetime = Field(
|
|
102
|
+
default_factory=datetime.utcnow,
|
|
103
|
+
description="When the webhook was received by our system",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def platform(self) -> PlatformType:
|
|
109
|
+
"""Get the platform type this webhook came from."""
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
@abstractmethod
|
|
114
|
+
def webhook_type(self) -> WebhookType:
|
|
115
|
+
"""Get the type of webhook (messages, status updates, errors, etc.)."""
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
@abstractmethod
|
|
120
|
+
def business_id(self) -> str:
|
|
121
|
+
"""Get the business/account identifier."""
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
@abstractmethod
|
|
126
|
+
def source_id(self) -> str:
|
|
127
|
+
"""Get the webhook source identifier (phone number ID, bot token, etc.)."""
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
@abstractmethod
|
|
131
|
+
def is_incoming_message(self) -> bool:
|
|
132
|
+
"""Check if this webhook contains incoming messages."""
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
@abstractmethod
|
|
136
|
+
def is_status_update(self) -> bool:
|
|
137
|
+
"""Check if this webhook contains message status updates."""
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
@abstractmethod
|
|
141
|
+
def has_errors(self) -> bool:
|
|
142
|
+
"""Check if this webhook contains error information."""
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
@abstractmethod
|
|
146
|
+
def get_raw_messages(self) -> list[dict[str, Any]]:
|
|
147
|
+
"""
|
|
148
|
+
Get raw message data for parsing with platform-specific schemas.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of raw message dictionaries ready for platform-specific parsing.
|
|
152
|
+
"""
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
@abstractmethod
|
|
156
|
+
def get_raw_statuses(self) -> list[dict[str, Any]]:
|
|
157
|
+
"""
|
|
158
|
+
Get raw status data for parsing with platform-specific schemas.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
List of raw status dictionaries ready for platform-specific parsing.
|
|
162
|
+
"""
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
@abstractmethod
|
|
166
|
+
def get_contacts(self) -> list[BaseContact]:
|
|
167
|
+
"""
|
|
168
|
+
Get contact information from the webhook.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
List of contact objects with universal interface.
|
|
172
|
+
"""
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
@abstractmethod
|
|
176
|
+
def get_metadata(self) -> BaseWebhookMetadata:
|
|
177
|
+
"""
|
|
178
|
+
Get webhook metadata with universal interface.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Metadata object with platform-agnostic interface.
|
|
182
|
+
"""
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
@abstractmethod
|
|
186
|
+
def to_universal_dict(self) -> dict[str, Any]:
|
|
187
|
+
"""
|
|
188
|
+
Convert webhook to platform-agnostic dictionary representation.
|
|
189
|
+
|
|
190
|
+
This method should return a dictionary with standardized keys
|
|
191
|
+
that can be used across all platforms for logging, analytics, etc.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Dictionary with universal webhook data structure.
|
|
195
|
+
"""
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
@abstractmethod
|
|
199
|
+
def get_processing_context(self) -> dict[str, Any]:
|
|
200
|
+
"""
|
|
201
|
+
Get context information needed for message processing.
|
|
202
|
+
|
|
203
|
+
This includes tenant information, routing data, and other
|
|
204
|
+
metadata required for Symphony AI integration.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Dictionary with processing context data.
|
|
208
|
+
"""
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
def get_webhook_id(self) -> str:
|
|
212
|
+
"""
|
|
213
|
+
Generate a unique identifier for this webhook.
|
|
214
|
+
|
|
215
|
+
This can be used for deduplication, logging, and tracking.
|
|
216
|
+
"""
|
|
217
|
+
import hashlib
|
|
218
|
+
import json
|
|
219
|
+
|
|
220
|
+
# Create deterministic ID based on platform, business_id, and timestamp
|
|
221
|
+
data = {
|
|
222
|
+
"platform": self.platform.value,
|
|
223
|
+
"business_id": self.business_id,
|
|
224
|
+
"source_id": self.source_id,
|
|
225
|
+
"received_at": self.received_at.isoformat(),
|
|
226
|
+
"webhook_type": self.webhook_type.value,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
content = json.dumps(data, sort_keys=True)
|
|
230
|
+
return hashlib.sha256(content.encode()).hexdigest()[:16]
|
|
231
|
+
|
|
232
|
+
def get_summary(self) -> dict[str, Any]:
|
|
233
|
+
"""
|
|
234
|
+
Get a summary of the webhook for logging and monitoring.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Dictionary with key webhook information for structured logging.
|
|
238
|
+
"""
|
|
239
|
+
return {
|
|
240
|
+
"webhook_id": self.get_webhook_id(),
|
|
241
|
+
"platform": self.platform.value,
|
|
242
|
+
"webhook_type": self.webhook_type.value,
|
|
243
|
+
"business_id": self.business_id,
|
|
244
|
+
"source_id": self.source_id,
|
|
245
|
+
"received_at": self.received_at.isoformat(),
|
|
246
|
+
"has_messages": self.is_incoming_message(),
|
|
247
|
+
"has_statuses": self.is_status_update(),
|
|
248
|
+
"has_errors": self.has_errors(),
|
|
249
|
+
"message_count": len(self.get_raw_messages()),
|
|
250
|
+
"status_count": len(self.get_raw_statuses()),
|
|
251
|
+
"contact_count": len(self.get_contacts()),
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
@abstractmethod
|
|
256
|
+
def from_platform_payload(cls, payload: dict[str, Any], **kwargs) -> "BaseWebhook":
|
|
257
|
+
"""
|
|
258
|
+
Create webhook instance from platform-specific payload.
|
|
259
|
+
|
|
260
|
+
This factory method should handle platform-specific validation
|
|
261
|
+
and transformation of the raw webhook payload.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
payload: Raw webhook payload from the platform
|
|
265
|
+
**kwargs: Additional platform-specific parameters
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Validated webhook instance
|
|
269
|
+
|
|
270
|
+
Raises:
|
|
271
|
+
ValidationError: If payload is invalid
|
|
272
|
+
PlatformError: If platform-specific validation fails
|
|
273
|
+
"""
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class BaseWebhookError(BaseModel, ABC):
|
|
278
|
+
"""
|
|
279
|
+
Platform-agnostic webhook error base class.
|
|
280
|
+
|
|
281
|
+
Represents errors that occur during webhook processing.
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
@abstractmethod
|
|
288
|
+
def error_code(self) -> str:
|
|
289
|
+
"""Get the platform-specific error code."""
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
@abstractmethod
|
|
294
|
+
def error_message(self) -> str:
|
|
295
|
+
"""Get the human-readable error message."""
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
@abstractmethod
|
|
300
|
+
def platform(self) -> PlatformType:
|
|
301
|
+
"""Get the platform this error originated from."""
|
|
302
|
+
pass
|
|
303
|
+
|
|
304
|
+
@abstractmethod
|
|
305
|
+
def is_retryable(self) -> bool:
|
|
306
|
+
"""Check if this error condition is retryable."""
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
@abstractmethod
|
|
310
|
+
def to_universal_dict(self) -> dict[str, Any]:
|
|
311
|
+
"""Convert to platform-agnostic dictionary representation."""
|
|
312
|
+
pass
|