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
@@ -7,7 +7,7 @@ BaseModel serialization, and atomic file operations.
7
7
 
8
8
  import logging
9
9
  from datetime import datetime
10
- from typing import Any, Dict, Optional
10
+ from typing import Any
11
11
 
12
12
  from pydantic import BaseModel
13
13
 
@@ -24,69 +24,69 @@ logger = logging.getLogger("JSONStorageManager")
24
24
 
25
25
  class JSONStorageManager:
26
26
  """High-level JSON storage operations manager."""
27
-
27
+
28
28
  def __init__(self):
29
29
  # Ensure cache directories exist on initialization
30
30
  file_manager.ensure_cache_directories()
31
-
31
+
32
32
  async def get(
33
- self,
34
- cache_type: str,
35
- tenant_id: str,
36
- user_id: Optional[str],
33
+ self,
34
+ cache_type: str,
35
+ tenant_id: str,
36
+ user_id: str | None,
37
37
  key: str,
38
- model: type[BaseModel] | None = None
38
+ model: type[BaseModel] | None = None,
39
39
  ) -> Any:
40
40
  """
41
41
  Get value from JSON cache.
42
-
42
+
43
43
  Args:
44
44
  cache_type: "users", "tables", or "states"
45
45
  tenant_id: Tenant identifier
46
46
  user_id: User identifier (required for users/states)
47
47
  key: Cache key
48
48
  model: Optional BaseModel for deserialization
49
-
49
+
50
50
  Returns:
51
51
  Cached value or None if not found/expired
52
52
  """
53
53
  try:
54
54
  file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
55
55
  file_data = await file_manager.read_file(file_path)
56
-
56
+
57
57
  if not file_data:
58
58
  return None
59
-
59
+
60
60
  # Extract data and check expiration
61
61
  cache_data = extract_cache_file_data(file_data)
62
62
  if cache_data is None:
63
63
  # Expired - delete the file
64
64
  await file_manager.delete_file(file_path)
65
65
  return None
66
-
66
+
67
67
  # Get specific key data
68
68
  if key not in cache_data:
69
69
  return None
70
-
70
+
71
71
  value_data = cache_data[key]
72
72
  return deserialize_from_json(value_data, model)
73
-
73
+
74
74
  except Exception as e:
75
75
  logger.error(f"Failed to get key '{key}' from {cache_type} cache: {e}")
76
76
  return None
77
-
77
+
78
78
  async def set(
79
79
  self,
80
80
  cache_type: str,
81
81
  tenant_id: str,
82
- user_id: Optional[str],
82
+ user_id: str | None,
83
83
  key: str,
84
84
  value: Any,
85
- ttl: Optional[int] = None
85
+ ttl: int | None = None,
86
86
  ) -> bool:
87
87
  """
88
88
  Set value in JSON cache.
89
-
89
+
90
90
  Args:
91
91
  cache_type: "users", "tables", or "states"
92
92
  tenant_id: Tenant identifier
@@ -94,192 +94,179 @@ class JSONStorageManager:
94
94
  key: Cache key
95
95
  value: Value to cache
96
96
  ttl: Time to live in seconds
97
-
97
+
98
98
  Returns:
99
99
  True if successful, False otherwise
100
100
  """
101
101
  try:
102
102
  file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
103
-
103
+
104
104
  # Read existing data
105
105
  file_data = await file_manager.read_file(file_path)
106
106
  cache_data = extract_cache_file_data(file_data) if file_data else {}
107
107
  if cache_data is None:
108
108
  cache_data = {}
109
-
109
+
110
110
  # Update key
111
111
  cache_data[key] = serialize_for_json(value)
112
-
112
+
113
113
  # Create new file data with TTL
114
114
  new_file_data = create_cache_file_data(cache_data, ttl)
115
-
115
+
116
116
  # Write file
117
117
  return await file_manager.write_file(file_path, new_file_data)
118
-
118
+
119
119
  except Exception as e:
120
120
  logger.error(f"Failed to set key '{key}' in {cache_type} cache: {e}")
