wappa 0.1.8__py3-none-any.whl → 0.1.10__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 (147) hide show
  1. wappa/__init__.py +4 -5
  2. wappa/api/controllers/webhook_controller.py +5 -2
  3. wappa/api/dependencies/__init__.py +0 -5
  4. wappa/api/middleware/error_handler.py +4 -4
  5. wappa/api/middleware/owner.py +11 -5
  6. wappa/api/routes/webhooks.py +2 -2
  7. wappa/cli/__init__.py +1 -1
  8. wappa/cli/examples/init/.env.example +33 -0
  9. wappa/cli/examples/init/app/__init__.py +0 -0
  10. wappa/cli/examples/init/app/main.py +9 -0
  11. wappa/cli/examples/init/app/master_event.py +10 -0
  12. wappa/cli/examples/json_cache_example/.env.example +33 -0
  13. wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
  14. wappa/cli/examples/json_cache_example/app/main.py +247 -0
  15. wappa/cli/examples/json_cache_example/app/master_event.py +455 -0
  16. wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
  17. wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +256 -0
  18. wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
  19. wappa/cli/examples/json_cache_example/app/scores/score_base.py +192 -0
  20. wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +256 -0
  21. wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +187 -0
  22. wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +272 -0
  23. wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +239 -0
  24. wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
  25. wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +174 -0
  26. wappa/cli/examples/json_cache_example/app/utils/message_utils.py +251 -0
  27. wappa/cli/examples/openai_transcript/.gitignore +63 -4
  28. wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
  29. wappa/cli/examples/openai_transcript/app/main.py +9 -0
  30. wappa/cli/examples/openai_transcript/app/master_event.py +62 -0
  31. wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
  32. wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +89 -0
  33. wappa/cli/examples/redis_cache_example/.env.example +33 -0
  34. wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
  35. wappa/cli/examples/redis_cache_example/app/main.py +246 -0
  36. wappa/cli/examples/redis_cache_example/app/master_event.py +455 -0
  37. wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +256 -0
  38. wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
  39. wappa/cli/examples/redis_cache_example/app/scores/score_base.py +192 -0
  40. wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +256 -0
  41. wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +187 -0
  42. wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +272 -0
  43. wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +239 -0
  44. wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
  45. wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +174 -0
  46. wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +251 -0
  47. wappa/cli/examples/simple_echo_example/.env.example +33 -0
  48. wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
  49. wappa/cli/examples/simple_echo_example/app/main.py +191 -0
  50. wappa/cli/examples/simple_echo_example/app/master_event.py +230 -0
  51. wappa/cli/examples/wappa_full_example/.env.example +33 -0
  52. wappa/cli/examples/wappa_full_example/.gitignore +63 -4
  53. wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
  54. wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
  55. wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +492 -0
  56. wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +559 -0
  57. wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +514 -0
  58. wappa/cli/examples/wappa_full_example/app/main.py +269 -0
  59. wappa/cli/examples/wappa_full_example/app/master_event.py +504 -0
  60. wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
  61. wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
  62. wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
  63. wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
  64. wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
  65. wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
  66. wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
  67. wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
  68. wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
  69. wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
  70. wappa/cli/examples/wappa_full_example/app/models/state_models.py +434 -0
  71. wappa/cli/examples/wappa_full_example/app/models/user_models.py +303 -0
  72. wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +327 -0
  73. wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
  74. wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +502 -0
  75. wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +516 -0
  76. wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +337 -0
  77. wappa/cli/main.py +14 -5
  78. wappa/core/__init__.py +18 -23
  79. wappa/core/config/settings.py +7 -5
  80. wappa/core/events/default_handlers.py +1 -1
  81. wappa/core/factory/wappa_builder.py +38 -25
  82. wappa/core/plugins/redis_plugin.py +1 -3
  83. wappa/core/plugins/wappa_core_plugin.py +7 -6
  84. wappa/core/types.py +12 -12
  85. wappa/core/wappa_app.py +10 -8
  86. wappa/database/__init__.py +3 -4
  87. wappa/domain/enums/messenger_platform.py +1 -2
  88. wappa/domain/factories/media_factory.py +5 -20
  89. wappa/domain/factories/message_factory.py +5 -20
  90. wappa/domain/factories/messenger_factory.py +2 -4
  91. wappa/domain/interfaces/cache_interface.py +7 -7
  92. wappa/domain/interfaces/media_interface.py +2 -5
  93. wappa/domain/models/media_result.py +1 -3
  94. wappa/domain/models/platforms/platform_config.py +1 -3
  95. wappa/messaging/__init__.py +9 -12
  96. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
  97. wappa/models/__init__.py +27 -35
  98. wappa/persistence/__init__.py +12 -15
  99. wappa/persistence/cache_factory.py +0 -1
  100. wappa/persistence/json/__init__.py +1 -1
  101. wappa/persistence/json/cache_adapters.py +37 -25
  102. wappa/persistence/json/handlers/state_handler.py +60 -52
  103. wappa/persistence/json/handlers/table_handler.py +51 -49
  104. wappa/persistence/json/handlers/user_handler.py +71 -55
  105. wappa/persistence/json/handlers/utils/file_manager.py +42 -39
  106. wappa/persistence/json/handlers/utils/key_factory.py +1 -1
  107. wappa/persistence/json/handlers/utils/serialization.py +13 -11
  108. wappa/persistence/json/json_cache_factory.py +4 -8
  109. wappa/persistence/json/storage_manager.py +66 -79
  110. wappa/persistence/memory/__init__.py +1 -1
  111. wappa/persistence/memory/cache_adapters.py +37 -25
  112. wappa/persistence/memory/handlers/state_handler.py +62 -52
  113. wappa/persistence/memory/handlers/table_handler.py +59 -53
  114. wappa/persistence/memory/handlers/user_handler.py +75 -55
  115. wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
  116. wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
  117. wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
  118. wappa/persistence/memory/memory_cache_factory.py +3 -7
  119. wappa/persistence/memory/storage_manager.py +52 -62
  120. wappa/persistence/redis/cache_adapters.py +27 -21
  121. wappa/persistence/redis/ops.py +11 -11
  122. wappa/persistence/redis/redis_client.py +4 -6
  123. wappa/persistence/redis/redis_manager.py +12 -4
  124. wappa/processors/factory.py +5 -5
  125. wappa/schemas/factory.py +2 -5
  126. wappa/schemas/whatsapp/message_types/errors.py +3 -12
  127. wappa/schemas/whatsapp/validators.py +3 -3
  128. wappa/webhooks/__init__.py +17 -18
  129. wappa/webhooks/factory.py +3 -5
  130. wappa/webhooks/whatsapp/__init__.py +10 -13
  131. wappa/webhooks/whatsapp/message_types/audio.py +0 -4
  132. wappa/webhooks/whatsapp/message_types/document.py +1 -9
  133. wappa/webhooks/whatsapp/message_types/errors.py +3 -12
  134. wappa/webhooks/whatsapp/message_types/location.py +1 -21
  135. wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
  136. wappa/webhooks/whatsapp/message_types/text.py +0 -6
  137. wappa/webhooks/whatsapp/message_types/video.py +1 -20
  138. wappa/webhooks/whatsapp/status_models.py +2 -2
  139. wappa/webhooks/whatsapp/validators.py +3 -3
  140. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
  141. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/RECORD +144 -80
  142. wappa/cli/examples/init/pyproject.toml +0 -7
  143. wappa/cli/examples/simple_echo_example/.python-version +0 -1
  144. wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
  145. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
  146. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
  147. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/licenses/LICENSE +0 -0
