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,244 @@
1
+ """
2
+ Messenger factory for creating platform-specific IMessenger implementations.
3
+
4
+ This factory implements the Strategy Pattern to create appropriate IMessenger
5
+ instances based on platform type, handling all dependency injection and
6
+ configuration for each platform-specific messenger.
7
+ """
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ from wappa.core.logging.logger import get_logger
12
+ from wappa.domain.interfaces.messaging_interface import IMessenger
13
+ from wappa.domain.services.tenant_credentials_service import TenantCredentialsService
14
+ from wappa.messaging.whatsapp.client.whatsapp_client import WhatsAppClient
15
+ from wappa.messaging.whatsapp.handlers.whatsapp_interactive_handler import (
16
+ WhatsAppInteractiveHandler,
17
+ )
18
+ from wappa.messaging.whatsapp.handlers.whatsapp_media_handler import (
19
+ WhatsAppMediaHandler,
20
+ )
21
+ from wappa.messaging.whatsapp.handlers.whatsapp_specialized_handler import (
22
+ WhatsAppSpecializedHandler,
23
+ )
24
+ from wappa.messaging.whatsapp.handlers.whatsapp_template_handler import (
25
+ WhatsAppTemplateHandler,
26
+ )
27
+ from wappa.messaging.whatsapp.messenger.whatsapp_messenger import WhatsAppMessenger
28
+ from wappa.schemas.core.types import PlatformType
29
+
30
+ if TYPE_CHECKING:
31
+ import aiohttp
32
+
33
+
34
+ class MessengerFactory:
35
+ """
36
+ Factory for creating platform-specific messenger implementations.
37
+
38
+ Uses Strategy Pattern to provide appropriate IMessenger implementations
39
+ based on platform type. Handles dependency injection and configuration
40
+ for each platform-specific messenger.
41
+
42
+ Currently supported platforms:
43
+ - WhatsApp Business API
44
+
45
+ Future platforms:
46
+ - Telegram Bot API
47
+ - Microsoft Teams
48
+ - Discord
49
+ """
50
+
51
+ def __init__(self, http_session: "aiohttp.ClientSession" = None):
52
+ """
53
+ Initialize the messenger factory.
54
+
55
+ Args:
56
+ http_session: Shared HTTP session for efficient connection pooling
57
+ """
58
+ self._http_session = http_session
59
+ self.logger = get_logger(__name__)
60
+
61
+ # Cache for created messengers to avoid recreating for same tenant/platform
62
+ self._messenger_cache: dict[str, IMessenger] = {}
63
+
64
+ async def create_messenger(
65
+ self, platform: PlatformType, tenant_id: str, force_recreate: bool = False
66
+ ) -> IMessenger:
67
+ """
68
+ Create platform-specific messenger implementation.
69
+
70
+ Args:
71
+ platform: Platform type (WHATSAPP, TELEGRAM, etc.)
72
+ tenant_id: Tenant-specific identifier
73
+ force_recreate: Force creation of new instance even if cached
74
+
75
+ Returns:
76
+ Configured IMessenger implementation for the specified platform
77
+
78
+ Raises:
79
+ ValueError: If platform is not supported
80
+ RuntimeError: If messenger creation fails
81
+ """
82
+ cache_key = f"{platform.value}:{tenant_id}"
83
+
84
+ # Return cached instance if available and not forcing recreation
85
+ if not force_recreate and cache_key in self._messenger_cache:
86
+ self.logger.debug(f"Using cached messenger for {cache_key}")
87
+ return self._messenger_cache[cache_key]
88
+
89
+ self.logger.debug(
90
+ f"Creating new messenger for platform: {platform.value}, tenant: {tenant_id}"
91
+ )
92
+
93
+ try:
94
+ if platform == PlatformType.WHATSAPP:
95
+ messenger = await self._create_whatsapp_messenger(tenant_id)
96
+
97
+ # Future platform implementations:
98
+ # elif platform == PlatformType.TELEGRAM:
99
+ # messenger = await self._create_telegram_messenger(tenant_id)
100
+ # elif platform == PlatformType.TEAMS:
101
+ # messenger = await self._create_teams_messenger(tenant_id)
102
+
103
+ else:
104
+ raise ValueError(f"Unsupported platform: {platform.value}")
105
+
106
+ # Cache the created messenger
107
+ self._messenger_cache[cache_key] = messenger
108
+
109
+ return messenger
110
+
111
+ except Exception as e:
112
+ self.logger.error(
113
+ f"Failed to create messenger for platform {platform.value}: {e}"
114
+ )
115
+ raise RuntimeError(f"Messenger creation failed: {e}") from e
116
+
117
+ async def _create_whatsapp_messenger(self, tenant_id: str) -> WhatsAppMessenger:
118
+ """
119
+ Create configured WhatsApp messenger with all dependencies.
120
+
121
+ Reuses the existing dependency injection chain from whatsapp_dependencies.py
122
+ to maintain consistency and avoid code duplication.
123
+
124
+ Args:
125
+ tenant_id: WhatsApp phone_number_id (tenant identifier)
126
+
127
+ Returns:
128
+ Fully configured WhatsAppMessenger instance
129
+
130
+ Raises:
131
+ ValueError: If tenant credentials are invalid
132
+ """
133
+ self.logger.debug(f"Creating WhatsApp messenger for tenant: {tenant_id}")
134
+
135
+ # Validate tenant credentials
136
+ if not TenantCredentialsService.validate_tenant(tenant_id):
137
+ raise ValueError(f"Invalid or inactive tenant: {tenant_id}")
138
+
139
+ # Get tenant-specific access token
140
+ access_token = TenantCredentialsService.get_whatsapp_access_token(tenant_id)
141
+
142
+ # Create WhatsApp client (core dependency)
143
+ client = WhatsAppClient(
144
+ session=self._http_session,
145
+ access_token=access_token,
146
+ phone_number_id=tenant_id,
147
+ logger=self.logger,
148
+ )
149
+
150
+ # Create all WhatsApp handlers (following dependency injection pattern)
151
+ media_handler = WhatsAppMediaHandler(client=client, tenant_id=tenant_id)
152
+ interactive_handler = WhatsAppInteractiveHandler(
153
+ client=client, tenant_id=tenant_id
154
+ )
155
+ template_handler = WhatsAppTemplateHandler(client=client, tenant_id=tenant_id)
156
+ specialized_handler = WhatsAppSpecializedHandler(
157
+ client=client, tenant_id=tenant_id
158
+ )
159
+
160
+ # Create unified WhatsApp messenger with all handlers
161
+ messenger = WhatsAppMessenger(
162
+ client=client,
163
+ media_handler=media_handler,
164
+ interactive_handler=interactive_handler,
165
+ template_handler=template_handler,
166
+ specialized_handler=specialized_handler,
167
+ tenant_id=tenant_id,
168
+ )
169
+
170
+ self.logger.info(
171
+ f"✅ WhatsApp messenger created successfully for tenant: {tenant_id}"
172
+ )
173
+ return messenger
174
+
175
+ def get_supported_platforms(self) -> list[PlatformType]:
176
+ """
177
+ Get list of currently supported platforms.
178
+
179
+ Returns:
180
+ List of supported platform types
181
+ """
182
+ return [PlatformType.WHATSAPP]
183
+
184
+ def is_platform_supported(self, platform: PlatformType) -> bool:
185
+ """
186
+ Check if platform is supported by this factory.
187
+
188
+ Args:
189
+ platform: Platform type to check
190
+
191
+ Returns:
192
+ True if platform is supported, False otherwise
193
+ """
194
+ return platform in self.get_supported_platforms()
195
+
196
+ def clear_cache(self, platform: PlatformType = None, tenant_id: str = None):
197
+ """
198
+ Clear messenger cache.
199
+
200
+ Args:
201
+ platform: Optional platform to clear (clear all if None)
202
+ tenant_id: Optional tenant to clear (clear all if None)
203
+ """
204
+ if platform and tenant_id:
205
+ # Clear specific platform/tenant combination
206
+ cache_key = f"{platform.value}:{tenant_id}"
207
+ self._messenger_cache.pop(cache_key, None)
208
+ self.logger.debug(f"Cleared messenger cache for {cache_key}")
209
+ elif platform:
210
+ # Clear all messengers for a platform
211
+ to_remove = [
212
+ key
213
+ for key in self._messenger_cache.keys()
214
+ if key.startswith(f"{platform.value}:")
215
+ ]
216
+ for key in to_remove:
217
+ del self._messenger_cache[key]
218
+ self.logger.debug(f"Cleared messenger cache for platform: {platform.value}")
219
+ elif tenant_id:
220
+ # Clear all messengers for a tenant
221
+ to_remove = [
222
+ key
223
+ for key in self._messenger_cache.keys()
224
+ if key.endswith(f":{tenant_id}")
225
+ ]
226
+ for key in to_remove:
227
+ del self._messenger_cache[key]
228
+ self.logger.debug(f"Cleared messenger cache for tenant: {tenant_id}")
229
+ else:
230
+ # Clear entire cache
231
+ self._messenger_cache.clear()
232
+ self.logger.debug("Cleared entire messenger cache")
233
+
234
+ # Future platform implementations:
235
+
236
+ # async def _create_telegram_messenger(self, tenant_id: str) -> IMessenger:
237
+ # """Create configured Telegram messenger (future implementation)."""
238
+ # # TODO: Implement Telegram Bot API messenger
239
+ # raise NotImplementedError("Telegram messenger not yet implemented")
240
+
241
+ # async def _create_teams_messenger(self, tenant_id: str) -> IMessenger:
242
+ # """Create configured Teams messenger (future implementation)."""
243
+ # # TODO: Implement Microsoft Teams messenger
244
+ # raise NotImplementedError("Teams messenger not yet implemented")
@@ -0,0 +1,32 @@
1
+ """
2
+ Domain interfaces.
3
+
4
+ Defines the contracts that infrastructure layer must implement.
5
+ """
6
+
7
+ from .base_repository import IBaseRepository
8
+ from .cache_factory import ICacheFactory
9
+ from .cache_interface import ICache
10
+ from .expiry_repository import IExpiryRepository
11
+ from .media_interface import IMediaHandler
12
+ from .messaging_interface import IMessenger
13
+ from .pubsub_repository import IPubSubRepository
14
+ from .repository_factory import IRepositoryFactory
15
+ from .shared_state_repository import ISharedStateRepository
16
+ from .state_repository import IStateRepository
17
+ from .user_repository import IUserRepository
18
+
19
+ __all__ = [
20
+ "IBaseRepository",
21
+ "IUserRepository",
22
+ "IStateRepository",
23
+ "ISharedStateRepository",
24
+ "IExpiryRepository",
25
+ "IPubSubRepository",
26
+ "IRepositoryFactory",
27
+ "IMessenger",
28
+ "IMediaHandler",
29
+ # Cache interfaces
30
+ "ICache",
31
+ "ICacheFactory",
32
+ ]
@@ -0,0 +1,94 @@
1
+ """
2
+ Base repository interface.
3
+
4
+ Defines common contract for all Redis-based repositories.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from datetime import timedelta
9
+ from typing import Any
10
+
11
+ from pydantic import BaseModel
12
+
13
+
14
+ class IBaseRepository(ABC):
15
+ """
16
+ Base interface for all Redis repositories.
17
+
18
+ Provides common contract for key-value operations, TTL management,
19
+ and context-aware operations.
20
+ """
21
+
22
+ @abstractmethod
23
+ async def get(self, key: str) -> Any | None:
24
+ """Get value by key."""
25
+ pass
26
+
27
+ @abstractmethod
28
+ async def set(self, key: str, value: Any, ttl: timedelta | None = None) -> bool:
29
+ """Set value with optional TTL."""
30
+ pass
31
+
32
+ @abstractmethod
33
+ async def delete(self, key: str) -> bool:
34
+ """Delete key."""
35
+ pass
36
+
37
+ @abstractmethod
38
+ async def exists(self, key: str) -> bool:
39
+ """Check if key exists."""
40
+ pass
41
+
42
+ @abstractmethod
43
+ async def get_ttl(self, key: str) -> int | None:
44
+ """Get TTL for key in seconds."""
45
+ pass
46
+
47
+ @abstractmethod
48
+ async def set_ttl(self, key: str, ttl: timedelta) -> bool:
49
+ """Set TTL for existing key."""
50
+ pass
51
+
52
+ @abstractmethod
53
+ async def get_hash(
54
+ self, key: str, models: type[BaseModel] | None = None
55
+ ) -> dict[str, Any] | None:
56
+ """
57
+ Get hash value with optional BaseModel deserialization.
58
+
59
+ Args:
60
+ key: Redis key
61
+ models: Optional BaseModel class for full object reconstruction
62
+ e.g., User (will automatically handle nested UserContact, UserLocation)
63
+
64
+ Returns:
65
+ Hash data dictionary or None if not found
66
+ """
67
+ pass
68
+
69
+ @abstractmethod
70
+ async def set_hash(
71
+ self, key: str, data: dict[str, Any], ttl: timedelta | None = None
72
+ ) -> bool:
73
+ """Set hash value with optional TTL."""
74
+ pass
75
+
76
+ @abstractmethod
77
+ async def get_hash_field(self, key: str, field: str) -> Any | None:
78
+ """Get single field from hash."""
79
+ pass
80
+
81
+ @abstractmethod
82
+ async def set_hash_field(self, key: str, field: str, value: Any) -> bool:
83
+ """Set single field in hash."""
84
+ pass
85
+
86
+ @abstractmethod
87
+ async def delete_hash_field(self, key: str, field: str) -> bool:
88
+ """Delete field from hash."""
89
+ pass
90
+
91
+ @abstractmethod
92
+ async def get_keys_pattern(self, pattern: str) -> list[str]:
93
+ """Get keys matching pattern."""
94
+ pass
@@ -0,0 +1,85 @@
1
+ """
2
+ Cache factory interface definition for Wappa framework.
3
+
4
+ Defines the contract for creating context-aware cache instances.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+
9
+ from .cache_interface import ICache
10
+
11
+
12
+ class ICacheFactory(ABC):
13
+ """
14
+ Interface for creating context-aware cache instances.
15
+
16
+ Cache factories create cache instances bound to specific tenants and users,
17
+ ensuring proper data isolation and context management.
18
+
19
+ Context (tenant_id, user_id) is injected at construction time, eliminating
20
+ the need for manual parameter passing in cache creation methods.
21
+ """
22
+
23
+ def __init__(self, tenant_id: str, user_id: str):
24
+ """
25
+ Initialize cache factory with request context.
26
+
27
+ This eliminates the need for manual parameter passing in cache methods.
28
+ Context is injected once at construction and used throughout the factory lifetime.
29
+
30
+ Args:
31
+ tenant_id: Tenant identifier for namespace isolation
32
+ user_id: User identifier for user-specific caches
33
+
34
+ Raises:
35
+ ValueError: If tenant_id or user_id is None or empty
36
+ """
37
+ self.tenant_id = tenant_id
38
+ self.user_id = user_id
39
+ self._validate_context()
40
+
41
+ def _validate_context(self):
42
+ """Validate that required context is available."""
43
+ if not self.tenant_id or not self.user_id:
44
+ raise ValueError(
45
+ f"Missing required context: tenant_id={self.tenant_id}, user_id={self.user_id}"
46
+ )
47
+
48
+ @abstractmethod
49
+ def create_state_cache(self) -> ICache:
50
+ """
51
+ Create state cache instance with context binding.
52
+
53
+ Used for handler state management and conversation state tracking.
54
+ Context (tenant_id, user_id) is automatically injected from constructor.
55
+
56
+ Returns:
57
+ Context-bound state cache instance
58
+ """
59
+ pass
60
+
61
+ @abstractmethod
62
+ def create_user_cache(self) -> ICache:
63
+ """
64
+ Create user cache instance with context binding.
65
+
66
+ Used for user profile data, preferences, and user-specific information.
67
+ Context (tenant_id, user_id) is automatically injected from constructor.
68
+
69
+ Returns:
70
+ Context-bound user cache instance
71
+ """
72
+ pass
73
+
74
+ @abstractmethod
75
+ def create_table_cache(self) -> ICache:
76
+ """
77
+ Create table cache instance with context binding.
78
+
79
+ Used for shared data, lookup tables, and tenant-wide information.
80
+ Context (tenant_id) is automatically injected from constructor.
81
+
82
+ Returns:
83
+ Context-bound table cache instance
84
+ """
85
+ pass
@@ -0,0 +1,199 @@
1
+ """
2
+ Cache interface definition for Wappa framework.
3
+
4
+ Defines the contract for cache implementations (Redis, Memory, JSON).
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any
9
+
10
+ from pydantic import BaseModel
11
+
12
+
13
+ class ICache(ABC):
14
+ """
15
+ Interface for cache implementations in the Wappa framework.
16
+
17
+ Provides basic cache operations with context awareness for tenant and user isolation.
18
+ All cache implementations must support these core operations.
19
+ """
20
+
21
+ @abstractmethod
22
+ async def get(
23
+ self, key: str, models: type[BaseModel] | None = None
24
+ ) -> dict[str, Any] | None:
25
+ """
26
+ Get cached data by key.
27
+
28
+ Args:
29
+ key: Cache key
30
+ models: Optional BaseModel class for deserialization
31
+
32
+ Returns:
33
+ Cached data or None if not found
34
+ """
35
+ pass
36
+
37
+ @abstractmethod
38
+ async def set(self, key: str, data: dict[str, Any], ttl: int | None = None) -> bool:
39
+ """
40
+ Set cached data with optional TTL.
41
+
42
+ Args:
43
+ key: Cache key
44
+ data: Data to cache
45
+ ttl: Time to live in seconds
46
+
47
+ Returns:
48
+ True if successful, False otherwise
49
+ """
50
+ pass
51
+
52
+ @abstractmethod
53
+ async def delete(self, key: str) -> bool:
54
+ """
55
+ Delete cached data by key.
56
+
57
+ Args:
58
+ key: Cache key to delete
59
+
60
+ Returns:
61
+ True if deleted, False if not found
62
+ """
63
+ pass
64
+
65
+ @abstractmethod
66
+ async def exists(self, key: str) -> bool:
67
+ """
68
+ Check if key exists in cache.
69
+
70
+ Args:
71
+ key: Cache key to check
72
+
73
+ Returns:
74
+ True if exists, False otherwise
75
+ """
76
+ pass
77
+
78
+ @abstractmethod
79
+ async def get_field(self, key: str, field: str) -> Any | None:
80
+ """
81
+ Get a specific field from cached hash data.
82
+
83
+ Args:
84
+ key: Cache key
85
+ field: Field name
86
+
87
+ Returns:
88
+ Field value or None if not found
89
+ """
90
+ pass
91
+
92
+ @abstractmethod
93
+ async def set_field(
94
+ self, key: str, field: str, value: Any, ttl: int | None = None
95
+ ) -> bool:
96
+ """
97
+ Set a specific field in cached hash data.
98
+
99
+ Args:
100
+ key: Cache key
101
+ field: Field name
102
+ value: Field value
103
+ ttl: Time to live in seconds
104
+
105
+ Returns:
106
+ True if successful, False otherwise
107
+ """
108
+ pass
109
+
110
+ @abstractmethod
111
+ async def increment_field(
112
+ self, key: str, field: str, increment: int = 1, ttl: int | None = None
113
+ ) -> int | None:
114
+ """
115
+ Atomically increment an integer field.
116
+
117
+ Args:
118
+ key: Cache key
119
+ field: Field name
120
+ increment: Amount to increment by
121
+ ttl: Time to live in seconds
122
+
123
+ Returns:
124
+ New value after increment or None on error
125
+ """
126
+ pass
127
+
128
+ @abstractmethod
129
+ async def append_to_list(
130
+ self, key: str, field: str, value: Any, ttl: int | None = None
131
+ ) -> bool:
132
+ """
133
+ Append value to a list field.
134
+
135
+ Args:
136
+ key: Cache key
137
+ field: Field name containing list
138
+ value: Value to append
139
+ ttl: Time to live in seconds
140
+
141
+ Returns:
142
+ True if successful, False otherwise
143
+ """
144
+ pass
145
+
146
+ @abstractmethod
147
+ async def get_ttl(self, key: str) -> int:
148
+ """
149
+ Get remaining time to live for a key.
150
+
151
+ Args:
152
+ key: Cache key
153
+
154
+ Returns:
155
+ Remaining TTL in seconds, -1 if no expiry, -2 if key doesn't exist
156
+ """
157
+ pass
158
+
159
+ @abstractmethod
160
+ async def set_ttl(self, key: str, ttl: int) -> bool:
161
+ """
162
+ Set time to live for a key.
163
+
164
+ Args:
165
+ key: Cache key
166
+ ttl: Time to live in seconds
167
+
168
+ Returns:
169
+ True if successful, False otherwise
170
+ """
171
+ pass
172
+
173
+ @staticmethod
174
+ def create_table_key(table_name: str, pkid: str) -> str:
175
+ """
176
+ Create a properly formatted table cache key.
177
+
178
+ This is a static utility method available on all cache implementations
179
+ to ensure consistent key formatting across cache backends.
180
+
181
+ Args:
182
+ table_name: Name of the table (e.g., "user_profiles", "message_logs")
183
+ pkid: Primary key ID (e.g., user_id, message_id)
184
+
185
+ Returns:
186
+ Formatted key string for use with cache methods
187
+
188
+ Example:
189
+ key = ICache.create_table_key("user_profiles", "12345")
190
+ # Returns: "user_profiles:12345"
191
+ """
192
+ if not table_name or not pkid:
193
+ raise ValueError("Both table_name and pkid must be provided and non-empty")
194
+
195
+ # Sanitize inputs to avoid conflicts
196
+ safe_table_name = str(table_name).replace(":", "_")
197
+ safe_pkid = str(pkid).replace(":", "_")
198
+
199
+ return f"{safe_table_name}:{safe_pkid}"