121
121
  return False
122
-
122
+
123
123
  async def delete(
124
- self,
125
- cache_type: str,
126
- tenant_id: str,
127
- user_id: Optional[str],
128
- key: str
124
+ self, cache_type: str, tenant_id: str, user_id: str | None, key: str
129
125
  ) -> bool:
130
126
  """
131
127
  Delete key from JSON cache.
132
-
128
+
133
129
  Args:
134
130
  cache_type: "users", "tables", or "states"
135
131
  tenant_id: Tenant identifier
136
132
  user_id: User identifier (required for users/states)
137
133
  key: Cache key to delete
138
-
134
+
139
135
  Returns:
140
136
  True if deleted or didn't exist, False on error
141
137
  """
142
138
  try:
143
139
  file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
144
-
140
+
145
141
  # Read existing data
146
142
  file_data = await file_manager.read_file(file_path)
147
143
  if not file_data:
148
144
  return True # Already doesn't exist
149
-
145
+
150
146
  cache_data = extract_cache_file_data(file_data)
151
147
  if cache_data is None or key not in cache_data:
152
148
  return True # Already doesn't exist
153
-
149
+
154
150
  # Remove key
155
151
  del cache_data[key]
156
-
152
+
157
153
  # If no keys left, delete the file
158
154
  if not cache_data:
159
155
  return await file_manager.delete_file(file_path)
160
-
156
+
161
157
  # Otherwise update file
162
158
  new_file_data = create_cache_file_data(cache_data)
163
159
  return await file_manager.write_file(file_path, new_file_data)
164
-
160
+
165
161
  except Exception as e:
166
162
  logger.error(f"Failed to delete key '{key}' from {cache_type} cache: {e}")
167
163
  return False
168
-
164
+
169
165
  async def exists(
170
- self,
171
- cache_type: str,
172
- tenant_id: str,
173
- user_id: Optional[str],
174
- key: str
166
+ self, cache_type: str, tenant_id: str, user_id: str | None, key: str
175
167
  ) -> bool:
176
168
  """
177
169
  Check if key exists in JSON cache.
178
-
170
+
179
171
  Args:
180
172
  cache_type: "users", "tables", or "states"
181
173
  tenant_id: Tenant identifier
182
174
  user_id: User identifier (required for users/states)
183
175
  key: Cache key to check
184
-
176
+
185
177
  Returns:
186
178
  True if exists and not expired, False otherwise
187
179
  """
188
180
  try:
189
181
  file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
190
182
  file_data = await file_manager.read_file(file_path)
191
-
183
+
192
184
  if not file_data:
193
185
  return False
194
-
186
+
195
187
  cache_data = extract_cache_file_data(file_data)
196
188
  if cache_data is None:
197
189
  # Expired - delete the file
198
190
  await file_manager.delete_file(file_path)
199
191
  return False
200
-
192
+
201
193
  return key in cache_data
202
-
194
+
203
195
  except Exception as e:
204
- logger.error(f"Failed to check existence of key '{key}' in {cache_type} cache: {e}")
196
+ logger.error(
197
+ f"Failed to check existence of key '{key}' in {cache_type} cache: {e}"
198
+ )
205
199
  return False
206
-
200
+
207
201
  async def get_ttl(
208
- self,
209
- cache_type: str,
210
- tenant_id: str,
211
- user_id: Optional[str]
202
+ self, cache_type: str, tenant_id: str, user_id: str | None
212
203
  ) -> int:
213
204
  """
214
205
  Get remaining TTL for cache file.
215
-
206
+
216
207
  Returns:
217
208
  Remaining TTL in seconds, -1 if no expiry, -2 if doesn't exist
218
209
  """
219
210
  try:
220
211
  file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
221
212
  file_data = await file_manager.read_file(file_path)
222
-
213
+
223
214
  if not file_data:
224
215
  return -2 # Doesn't exist
225
-
216
+
226
217
  metadata = file_data.get("_metadata", {})
227
218
  expires_at_str = metadata.get("expires_at")