@@ -5,10 +5,8 @@ Provides additional TTL management functionality beyond the basic
5
5
  memory store implementation.
6
6
  """
7
7
 
8
- import asyncio
9
8
  import logging
10
9
  from datetime import datetime, timedelta
11
- from typing import Optional
12
10
 
13
11
  from .memory_store import get_memory_store
14
12
 
@@ -18,36 +16,36 @@ logger = logging.getLogger("TTLManager")
18
16
  class TTLManager:
19
17
  """
20
18
  Advanced TTL management for memory cache.
21
-
19
+
22
20
  Provides utilities for TTL monitoring, batch operations,
23
21
  and advanced expiration handling.
24
22
  """
25
-
23
+
26
24
  def __init__(self):
27
25
  self.memory_store = get_memory_store()
28
-
26
+
29
27
  async def get_ttl_info(self, namespace: str, context_key: str, key: str) -> dict:
30
28
  """
31
29
  Get detailed TTL information for a key.
32
-
30
+
33
31
  Args:
34
32
  namespace: Cache namespace
35
33
  context_key: Context identifier
36
34
  key: Cache key
37
-
35
+
38
36
  Returns:
39
37
  Dictionary with TTL details
40
38
  """
41
39
  ttl_seconds = await self.memory_store.get_ttl(namespace, context_key, key)
42
-
40
+
43
41
  info = {
44
42
  "key": key,
45
43
  "namespace": namespace,
46
44
  "context_key": context_key,
47
45
  "ttl_seconds": ttl_seconds,
48
- "status": "unknown"
46
+ "status": "unknown",
49
47
  }
50
-
48
+
51
49
  if ttl_seconds == -2:
52
50
  info["status"] = "not_found"
53
51
  info["message"] = "Key does not exist"
@@ -58,140 +56,134 @@ class TTLManager:
58
56
  info["status"] = "expires"
59
57
  info["expires_at"] = datetime.now() + timedelta(seconds=ttl_seconds)
60
58
  info["message"] = f"Key expires in {ttl_seconds} seconds"
61
-
59
+
62
60
  return info
63
-
61
+
64
62
  async def extend_ttl(
65
- self,
66
- namespace: str,
67
- context_key: str,
68
- key: str,
69
- additional_seconds: int
63
+ self, namespace: str, context_key: str, key: str, additional_seconds: int
70
64
  ) -> bool:
71
65
  """
