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,189 @@
1
+ """
2
+ Redis Manager for Wappa application lifecycle management.
3
+
4
+ Wraps the Wappa RedisClient with application-specific initialization and cleanup.
5
+ Provides a clean interface for applications to manage Redis connections.
6
+ """
7
+
8
+ import logging
9
+ from typing import Any
10
+
11
+ from ...core.config.settings import settings
12
+ from .redis_client import POOL_DB_MAPPING, PoolAlias, RedisClient
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class RedisManager:
18
+ """
19
+ Application-level wrapper around the Wappa RedisClient.
20
+
21
+ Handles lifecycle management, health monitoring, and provides
22
+ a clean interface for applications to manage Redis connections.
23
+
24
+ This manager follows the same patterns as the reference implementation
25
+ but uses Wappa's own RedisClient instead of external dependencies.
26
+ """
27
+
28
+ _initialized: bool = False
29
+
30
+ @classmethod
31
+ async def initialize(
32
+ cls, redis_url: str | None = None, max_connections: int | None = None
33
+ ) -> None:
34
+ """
35
+ Initialize Redis pools with enhanced logging.
36
+
37
+ Uses the existing multi-pool setup with proper configuration
38
+ from application settings or provided parameters.
39
+
40
+ Args:
41
+ redis_url: Redis connection URL (defaults to settings.redis_url)
42
+ max_connections: Max connections per pool (defaults to settings.redis_max_connections)
43
+ """
44
+ if cls._initialized:
45
+ logger.info("Redis pools already initialized - skipping")
46
+ return
47
+
48
+ try:
49
+ # Enhanced initialization logging
50
+ url = redis_url or settings.redis_url
51
+ connections = max_connections or settings.redis_max_connections
52
+
53
+ logger.info(f"Setting up Redis pools from {url} (max_connections: {connections})")
54
+
55
+ # Use Wappa's RedisClient.setup_single_url
56
+ RedisClient.setup_single_url(
57
+ base_url=url,
58
+ max_connections=connections,
59
+ )
60
+
61
+ # Verify all pools with detailed logging
62
+ logger.info("Verifying Redis pool health...")
63
+ await cls._verify_pools()
64
+
65
+ cls._initialized = True
66
+
67
+ # Success confirmation with pool details
68
+ pool_count = len(POOL_DB_MAPPING)
69
+ pool_details = ", ".join(f"{alias}:db{db}" for alias, db in POOL_DB_MAPPING.items())
70
+ logger.info(f"✅ Redis pools ready: {pool_count} pools ({pool_details})")
71
+
72
+ except Exception as e:
73
+ logger.error(f"❌ Redis pool initialization failed: {e}", exc_info=True)
74
+ raise
75
+
76
+ @classmethod
77
+ async def _verify_pools(cls) -> None:
78
+ """
79
+ Health check all Redis pools with detailed logging.
80
+
81
+ Ensures all expected pools are accessible and responding.
82
+ """
83
+ failed_pools = []
84
+ successful_pools = []
85
+
86
+ for alias in POOL_DB_MAPPING:
87
+ pool_alias: PoolAlias = alias # type: ignore
88
+ try:
89
+ redis = await RedisClient.get(pool_alias)
90
+ await redis.ping()
91
+ successful_pools.append(f"{alias}:db{POOL_DB_MAPPING[alias]}")
92
+ logger.debug(f"✅ Redis pool '{alias}' (db{POOL_DB_MAPPING[alias]}) health check passed")
93
+ except Exception as e:
94
+ failed_pools.append(f"{alias}:db{POOL_DB_MAPPING[alias]}")
95
+ logger.error(f"❌ Redis pool '{alias}' (db{POOL_DB_MAPPING[alias]}) health check failed: {e}")
96
+
97
+ if failed_pools:
98
+ raise ConnectionError(f"Failed Redis pools: {', '.join(failed_pools)}")
99
+
100
+ logger.info(f"✅ All Redis pools healthy: {', '.join(successful_pools)}")
101
+
102
+ @classmethod
103
+ async def get_health_status(cls) -> dict[str, Any]:
104
+ """
105
+ Get health status of all Redis pools.
106
+
107
+ Returns detailed health information for monitoring.
108
+
109
+ Returns:
110
+ Dictionary containing initialization status and per-pool health info
111
+ """
112
+ health_status = {"initialized": cls._initialized, "pools": {}}
113
+
114
+ if not cls._initialized:
115
+ health_status["message"] = "Redis not initialized"
116
+ return health_status
117
+
118
+ for alias in POOL_DB_MAPPING:
119
+ pool_alias: PoolAlias = alias # type: ignore
120
+ try:
121
+ redis = await RedisClient.get(pool_alias)
122
+ await redis.ping()
123
+ health_status["pools"][alias] = {
124
+ "status": "healthy",
125
+ "database": POOL_DB_MAPPING[alias],
126
+ "error": None,
127
+ }
128
+ except Exception as e:
129
+ health_status["pools"][alias] = {
130
+ "status": "unhealthy",
131
+ "database": POOL_DB_MAPPING[alias],
132
+ "error": str(e),
133
+ }
134
+
135
+ return health_status
136
+
137
+ @classmethod
138
+ async def get_client(cls, alias: PoolAlias = "users"):
139
+ """
140
+ Get Redis client for specified pool.
141
+
142
+ Args:
143
+ alias: Pool alias ("users", "state_handler", "table")
144
+
145
+ Returns:
146
+ Redis client instance
147
+
148
+ Raises:
149
+ RuntimeError: If Redis not initialized
150
+ """
151
+ if not cls._initialized:
152
+ raise RuntimeError("RedisManager not initialized. Call initialize() first.")
153
+
154
+ return await RedisClient.get(alias)
155
+
156
+ @classmethod
157
+ async def cleanup(cls) -> None:
158
+ """
159
+ Clean shutdown of all Redis pools.
160
+
161
+ Should be called during application shutdown.
162
+ """
163
+ if not cls._initialized:
164
+ logger.info("Redis pools not initialized, skipping cleanup")
165
+ return
166
+
167
+ try:
168
+ logger.info("Shutting down Redis pools...")
169
+ await RedisClient.close()
170
+ cls._initialized = False
171
+ logger.info("Redis pools shut down successfully")
172
+ except Exception as e:
173
+ logger.error(f"Error during Redis cleanup: {e}", exc_info=True)
174
+ raise
175
+
176
+ @classmethod
177
+ def is_initialized(cls) -> bool:
178
+ """Check if Redis pools are initialized."""
179
+ return cls._initialized
180
+
181
+ @classmethod
182
+ def get_pool_info(cls) -> dict[str, int]:
183
+ """
184
+ Get information about available Redis pools.
185
+
186
+ Returns:
187
+ Dictionary mapping pool aliases to database numbers
188
+ """
189
+ return POOL_DB_MAPPING.copy()
@@ -0,0 +1,6 @@
1
+ """
2
+ Webhook processor infrastructure for the Mimeia AI Agent Platform.
3
+
4
+ This module provides platform-agnostic webhook processing capabilities
5
+ with specialized processors for different messaging platforms.
6
+ """
@@ -0,0 +1,262 @@
1
+ """
2
+ Base processor abstraction for platform-agnostic webhook processing.
3
+
4
+ This module defines the abstract base classes that all platform-specific
5
+ webhook processors must inherit from to ensure consistent interfaces.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from collections.abc import Callable
10
+ from typing import Any
11
+
12
+ from wappa.core.logging.logger import get_logger
13
+ from wappa.schemas.core.base_message import BaseMessage
14
+ from wappa.schemas.core.base_status import BaseMessageStatus
15
+ from wappa.schemas.core.base_webhook import BaseWebhook
16
+ from wappa.schemas.core.types import ErrorCode, MessageType, PlatformType
17
+
18
+ # Legacy ProcessingResult class removed - Universal Webhook Interface is now the ONLY way
19
+ # Use processor.create_universal_webhook() method instead for type-safe webhook handling
20
+
21
+
22
+ class ProcessorCapabilities:
23
+ """
24
+ Represents the capabilities of a platform processor.
25
+
26
+ This allows the system to understand what each processor can handle
27
+ and route webhooks appropriately.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ platform: PlatformType,
33
+ supported_message_types: set[MessageType],
34
+ supports_status_updates: bool = True,
35
+ supports_signature_validation: bool = True,
36
+ supports_error_webhooks: bool = True,
37
+ max_payload_size: int | None = None,
38
+ rate_limit_per_minute: int | None = None,
39
+ ):
40
+ self.platform = platform
41
+ self.supported_message_types = supported_message_types
42
+ self.supports_status_updates = supports_status_updates
43
+ self.supports_signature_validation = supports_signature_validation
44
+ self.supports_error_webhooks = supports_error_webhooks
45
+ self.max_payload_size = max_payload_size
46
+ self.rate_limit_per_minute = rate_limit_per_minute
47
+
48
+ def can_handle_message_type(self, message_type: MessageType) -> bool:
49
+ """Check if processor can handle a specific message type."""
50
+ return message_type in self.supported_message_types
51
+
52
+ def to_dict(self) -> dict[str, Any]:
53
+ """Convert capabilities to dictionary."""
54
+ return {
55
+ "platform": self.platform.value,
56
+ "supported_message_types": [
57
+ mt.value for mt in self.supported_message_types
58
+ ],
59
+ "supports_status_updates": self.supports_status_updates,
60
+ "supports_signature_validation": self.supports_signature_validation,
61
+ "supports_error_webhooks": self.supports_error_webhooks,
62
+ "max_payload_size": self.max_payload_size,
63
+ "rate_limit_per_minute": self.rate_limit_per_minute,
64
+ }
65
+
66
+
67
+ class BaseWebhookProcessor(ABC):
68
+ """
69
+ Platform-agnostic webhook processor base class.
70
+
71
+ All platform-specific processors must inherit from this class
72
+ to provide a consistent interface for webhook processing.
73
+ """
74
+
75
+ def __init__(self):
76
+ self.logger = get_logger(__name__)
77
+
78
+ # Message type handlers - subclasses should populate this
79
+ self._message_type_handlers: dict[str, callable] = {}
80
+
81
+ # Processing statistics
82
+ self._processed_count = 0
83
+ self._error_count = 0
84
+ self._last_processed = None
85
+
86
+ @property
87
+ @abstractmethod
88
+ def platform(self) -> PlatformType:
89
+ """Get the platform this processor handles."""
90
+ pass
91
+
92
+ @property
93
+ @abstractmethod
94
+ def capabilities(self) -> ProcessorCapabilities:
95
+ """Get the capabilities of this processor."""
96
+ pass
97
+
98
+ # Legacy process_webhook abstract method removed - Universal Webhook Interface is the ONLY way
99
+ # All processors must implement create_universal_webhook() method instead
100
+
101
+ @abstractmethod
102
+ def validate_webhook_signature(
103
+ self, payload: bytes, signature: str, **kwargs
104
+ ) -> bool:
105
+ """
106
+ Validate webhook signature for security.
107
+
108
+ Args:
109
+ payload: Raw webhook payload bytes
110
+ signature: Platform-specific signature header
111
+ **kwargs: Additional validation parameters
112
+
113
+ Returns:
114
+ True if signature is valid, False otherwise
115
+ """
116
+ pass
117
+
118
+ @abstractmethod
119
+ def parse_webhook_container(self, payload: dict[str, Any], **kwargs) -> BaseWebhook:
120
+ """
121
+ Parse the top-level webhook structure.
122
+
123
+ Args:
124
+ payload: Raw webhook payload
125
+ **kwargs: Additional parsing parameters
126
+
127
+ Returns:
128
+ Parsed webhook container with universal interface
129
+
130
+ Raises:
131
+ ValidationError: If webhook structure is invalid
132
+ """
133
+ pass
134
+
135
+ @abstractmethod
136
+ def get_supported_message_types(self) -> set[MessageType]:
137
+ """Get the set of message types this processor supports."""
138
+ pass
139
+
140
+ @abstractmethod
141
+ def create_message_from_data(
142
+ self, message_data: dict[str, Any], message_type: MessageType, **kwargs
143
+ ) -> BaseMessage:
144
+ """
145
+ Create a message instance from raw data.
146
+
147
+ Args:
148
+ message_data: Raw message data from webhook
149
+ message_type: The type of message to create
150
+ **kwargs: Additional creation parameters
151
+
152
+ Returns:
153
+ Parsed message with universal interface
154
+
155
+ Raises:
156
+ ValidationError: If message data is invalid
157
+ UnsupportedMessageType: If message type is not supported
158
+ """
159
+ pass
160
+
161
+ @abstractmethod
162
+ def create_status_from_data(
163
+ self, status_data: dict[str, Any], **kwargs
164
+ ) -> BaseMessageStatus:
165
+ """
166
+ Create a status instance from raw data.
167
+
168
+ Args:
169
+ status_data: Raw status data from webhook
170
+ **kwargs: Additional creation parameters
171
+
172
+ Returns:
173
+ Parsed status with universal interface
174
+
175
+ Raises:
176
+ ValidationError: If status data is invalid
177
+ """
178
+ pass
179
+
180
+ def is_supported_platform(self, platform: str) -> bool:
181
+ """Check if the platform string matches this processor."""
182
+ return platform.lower() == self.platform.value.lower()
183
+
184
+ def register_message_handler(self, message_type: str, handler: Callable) -> None:
185
+ """Register a handler for a specific message type."""
186
+ self._message_type_handlers[message_type] = handler
187
+
188
+ def get_message_handler(self, message_type: str) -> Callable | None:
189
+ """Get the handler for a specific message type."""
190
+ return self._message_type_handlers.get(message_type)
191
+
192
+ # Legacy _process_incoming_messages method removed - Universal Webhook Interface handles this via IncomingMessageWebhook
193
+
194
+ # Legacy _process_status_updates method removed - Universal Webhook Interface handles this via StatusWebhook
195
+
196
+ def get_processing_stats(self) -> dict[str, Any]:
197
+ """
198
+ Get processing statistics for monitoring and analysis.
199
+
200
+ Returns:
201
+ Dictionary with processing statistics
202
+ """
203
+ return {
204
+ "platform": self.platform.value,
205
+ "capabilities": self.capabilities.to_dict(),
206
+ "processed_count": self._processed_count,
207
+ "error_count": self._error_count,
208
+ "error_rate": self._error_count / max(self._processed_count, 1),
209
+ "last_processed": self._last_processed.isoformat()
210
+ if self._last_processed
211
+ else None,
212
+ "supported_message_types": [
213
+ mt.value for mt in self.get_supported_message_types()
214
+ ],
215
+ }
216
+
217
+ # Legacy create_error_result method removed - Universal Webhook Interface handles errors via ErrorWebhook
218
+
219
+ def __str__(self) -> str:
220
+ """String representation of the processor."""
221
+ return f"{self.__class__.__name__}(platform={self.platform.value})"
222
+
223
+ def __repr__(self) -> str:
224
+ """Detailed string representation of the processor."""
225
+ return (
226
+ f"{self.__class__.__name__}("
227
+ f"platform={self.platform.value}, "
228
+ f"processed={self._processed_count}, "
229
+ f"errors={self._error_count})"
230
+ )
231
+
232
+
233
+ class ProcessorError(Exception):
234
+ """Base exception for processor-related errors."""
235
+
236
+ def __init__(self, message: str, error_code: ErrorCode, platform: PlatformType):
237
+ self.message = message
238
+ self.error_code = error_code
239
+ self.platform = platform
240
+ super().__init__(message)
241
+
242
+
243
+ class UnsupportedMessageTypeError(ProcessorError):
244
+ """Raised when a processor encounters an unsupported message type."""
245
+
246
+ def __init__(self, message_type: str, platform: PlatformType):
247
+ super().__init__(
248
+ f"Message type '{message_type}' not supported by {platform.value} processor",
249
+ ErrorCode.UNKNOWN_MESSAGE_TYPE,
250
+ platform,
251
+ )
252
+
253
+
254
+ class SignatureValidationError(ProcessorError):
255
+ """Raised when webhook signature validation fails."""
256
+
257
+ def __init__(self, platform: PlatformType):
258
+ super().__init__(
259
+ f"Webhook signature validation failed for {platform.value}",
260
+ ErrorCode.SIGNATURE_VALIDATION_FAILED,
261
+ platform,
262
+ )