wappa 0.1.9__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/app/main.py +2 -1
- wappa/cli/examples/init/app/master_event.py +5 -3
- wappa/cli/examples/json_cache_example/app/__init__.py +1 -1
- wappa/cli/examples/json_cache_example/app/main.py +56 -44
- wappa/cli/examples/json_cache_example/app/master_event.py +181 -145
- wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -1
- wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +32 -51
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +2 -2
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +52 -46
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +70 -62
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +41 -44
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +83 -71
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +73 -57
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +2 -2
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +54 -56
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +85 -80
- wappa/cli/examples/openai_transcript/app/main.py +2 -1
- wappa/cli/examples/openai_transcript/app/master_event.py +31 -22
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +1 -1
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +37 -24
- wappa/cli/examples/redis_cache_example/app/__init__.py +1 -1
- wappa/cli/examples/redis_cache_example/app/main.py +56 -44
- wappa/cli/examples/redis_cache_example/app/master_event.py +181 -145
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +31 -50
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +2 -2
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +52 -46
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +70 -62
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +41 -44
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +83 -71
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +73 -57
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +2 -2
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +54 -56
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +85 -80
- wappa/cli/examples/simple_echo_example/app/__init__.py +1 -1
- wappa/cli/examples/simple_echo_example/app/main.py +41 -33
- wappa/cli/examples/simple_echo_example/app/master_event.py +78 -57
- wappa/cli/examples/wappa_full_example/app/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +134 -126
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +237 -229
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +170 -148
- wappa/cli/examples/wappa_full_example/app/main.py +51 -39
- wappa/cli/examples/wappa_full_example/app/master_event.py +179 -120
- wappa/cli/examples/wappa_full_example/app/models/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/models/state_models.py +113 -104
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +92 -76
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +109 -83
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +1 -1
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +132 -113
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +175 -132
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +126 -87
- wappa/cli/main.py +9 -4
- 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.9.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/RECORD +126 -126
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
- {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
- {wappa-0.1.9.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(
|
|
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(
|
|
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
|
|
147
|
+
|
|
148
|
+
for key in all_keys:
|
|
158
149
|
ttl_info = await self.get_ttl_info(namespace, context_key, key)
|
|
159
|
-
if
|
|
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(
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
73
|
+
user_id: str | None,
|
|
74
74
|
key: str,
|
|
75
75
|
value: Any,
|
|
76
|
-
ttl:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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()
|