72
66
  Extend TTL by adding additional seconds to current TTL.
73
-
67
+
74
68
  Args:
75
69
  namespace: Cache namespace
76
70
  context_key: Context identifier
77
71
  key: Cache key
78
72
  additional_seconds: Seconds to add to current TTL
79
-
73
+
80
74
  Returns:
81
75
  True if successful, False otherwise
82
76
  """
83
77
  current_ttl = await self.memory_store.get_ttl(namespace, context_key, key)
84
-
78
+
85
79
  if current_ttl == -2:
86
80
  # Key doesn't exist
87
81
  return False
88
82
  elif current_ttl == -1:
89
83
  # No current expiry, set new TTL
90
- return await self.memory_store.set_ttl(namespace, context_key, key, additional_seconds)
84
+ return await self.memory_store.set_ttl(
85
+ namespace, context_key, key, additional_seconds
86
+ )
91
87
  else:
92
88
  # Extend current TTL
93
89
  new_ttl = current_ttl + additional_seconds
94
90
  return await self.memory_store.set_ttl(namespace, context_key, key, new_ttl)
95
-
91
+
96
92
  async def refresh_ttl(
97
- self,
98
- namespace: str,
99
- context_key: str,
100
- key: str,
101
- ttl_seconds: int
93
+ self, namespace: str, context_key: str, key: str, ttl_seconds: int
102
94
  ) -> bool:
103
95
  """
104
96
  Refresh TTL to a new value (reset expiration timer).
105
-
97
+
106
98
  Args:
107
99
  namespace: Cache namespace
108
100
  context_key: Context identifier
109
101
  key: Cache key
110
102
  ttl_seconds: New TTL in seconds
111
-
103
+
112
104
  Returns:
113
105
  True if successful, False otherwise
114
106
  """
115
107
  return await self.memory_store.set_ttl(namespace, context_key, key, ttl_seconds)
116
-
108
+
117
109
  async def clear_ttl(self, namespace: str, context_key: str, key: str) -> bool:
118
110
  """