228
-
219
+
229
220
  if not expires_at_str:
230
221
  return -1 # No expiry
231
-
222
+
232
223
  try:
233
224
  expires_at = datetime.fromisoformat(expires_at_str)
234
225
  now = datetime.now()
235
-
226
+
236
227
  if now >= expires_at:
237
228
  return -2 # Already expired
238
-
229
+
239
230
  return int((expires_at - now).total_seconds())
240
-
231
+
241
232
  except ValueError:
242
233
  return -1 # Invalid expiry format, treat as no expiry
243
-
234
+
244
235
  except Exception as e:
245
236
  logger.error(f"Failed to get TTL for {cache_type} cache: {e}")
246
237
  return -2
247
-
238
+
248
239
  async def set_ttl(
249
- self,
250
- cache_type: str,
251
- tenant_id: str,
252
- user_id: Optional[str],
253
- ttl: int
240
+ self, cache_type: str, tenant_id: str, user_id: str | None, ttl: int
254
241
  ) -> bool:
255
242
  """
256
243
  Set TTL for cache file.
257
-
244
+
258
245
  Args:
259
246
  ttl: Time to live in seconds
260
-
247
+
261
248
  Returns:
262
249
  True if successful, False otherwise
263
250
  """
264
251
  try:
265
252
  file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
266
253
  file_data = await file_manager.read_file(file_path)
267
-
254
+
268
255
  if not file_data:
269
256
  return False # File doesn't exist
270
-
257
+
271
258
  cache_data = extract_cache_file_data(file_data)
272
259
  if cache_data is None:
273
260
  return False # Already expired
274
-
261
+
275
262
  # Create new file data with updated TTL
276
263
  new_file_data = create_cache_file_data(cache_data, ttl)
277
264
  return await file_manager.write_file(file_path, new_file_data)
278
-
265
+
279
266
  except Exception as e:
280
267
  logger.error(f"Failed to set TTL for {cache_type} cache: {e}")
281
268
  return False
282
269
 
283
270
 
284
271
  # Global storage manager instance
285
- storage_manager = JSONStorageManager()
272
+ storage_manager = JSONStorageManager()
@@ -11,4 +11,4 @@ Usage:
11
11
 
12
12
  from .memory_cache_factory import MemoryCacheFactory
13
13
 
14
- __all__ = ["MemoryCacheFactory"]
14
+ __all__ = ["MemoryCacheFactory"]
@@ -28,7 +28,9 @@ class MemoryStateCacheAdapter(ICache):
28
28
  """Get cached data by key."""
29
29
  return await self._handler.get(key, models=models)
30
30
 
31
- async def set(self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None) -> bool:
31
+ async def set(
32
+ self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None
33
+ ) -> bool:
32
34
  """Set cached data with optional TTL."""
33
35
  return await self._handler.upsert(key, data, ttl=ttl)
34
36
 
@@ -84,7 +86,9 @@ class MemoryUserCacheAdapter(ICache):
84
86
  """Get cached data by key. For user cache, key is ignored as it uses user_id."""
85
87
  return await self._handler.get(models=models)
86
88
 
87
- async def set(self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None) -> bool:
89
+ async def set(
90
+ self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None
91
+ ) -> bool:
88
92
  """Set cached data with optional TTL."""
89
93
  return await self._handler.upsert(data, ttl=ttl)
90
94
 
@@ -131,15 +135,15 @@ class MemoryUserCacheAdapter(ICache):
131
135
  class MemoryTableCacheAdapter(ICache):
132
136
  """
133
137
  Adapter that makes MemoryTable implement ICache interface.
134
-
138
+
135
139
  Table Key Format Guide:
136
140
  Use create_table_key(table_name, pkid) to generate proper keys.
137
-
141
+
138
142
  Examples:
139
143
  # Good - using helper method
140
144
  key = cache.create_table_key("user_profiles", "12345")
141
145
  await cache.set(key, user_data)
142
-
146
+
143
147
  # Also supported - manual format
144
148
  key = "user_profiles:12345"
145
149
  await cache.set(key, user_data)
@@ -147,29 +151,29 @@ class MemoryTableCacheAdapter(ICache):
147
151
 
148
152
  def __init__(self, tenant_id: str):
149
153
  self._handler = MemoryTable(tenant=tenant_id)
150
-
154
+
151
155
  def create_table_key(self, table_name: str, pkid: str) -> str:
152
156
  """
