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.
- wappa/__init__.py +4 -5
- wappa/api/controllers/webhook_controller.py +5 -2
- wappa/api/dependencies/__init__.py +0 -5
- wappa/api/middleware/error_handler.py +4 -4
- wappa/api/middleware/owner.py +11 -5
- wappa/api/routes/webhooks.py +2 -2
- wappa/cli/__init__.py +1 -1
- wappa/cli/examples/init/.env.example +33 -0
- wappa/cli/examples/init/app/__init__.py +0 -0
- wappa/cli/examples/init/app/main.py +9 -0
- wappa/cli/examples/init/app/master_event.py +10 -0
- wappa/cli/examples/json_cache_example/.env.example +33 -0
- wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/main.py +247 -0
- wappa/cli/examples/json_cache_example/app/master_event.py +455 -0
- wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +256 -0
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +192 -0
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +256 -0
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +187 -0
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +272 -0
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +239 -0
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +174 -0
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +251 -0
- wappa/cli/examples/openai_transcript/.gitignore +63 -4
- wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
- wappa/cli/examples/openai_transcript/app/main.py +9 -0
- wappa/cli/examples/openai_transcript/app/master_event.py +62 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +89 -0
- wappa/cli/examples/redis_cache_example/.env.example +33 -0
- wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
- wappa/cli/examples/redis_cache_example/app/main.py +246 -0
- wappa/cli/examples/redis_cache_example/app/master_event.py +455 -0
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +256 -0
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +192 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +256 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +187 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +272 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +239 -0
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +174 -0
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +251 -0
- wappa/cli/examples/simple_echo_example/.env.example +33 -0
- wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
- wappa/cli/examples/simple_echo_example/app/main.py +191 -0
- wappa/cli/examples/simple_echo_example/app/master_event.py +230 -0
- wappa/cli/examples/wappa_full_example/.env.example +33 -0
- wappa/cli/examples/wappa_full_example/.gitignore +63 -4
- wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
- wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +492 -0
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +559 -0
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +514 -0
- wappa/cli/examples/wappa_full_example/app/main.py +269 -0
- wappa/cli/examples/wappa_full_example/app/master_event.py +504 -0
- wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
- wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
- wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/models/state_models.py +434 -0
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +303 -0
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +327 -0
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +502 -0
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +516 -0
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +337 -0
- wappa/cli/main.py +14 -5
- wappa/core/__init__.py +18 -23
- wappa/core/config/settings.py +7 -5
- wappa/core/events/default_handlers.py +1 -1
- wappa/core/factory/wappa_builder.py +38 -25
- wappa/core/plugins/redis_plugin.py +1 -3
- wappa/core/plugins/wappa_core_plugin.py +7 -6
- wappa/core/types.py +12 -12
- wappa/core/wappa_app.py +10 -8
- wappa/database/__init__.py +3 -4
- wappa/domain/enums/messenger_platform.py +1 -2
- wappa/domain/factories/media_factory.py +5 -20
- wappa/domain/factories/message_factory.py +5 -20
- wappa/domain/factories/messenger_factory.py +2 -4
- wappa/domain/interfaces/cache_interface.py +7 -7
- wappa/domain/interfaces/media_interface.py +2 -5
- wappa/domain/models/media_result.py +1 -3
- wappa/domain/models/platforms/platform_config.py +1 -3
- wappa/messaging/__init__.py +9 -12
- wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
- wappa/models/__init__.py +27 -35
- wappa/persistence/__init__.py +12 -15
- wappa/persistence/cache_factory.py +0 -1
- wappa/persistence/json/__init__.py +1 -1
- wappa/persistence/json/cache_adapters.py +37 -25
- wappa/persistence/json/handlers/state_handler.py +60 -52
- wappa/persistence/json/handlers/table_handler.py +51 -49
- wappa/persistence/json/handlers/user_handler.py +71 -55
- wappa/persistence/json/handlers/utils/file_manager.py +42 -39
- wappa/persistence/json/handlers/utils/key_factory.py +1 -1
- wappa/persistence/json/handlers/utils/serialization.py +13 -11
- wappa/persistence/json/json_cache_factory.py +4 -8
- wappa/persistence/json/storage_manager.py +66 -79
- wappa/persistence/memory/__init__.py +1 -1
- wappa/persistence/memory/cache_adapters.py +37 -25
- wappa/persistence/memory/handlers/state_handler.py +62 -52
- wappa/persistence/memory/handlers/table_handler.py +59 -53
- wappa/persistence/memory/handlers/user_handler.py +75 -55
- wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
- wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
- wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
- wappa/persistence/memory/memory_cache_factory.py +3 -7
- wappa/persistence/memory/storage_manager.py +52 -62
- wappa/persistence/redis/cache_adapters.py +27 -21
- wappa/persistence/redis/ops.py +11 -11
- wappa/persistence/redis/redis_client.py +4 -6
- wappa/persistence/redis/redis_manager.py +12 -4
- wappa/processors/factory.py +5 -5
- wappa/schemas/factory.py +2 -5
- wappa/schemas/whatsapp/message_types/errors.py +3 -12
- wappa/schemas/whatsapp/validators.py +3 -3
- wappa/webhooks/__init__.py +17 -18
- wappa/webhooks/factory.py +3 -5
- wappa/webhooks/whatsapp/__init__.py +10 -13
- wappa/webhooks/whatsapp/message_types/audio.py +0 -4
- wappa/webhooks/whatsapp/message_types/document.py +1 -9
- wappa/webhooks/whatsapp/message_types/errors.py +3 -12
- wappa/webhooks/whatsapp/message_types/location.py +1 -21
- wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
- wappa/webhooks/whatsapp/message_types/text.py +0 -6
- wappa/webhooks/whatsapp/message_types/video.py +1 -20
- wappa/webhooks/whatsapp/status_models.py +2 -2
- wappa/webhooks/whatsapp/validators.py +3 -3
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/RECORD +144 -80
- wappa/cli/examples/init/pyproject.toml +0 -7
- wappa/cli/examples/simple_echo_example/.python-version +0 -1
- wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
- {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
|
|
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:
|
|
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:
|
|
82
|
+
user_id: str | None,
|
|
83
83
|
key: str,
|
|
84
84
|
value: Any,
|
|
85
|
-
ttl:
|
|
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(
|
|
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()
|
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
270
|
-
|
|
271
|
-
|
|
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()
|