119
111
  Remove TTL from key (make it persistent).
120
-
112
+
121
113
  Note: This is achieved by setting a very long TTL (100 years).
122
-
114
+
123
115
  Args:
124
116
  namespace: Cache namespace
125
117
  context_key: Context identifier
126
118
  key: Cache key
127
-
119
+
128
120
  Returns:
129
121
  True if successful, False otherwise
130
122
  """
131
123
  # Set TTL to 100 years (effectively no expiry)
132
124
  very_long_ttl = 100 * 365 * 24 * 3600 # 100 years in seconds
133
- return await self.memory_store.set_ttl(namespace, context_key, key, very_long_ttl)
134
-
125
+ return await self.memory_store.set_ttl(
126
+ namespace, context_key, key, very_long_ttl
127
+ )
128
+
135
129
  async def get_expiring_keys(
136
- self,
137
- namespace: str,
138
- context_key: str,
139
- within_seconds: int = 300
130
+ self, namespace: str, context_key: str, within_seconds: int = 300
140
131
  ) -> list[dict]:
141
132
  """
142
133
  Get keys that will expire within specified seconds.
143
-
134
+
144
135
  Args:
145
136
  namespace: Cache namespace
146
137
  context_key: Context identifier
147
138
  within_seconds: Time window in seconds (default: 5 minutes)
148
-
139
+
149
140
  Returns:
150
141
  List of dictionaries with key info for expiring keys
151
142
  """
152
143
  expiring_keys = []
153
-
144
+
154
145
  # Get all keys for the context
155
146
  all_keys = await self.memory_store.get_all_keys(namespace, context_key)
156
-
157
- for key in all_keys.keys():
147
+
148
+ for key in all_keys:
158
149
  ttl_info = await self.get_ttl_info(namespace, context_key, key)
159
- if ttl_info["status"] == "expires" and ttl_info["ttl_seconds"] <= within_seconds:
150
+ if (
151
+ ttl_info["status"] == "expires"
152
+ and ttl_info["ttl_seconds"] <= within_seconds
153
+ ):
160
154
  expiring_keys.append(ttl_info)
161
-
155
+
162
156
  return expiring_keys
163
-
157
+
164
158
  async def batch_refresh_ttl(
165
- self,
166
- namespace: str,
167
- context_key: str,
168
- keys: list[str],
169
- ttl_seconds: int
159
+ self, namespace: str, context_key: str, keys: list[str], ttl_seconds: int
170
160
  ) -> dict[str, bool]:
171
161
  """
172
162
  Refresh TTL for multiple keys in batch.
173
-
163
+
174
164
  Args:
175
165
  namespace: Cache namespace
176
166
  context_key: Context identifier
177
167
  keys: List of cache keys
178
168
  ttl_seconds: New TTL in seconds
179
-
169
+
180
170
  Returns:
181
171
  Dictionary mapping key -> success status
182
172
  """
183
173
  results = {}
184
174
  for key in keys:
185
- results[key] = await self.refresh_ttl(namespace, context_key, key, ttl_seconds)
175
+ results[key] = await self.refresh_ttl(
176
+ namespace, context_key, key, ttl_seconds
177
+ )
186
178
  return results
187
-
179
+
188
180
  async def get_namespace_stats(self, namespace: str) -> dict:
189
181
  """
190
182
  Get statistics for a namespace.
191
-
183
+
192
184
  Args:
193
185
  namespace: Cache namespace
194
-
186
+
195
187
  Returns:
196
188
  Dictionary with namespace statistics