153
157
  Create a properly formatted table cache key.
154
-
158
+
155
159
  Args:
156
160
  table_name: Name of the table (e.g., "user_profiles", "message_logs")
157
161
  pkid: Primary key ID (e.g., user_id, message_id)
158
-
162
+
159
163
  Returns:
160
164
  Formatted key string for use with cache methods
161
-
165
+
162
166
  Example:
163
167
  key = cache.create_table_key("user_profiles", "12345")
164
168
  # Returns: "user_profiles:12345"
165
169
  """
166
170
  if not table_name or not pkid:
167
171
  raise ValueError("Both table_name and pkid must be provided and non-empty")
168
-
172
+
169
173
  # Sanitize inputs to avoid conflicts
170
174
  safe_table_name = str(table_name).replace(":", "_")
171
175
  safe_pkid = str(pkid).replace(":", "_")
172
-
176
+
173
177
  return f"{safe_table_name}:{safe_pkid}"
174
178
 
175
179
  async def get(
@@ -179,7 +183,9 @@ class MemoryTableCacheAdapter(ICache):
179
183
  table_name, pkid = self._parse_key(key)
180
184
  return await self._handler.get(table_name, pkid, models=models)
181
185
 
182
- async def set(self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None) -> bool:
186
+ async def set(
187
+ self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None
188
+ ) -> bool:
183
189
  """Set cached data with optional TTL."""
184
190
  table_name, pkid = self._parse_key(key)
185
191
  return await self._handler.upsert(table_name, pkid, data, ttl=ttl)
@@ -236,36 +242,42 @@ class MemoryTableCacheAdapter(ICache):
236
242
  def _parse_key(self, key: str) -> tuple[str, str]:
237
243
  """
238
244
  Parse key into table_name and pkid with validation.
239
-
245
+
240
246
  Args:
241
247
  key: Cache key in format "table_name:pkid"
242
-
248
+
243
249
  Returns:
244
250
  Tuple of (table_name, pkid)
245
-
251
+
246
252
  Raises:
247
253
  ValueError: If key format is invalid
248
254
  """
249
255
  if not key:
250
256
  raise ValueError("Key cannot be empty")
251
-
257
+
252
258
  if ":" not in key:
253
259
  raise ValueError(
254
260
  f"Invalid table cache key format: '{key}'. "
255
261
  f"Expected format: 'table_name:pkid'. "
256
262
  f"Use create_table_key(table_name, pkid) to generate proper keys."
257
263
  )
258
-
264
+
259
265
  parts = key.split(":", 1)
260
266
  if len(parts) != 2:
261
- raise ValueError(f"Invalid table cache key format: '{key}'. Expected exactly one ':' separator.")
262
-
267
+ raise ValueError(
268
+ f"Invalid table cache key format: '{key}'. Expected exactly one ':' separator."
269
+ )
270
+
263
271
  table_name, pkid = parts
264
-
272
+
265
273
  if not table_name.strip():
266
- raise ValueError(f"Invalid table cache key: '{key}'. Table name cannot be empty.")
267
-
274
+ raise ValueError(
275
+ f"Invalid table cache key: '{key}'. Table name cannot be empty."
276
+ )
277
+
268
278
  if not pkid.strip():
269
- raise ValueError(f"Invalid table cache key: '{key}'. Primary key ID cannot be empty.")
270
-
271
- return table_name.strip(), pkid.strip()
279
+ raise ValueError(
280
+ f"Invalid table cache key: '{key}'. Primary key ID cannot be empty."
281
+ )
282
+
283
+ return table_name.strip(), pkid.strip()