197
189
  """
@@ -201,22 +193,22 @@ class TTLManager:
201
193
  "total_keys": 0,
202
194
  "keys_with_ttl": 0,
203
195
  "keys_persistent": 0,
204
- "estimated_cleanup_needed": 0
196
+ "estimated_cleanup_needed": 0,
205
197
  }
206
-
198
+
207
199
  # This would require access to the internal store structure
208
200
  # For now, we'll provide basic stats that can be calculated
209
201
  # without breaking encapsulation
210
-
202
+
211
203
  try:
212
204
  # Access the store directly for stats (this is a utility function)
213
205
  store = self.memory_store._store[namespace]
214
206
  stats["total_contexts"] = len(store)
215
-
207
+
216
208
  for context_key, context_store in store.items():
217
209
  stats["total_keys"] += len(context_store)
218
-
219
- for key in context_store.keys():
210
+
211
+ for key in context_store:
220
212
  ttl = await self.memory_store.get_ttl(namespace, context_key, key)
221
213
  if ttl == -1:
222
214
  stats["keys_persistent"] += 1
@@ -224,12 +216,12 @@ class TTLManager:
224
216
  stats["keys_with_ttl"] += 1
225
217
  else:
226
218
  stats["estimated_cleanup_needed"] += 1
227
-
219
+
228
220
  except Exception as e:
229
221
  logger.warning(f"Failed to calculate namespace stats for {namespace}: {e}")
230
-
222
+
231
223
  return stats
232
224
 
233
225
 
234
226
  # Global TTL manager instance
235
- ttl_manager = TTLManager()
227
+ ttl_manager = TTLManager()
@@ -45,9 +45,7 @@ class MemoryCacheFactory(ICacheFactory):
45
45
  Returns:
46
46
  ICache adapter wrapping MemoryStateHandler
47
47
  """
48
- return MemoryStateCacheAdapter(
49
- tenant_id=self.tenant_id, user_id=self.user_id
50
- )
48
+ return MemoryStateCacheAdapter(tenant_id=self.tenant_id, user_id=self.user_id)
51
49
 
52
50
  def create_user_cache(self) -> ICache:
53
51
  """
@@ -59,9 +57,7 @@ class MemoryCacheFactory(ICacheFactory):
59
57
  Returns:
60
58
  ICache adapter wrapping MemoryUser
61
59
  """
62
- return MemoryUserCacheAdapter(
63
- tenant_id=self.tenant_id, user_id=self.user_id
64
- )
60
+ return MemoryUserCacheAdapter(tenant_id=self.tenant_id, user_id=self.user_id)
65
61
 
66
62
  def create_table_cache(self) -> ICache:
67
63
  """
@@ -73,4 +69,4 @@ class MemoryCacheFactory(ICacheFactory):
73
69
  Returns:
74
70
  ICache adapter wrapping MemoryTable
75
71
  """
76
- return MemoryTableCacheAdapter(tenant_id=self.tenant_id)
72
+ return MemoryTableCacheAdapter(tenant_id=self.tenant_id)
@@ -6,7 +6,7 @@ BaseModel serialization, and thread-safe operations.
6
6
  """
7
7
 
8
8
  import logging
9
- from typing import Any, Optional
9
+ from typing import Any
10
10
 
11
11
  from pydantic import BaseModel
12
12
 
@@ -17,44 +17,44 @@ logger = logging.getLogger("MemoryStorageManager")
17
17
 
18
18
  class MemoryStorageManager:
19
19
  """High-level memory storage operations manager."""
20
-
20
+
21
21
  def __init__(self):
22
22
  self.memory_store = get_memory_store()
23
-
23
+
24
24
  def _serialize_data(self, data: Any) -> Any:
25
25
  """Serialize data for memory storage (BaseModel -> dict)."""
26
26
  if isinstance(data, BaseModel):
27
27
  return data.model_dump()
28
28
  return data
29
-
29
+
30
30
  def _deserialize_data(self, data: Any, model: type[BaseModel] | None = None) -> Any:
31
31
  """Deserialize data from memory storage."""
32
32
  if data is None:
33
33
  return None
34
-
34
+
35
35
  if model is not None and isinstance(data, dict):
36
36
  return model.model_validate(data)
37
-
37
+
38
38
  return data
39
-
39
+
40
40
  async def get(
41
41
  self,
42
42
  cache_type: str,
43
43
  tenant_id: str,
44
- user_id: Optional[str],
44
+ user_id: str | None,
45
45
  key: str,
46
- model: type[BaseModel] | None = None
46
+ model: type[BaseModel] | None = None,
47
47
  ) -> Any:
48
48
  """
49
49
  Get value from memory cache.
50
-
50
+
51
51
  Args:
52
52
  cache_type: "users", "tables", or "states"
53
53
  tenant_id: Tenant identifier
54
54
  user_id: User identifier (required for users/states)
55
55
  key: Cache key
56
56
  model: Optional BaseModel for deserialization
57
-
57
+
58
58
  Returns:
59
59
  Cached value or None if not found/expired
60
60
  """
@@ -65,19 +65,19 @@ class MemoryStorageManager:
65
65
  except Exception as e:
66
66
  logger.error(f"Failed to get key '{key}' from {cache_type} cache: {e}")
67
67
  return None
68
-
68
+
69
69
  async def set(
70
70
  self,
71
71
  cache_type: str,
72
72
  tenant_id: str,
73
- user_id: Optional[str],
73
+ user_id: str | None,
74
74
  key: str,
75
75
  value: Any,
76
- ttl: Optional[int] = None
76
+ ttl: int | None = None,
77
77
  ) -> bool:
78
78
  """
79
79
  Set value in memory cache.
80
-
80
+
81
81
  Args:
82
82
  cache_type: "users", "tables", or "states"
83
83
  tenant_id: Tenant identifier
@@ -85,34 +85,32 @@ class MemoryStorageManager:
85
85
  key: Cache key
86
86
  value: Value to cache
87
87
  ttl: Time to live in seconds
88
-
88
+
89
89
  Returns:
90
90
  True if successful, False otherwise
91
91
  """
92
92
  try:
93
93
  context_key = self._build_context_key(cache_type, tenant_id, user_id)
94
94
  serialized_value = self._serialize_data(value)
95
- return await self.memory_store.set(cache_type, context_key, key, serialized_value, ttl)
95
+ return await self.memory_store.set(
96
+ cache_type, context_key, key, serialized_value, ttl
97
+ )
96
98
  except Exception as e:
97
99
  logger.error(f"Failed to set key '{key}' in {cache_type} cache: {e}")
98
100
  return False
99
-
101
+
100
102
  async def delete(
101
- self,
102
- cache_type: str,
103
- tenant_id: str,
104
- user_id: Optional[str],
105
- key: str
103
+ self, cache_type: str, tenant_id: str, user_id: str | None, key: str
106
104
  ) -> bool:
107
105
  """
108
106
  Delete key from memory cache.
109
-
107
+
110
108
  Args:
111
109
  cache_type: "users", "tables", or "states"
112
110
  tenant_id: Tenant identifier
113
111
  user_id: User identifier (required for users/states)
114
112
  key: Cache key to delete
115
-
113
+
116
114
  Returns:
117
115
  True if deleted or didn't exist, False on error
118
116
  """
@@ -122,23 +120,19 @@ class MemoryStorageManager:
122
120
  except Exception as e:
123
121
  logger.error(f"Failed to delete key '{key}' from {cache_type} cache: {e}")
124
122
  return False
125
-
123
+
126
124
  async def exists(
127
- self,
128
- cache_type: str,
129
- tenant_id: str,
130
- user_id: Optional[str],
131
- key: str
125
+ self, cache_type: str, tenant_id: str, user_id: str | None, key: str
132
126
  ) -> bool:
133
127
  """
134
128
  Check if key exists in memory cache.
135
-
129
+
136
130
  Args:
137
131
  cache_type: "users", "tables", or "states"
138
132
  tenant_id: Tenant identifier
139
133
  user_id: User identifier (required for users/states)
140
134
  key: Cache key to check
141
-
135
+
142
136
  Returns:
143
137
  True if exists and not expired, False otherwise
144
138
  """
@@ -146,19 +140,17 @@ class MemoryStorageManager:
146
140
  context_key = self._build_context_key(cache_type, tenant_id, user_id)
147
141
  return await self.memory_store.exists(cache_type, context_key, key)
148
142
  except Exception as e:
149
- logger.error(f"Failed to check existence of key '{key}' in {cache_type} cache: {e}")
143
+ logger.error(
144
+ f"Failed to check existence of key '{key}' in {cache_type} cache: {e}"
145
+ )
150
146
  return False
151
-
147
+
152
148
  async def get_ttl(
153
- self,
154
- cache_type: str,
155
- tenant_id: str,
156
- user_id: Optional[str],
157
- key: str
149
+ self, cache_type: str, tenant_id: str, user_id: str | None, key: str
158
150
  ) -> int:
159
151
  """
160
152
  Get remaining TTL for key.
161
-
153
+
162
154
  Returns:
163
155
  Remaining TTL in seconds, -1 if no expiry, -2 if doesn't exist
164
156
  """
@@ -166,23 +158,20 @@ class MemoryStorageManager:
166
158
  context_key = self._build_context_key(cache_type, tenant_id, user_id)
167
159
  return await self.memory_store.get_ttl(cache_type, context_key, key)
168
160
  except Exception as e:
169
- logger.error(f"Failed to get TTL for key '{key}' in {cache_type} cache: {e}")
161
+ logger.error(
162
+ f"Failed to get TTL for key '{key}' in {cache_type} cache: {e}"
163
+ )
170
164
  return -2
171
-
165
+
172
166
  async def set_ttl(
173
- self,
174
- cache_type: str,
175
- tenant_id: str,
176
- user_id: Optional[str],
177
- key: str,
178
- ttl: int
167
+ self, cache_type: str, tenant_id: str, user_id: str | None, key: str, ttl: int
179
168
  ) -> bool:
180
169
  """
181
170
  Set TTL for key.
182
-
171
+
183
172
  Args:
184
173
  ttl: Time to live in seconds
185
-
174
+
186
175
  Returns:
187
176
  True if successful, False otherwise
188
177
  """
@@ -190,23 +179,22 @@ class MemoryStorageManager:
190
179
  context_key = self._build_context_key(cache_type, tenant_id, user_id)
191
180
  return await self.memory_store.set_ttl(cache_type, context_key, key, ttl)
192
181
  except Exception as e:
193
- logger.error(f"Failed to set TTL for key '{key}' in {cache_type} cache: {e}")
182
+ logger.error(
183
+ f"Failed to set TTL for key '{key}' in {cache_type} cache: {e}"
184
+ )
194
185
  return False
195
-
186
+
196
187
  async def get_all_keys(
197
- self,
198
- cache_type: str,
199
- tenant_id: str,
200
- user_id: Optional[str]
188
+ self, cache_type: str, tenant_id: str, user_id: str | None
201
189
  ) -> dict[str, Any]:
202
190
  """
203
191
  Get all keys for a context.
204
-
192
+
205
193
  Args:
206
194
  cache_type: "users", "tables", or "states"
207
195
  tenant_id: Tenant identifier
208
196
  user_id: User identifier (required for users/states)
209
-
197
+
210
198
  Returns:
211
199
  Dictionary of all non-expired key-value pairs
212
200
  """
@@ -216,8 +204,10 @@ class MemoryStorageManager:
216
204
  except Exception as e:
217
205
  logger.error(f"Failed to get all keys from {cache_type} cache: {e}")
218
206
  return {}
219
-
220
- def _build_context_key(self, cache_type: str, tenant_id: str, user_id: Optional[str]) -> str:
207
+
208
+ def _build_context_key(
209
+ self, cache_type: str, tenant_id: str, user_id: str | None
210
+ ) -> str:
221
211
  """Build context key for isolation."""
222
212
  if cache_type == "tables":
223
213
  # Tables only use tenant_id for context
@@ -232,4 +222,4 @@ class MemoryStorageManager:
232
222
 
233
223
 
234
224
  # Global storage manager instance
235
- storage_manager = MemoryStorageManager()
225
+ storage_manager = MemoryStorageManager()
@@ -141,15 +141,15 @@ class RedisUserCacheAdapter(ICache):
141
141
  class RedisTableCacheAdapter(ICache):
142
142
  """
143
143
  Adapter that makes RedisTable implement ICache interface.
144
-
144
+
145
145
  Table Key Format Guide:
146
146
  Use create_table_key(table_name, pkid) to generate proper keys.
147
-
147
+
148
148
  Examples:
149
149
  # Good - using helper method
150
150
  key = cache.create_table_key("user_profiles", "12345")
151
151
  await cache.set(key, user_data)
152
-
152
+
153
153
  # Also supported - manual format
154
154
  key = "user_profiles:12345"
155
155
  await cache.set(key, user_data)
@@ -157,29 +157,29 @@ class RedisTableCacheAdapter(ICache):
157
157
 
158
158
  def __init__(self, tenant_id: str, redis_alias: str = "table"):
159
159
  self._handler = RedisTable(tenant=tenant_id, redis_alias=redis_alias)
160
-
160
+
161
161
  def create_table_key(self, table_name: str, pkid: str) -> str:
162
162
  """
163
163
  Create a properly formatted table cache key.
164
-
164
+
165
165
  Args:
166
166
  table_name: Name of the table (e.g., "user_profiles", "message_logs")
167
167
  pkid: Primary key ID (e.g., user_id, message_id)
168
-
168
+
169
169
  Returns:
170
170
  Formatted key string for use with cache methods
171
-
171
+
172
172
  Example:
173
173
  key = cache.create_table_key("user_profiles", "12345")
174
174
  # Returns: "user_profiles:12345"
175
175
  """
176
176
  if not table_name or not pkid:
177
177
  raise ValueError("Both table_name and pkid must be provided and non-empty")
178
-
178
+
179
179
  # Sanitize inputs to avoid conflicts
180
180
  safe_table_name = str(table_name).replace(":", "_")
181
181
  safe_pkid = str(pkid).replace(":", "_")
182
-
182
+
183
183
  return f"{safe_table_name}:{safe_pkid}"
184
184
 
185
185
  async def get(
@@ -250,36 +250,42 @@ class RedisTableCacheAdapter(ICache):
250
250
  def _parse_key(self, key: str) -> tuple[str, str]:
251
251
  """
252
252
  Parse key into table_name and pkid with validation.
253
-
253
+
254
254
  Args:
255
255
  key: Cache key in format "table_name:pkid"
256
-
256
+
257
257
  Returns:
258
258
  Tuple of (table_name, pkid)
259
-
259
+
260
260
  Raises:
261
261
  ValueError: If key format is invalid
262
262
  """
263
263
  if not key:
264
264
  raise ValueError("Key cannot be empty")
265
-
265
+
266
266
  if ":" not in key:
267
267
  raise ValueError(
268
268
  f"Invalid table cache key format: '{key}'. "
269
269
  f"Expected format: 'table_name:pkid'. "
270
270
  f"Use create_table_key(table_name, pkid) to generate proper keys."
271
271
  )
272
-
272
+
273
273
  parts = key.split(":", 1)
274
274
  if len(parts) != 2:
275
- raise ValueError(f"Invalid table cache key format: '{key}'. Expected exactly one ':' separator.")
276
-
275
+ raise ValueError(
276
+ f"Invalid table cache key format: '{key}'. Expected exactly one ':' separator."
277
+ )
278
+
277
279
  table_name, pkid = parts
278
-
280
+
279
281
  if not table_name.strip():
280
- raise ValueError(f"Invalid table cache key: '{key}'. Table name cannot be empty.")
281
-
282
+ raise ValueError(
283
+ f"Invalid table cache key: '{key}'. Table name cannot be empty."
284
+ )
285
+
282
286
  if not pkid.strip():
283
- raise ValueError(f"Invalid table cache key: '{key}'. Primary key ID cannot be empty.")
284
-
287
+ raise ValueError(
288
+ f"Invalid table cache key: '{key}'. Primary key ID cannot be empty."
289
+ )
290
+
285
291
  return table_name.strip(), pkid.strip()