wappa 0.1.8__py3-none-any.whl → 0.1.9__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/cli/examples/init/.env.example +33 -0
- wappa/cli/examples/init/app/__init__.py +0 -0
- wappa/cli/examples/init/app/main.py +8 -0
- wappa/cli/examples/init/app/master_event.py +8 -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 +235 -0
- wappa/cli/examples/json_cache_example/app/master_event.py +419 -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 +275 -0
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +186 -0
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +248 -0
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +190 -0
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +260 -0
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +223 -0
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +176 -0
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +246 -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 +8 -0
- wappa/cli/examples/openai_transcript/app/master_event.py +53 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +76 -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 +234 -0
- wappa/cli/examples/redis_cache_example/app/master_event.py +419 -0
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +275 -0
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +186 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +248 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +190 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +260 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +223 -0
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +176 -0
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +246 -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 +183 -0
- wappa/cli/examples/simple_echo_example/app/master_event.py +209 -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 +484 -0
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +551 -0
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +492 -0
- wappa/cli/examples/wappa_full_example/app/main.py +257 -0
- wappa/cli/examples/wappa_full_example/app/master_event.py +445 -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 +425 -0
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +287 -0
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +301 -0
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +483 -0
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +473 -0
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +298 -0
- wappa/cli/main.py +8 -4
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/METADATA +1 -1
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/RECORD +75 -11
- 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.9.dist-info}/WHEEL +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/entry_points.txt +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cache utilities for Redis operations in the Wappa Full Example application.
|
|
3
|
+
|
|
4
|
+
This module provides helper functions and classes for working with Redis cache,
|
|
5
|
+
including user management, state management, and statistics tracking.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from typing import Any, Dict, List, Optional, Type, TypeVar
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
from ..models.state_models import InteractiveState, StateType
|
|
15
|
+
from ..models.user_models import UserProfile
|
|
16
|
+
|
|
17
|
+
T = TypeVar('T', bound=BaseModel)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CacheHelper:
|
|
21
|
+
"""Helper class for common cache operations."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, cache_factory):
|
|
24
|
+
"""
|
|
25
|
+
Initialize CacheHelper with cache factory.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
cache_factory: Wappa cache factory instance
|
|
29
|
+
"""
|
|
30
|
+
self.cache_factory = cache_factory
|
|
31
|
+
self._user_cache = None
|
|
32
|
+
self._state_cache = None
|
|
33
|
+
self._table_cache = None
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def user_cache(self):
|
|
37
|
+
"""Get user cache instance."""
|
|
38
|
+
if not self._user_cache:
|
|
39
|
+
self._user_cache = self.cache_factory.create_user_cache()
|
|
40
|
+
return self._user_cache
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def state_cache(self):
|
|
44
|
+
"""Get state cache instance."""
|
|
45
|
+
if not self._state_cache:
|
|
46
|
+
self._state_cache = self.cache_factory.create_state_cache()
|
|
47
|
+
return self._state_cache
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def table_cache(self):
|
|
51
|
+
"""Get table cache instance."""
|
|
52
|
+
if not self._table_cache:
|
|
53
|
+
self._table_cache = self.cache_factory.create_table_cache()
|
|
54
|
+
return self._table_cache
|
|
55
|
+
|
|
56
|
+
async def get_user_profile(self, user_id: str) -> Optional[UserProfile]:
|
|
57
|
+
"""
|
|
58
|
+
Get user profile from cache.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
user_id: User phone number/ID
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
UserProfile object or None if not found
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
profile_data = await self.user_cache.get(f"user_profile_{user_id}", models=UserProfile)
|
|
68
|
+
return profile_data
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"Error getting user profile {user_id}: {e}")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
async def save_user_profile(self, user_profile: UserProfile, ttl_seconds: int = 86400) -> bool:
|
|
74
|
+
"""
|
|
75
|
+
Save user profile to cache.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
user_profile: UserProfile object to save
|
|
79
|
+
ttl_seconds: Time to live in seconds (default 24 hours)
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
True if successful, False otherwise
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
cache_key = f"user_profile_{user_profile.phone_number}"
|
|
86
|
+
await self.user_cache.set(cache_key, user_profile, ttl=ttl_seconds)
|
|
87
|
+
return True
|
|
88
|
+
except Exception as e:
|
|
89
|
+
print(f"Error saving user profile {user_profile.phone_number}: {e}")
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
async def get_or_create_user_profile(self, user_id: str, user_name: str|None = None,
|
|
93
|
+
profile_name: str|None = None) -> UserProfile:
|
|
94
|
+
"""
|
|
95
|
+
Get existing user profile or create a new one.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
user_id: User phone number/ID
|
|
99
|
+
user_name: Optional user name
|
|
100
|
+
profile_name: Optional profile name
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
UserProfile object (existing or new)
|
|
104
|
+
"""
|
|
105
|
+
# Try to get existing profile
|
|
106
|
+
profile = await self.get_user_profile(user_id)
|
|
107
|
+
|
|
108
|
+
if profile:
|
|
109
|
+
# Update profile information if provided
|
|
110
|
+
profile.update_profile_info(user_name, profile_name)
|
|
111
|
+
return profile
|
|
112
|
+
else:
|
|
113
|
+
# Create new profile
|
|
114
|
+
profile = UserProfile(
|
|
115
|
+
phone_number=user_id,
|
|
116
|
+
user_name=user_name,
|
|
117
|
+
profile_name=profile_name,
|
|
118
|
+
is_first_time_user=True,
|
|
119
|
+
has_received_welcome=False
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Save new profile
|
|
123
|
+
await self.save_user_profile(profile)
|
|
124
|
+
return profile
|
|
125
|
+
|
|
126
|
+
async def update_user_activity(self, user_id: str, message_type: str = "text",
|
|
127
|
+
command: str|None = None, interaction_type: str|None = None) -> Optional[UserProfile]:
|
|
128
|
+
"""
|
|
129
|
+
Update user activity statistics.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
user_id: User phone number/ID
|
|
133
|
+
message_type: Type of message
|
|
134
|
+
command: Optional command used
|
|
135
|
+
interaction_type: Optional interaction type
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Updated UserProfile or None if error
|
|
139
|
+
"""
|
|
140
|
+
try:
|
|
141
|
+
profile = await self.get_or_create_user_profile(user_id)
|
|
142
|
+
|
|
143
|
+
# Update message count
|
|
144
|
+
profile.increment_message_count(message_type)
|
|
145
|
+
|
|
146
|
+
# Update command usage
|
|
147
|
+
if command:
|
|
148
|
+
profile.increment_command_usage(command)
|
|
149
|
+
|
|
150
|
+
# Update interactions
|
|
151
|
+
if interaction_type:
|
|
152
|
+
profile.increment_interactions(interaction_type)
|
|
153
|
+
|
|
154
|
+
# Save updated profile
|
|
155
|
+
await self.save_user_profile(profile)
|
|
156
|
+
return profile
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
print(f"Error updating user activity {user_id}: {e}")
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
async def get_user_state(self, user_id: str, state_type: StateType) -> Optional[InteractiveState]:
|
|
163
|
+
"""
|
|
164
|
+
Get user interactive state.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
user_id: User phone number/ID
|
|
168
|
+
state_type: Type of state to get
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
InteractiveState object or None if not found/expired
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
# Use simpler cache key format: state_{state_type}
|
|
175
|
+
cache_key = f"state_{state_type.value}"
|
|
176
|
+
|
|
177
|
+
# Import the specific state classes for proper type casting
|
|
178
|
+
from ..models.state_models import ButtonState, ListState
|
|
179
|
+
|
|
180
|
+
# Use the appropriate model class based on state type
|
|
181
|
+
model_class = InteractiveState
|
|
182
|
+
if state_type == StateType.BUTTON:
|
|
183
|
+
model_class = ButtonState
|
|
184
|
+
elif state_type == StateType.LIST:
|
|
185
|
+
model_class = ListState
|
|
186
|
+
|
|
187
|
+
state_data = await self.state_cache.get(cache_key, models=model_class)
|
|
188
|
+
|
|
189
|
+
if state_data and state_data.is_expired():
|
|
190
|
+
# Remove expired state
|
|
191
|
+
await self.state_cache.delete(cache_key)
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
return state_data
|
|
195
|
+
except Exception as e:
|
|
196
|
+
print(f"Error getting user state {state_type.value}: {e}")
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
async def save_user_state(self, state: InteractiveState) -> bool:
|
|
200
|
+
"""
|
|
201
|
+
Save user interactive state.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
state: InteractiveState object to save
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
True if successful, False otherwise
|
|
208
|
+
"""
|
|
209
|
+
try:
|
|
210
|
+
# Use simpler cache key format: state_{state_type}
|
|
211
|
+
cache_key = f"state_{state.state_type.value}"
|
|
212
|
+
ttl = state.time_remaining_seconds()
|
|
213
|
+
|
|
214
|
+
if ttl <= 0:
|
|
215
|
+
# Don't save expired states
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
await self.state_cache.set(cache_key, state, ttl=ttl)
|
|
219
|
+
return True
|
|
220
|
+
except Exception as e:
|
|
221
|
+
print(f"Error saving user state {state.state_type.value}: {e}")
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
async def remove_user_state(self, user_id: str, state_type: StateType) -> bool:
|
|
225
|
+
"""
|
|
226
|
+
Remove user interactive state.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
user_id: User phone number/ID
|
|
230
|
+
state_type: Type of state to remove
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
True if successful, False otherwise
|
|
234
|
+
"""
|
|
235
|
+
try:
|
|
236
|
+
# Use simpler cache key format: state_{state_type}
|
|
237
|
+
cache_key = f"state_{state_type.value}"
|
|
238
|
+
result = await self.state_cache.delete(cache_key)
|
|
239
|
+
return result
|
|
240
|
+
except Exception as e:
|
|
241
|
+
print(f"Error removing user state {state_type.value}: {e}")
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
async def cleanup_expired_states(self, batch_size: int = 100) -> int:
|
|
245
|
+
"""
|
|
246
|
+
Cleanup expired states from cache.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
batch_size: Number of states to check in each batch
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Number of expired states cleaned up
|
|
253
|
+
"""
|
|
254
|
+
cleanup_count = 0
|
|
255
|
+
try:
|
|
256
|
+
# This is a simplified implementation
|
|
257
|
+
# In a real implementation, you would need to scan Redis keys
|
|
258
|
+
# and check expiration status
|
|
259
|
+
|
|
260
|
+
# For now, return 0 as this requires Redis-specific commands
|
|
261
|
+
return cleanup_count
|
|
262
|
+
except Exception as e:
|
|
263
|
+
print(f"Error during cleanup: {e}")
|
|
264
|
+
return 0
|
|
265
|
+
|
|
266
|
+
async def get_cache_statistics(self) -> Dict[str, Any]:
|
|
267
|
+
"""
|
|
268
|
+
Get cache usage statistics.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Dictionary with cache statistics
|
|
272
|
+
"""
|
|
273
|
+
try:
|
|
274
|
+
stats = {
|
|
275
|
+
"timestamp": datetime.now().isoformat(),
|
|
276
|
+
"user_cache": {
|
|
277
|
+
"type": "user_profiles",
|
|
278
|
+
"description": "User profile and activity data"
|
|
279
|
+
},
|
|
280
|
+
"state_cache": {
|
|
281
|
+
"type": "interactive_states",
|
|
282
|
+
"description": "Active interactive command states"
|
|
283
|
+
},
|
|
284
|
+
"table_cache": {
|
|
285
|
+
"type": "structured_data",
|
|
286
|
+
"description": "Structured application data"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
# Add cache-specific info if available
|
|
291
|
+
try:
|
|
292
|
+
# Try to get cache info from Redis if available
|
|
293
|
+
# This would require implementing Redis-specific info commands
|
|
294
|
+
pass
|
|
295
|
+
except:
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
return stats
|
|
299
|
+
except Exception as e:
|
|
300
|
+
print(f"Error getting cache statistics: {e}")
|
|
301
|
+
return {"error": str(e)}
|
|
302
|
+
|
|
303
|
+
async def store_message_history(self, user_id: str, message_data: Dict[str, Any],
|
|
304
|
+
max_history: int = 50) -> bool:
|
|
305
|
+
"""
|
|
306
|
+
Store message in user's history.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
user_id: User phone number/ID
|
|
310
|
+
message_data: Dictionary with message information
|
|
311
|
+
max_history: Maximum number of messages to keep
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
True if successful, False otherwise
|
|
315
|
+
"""
|
|
316
|
+
try:
|
|
317
|
+
history_key = f"history_{user_id}"
|
|
318
|
+
|
|
319
|
+
# Get existing history
|
|
320
|
+
history = await self.table_cache.get(history_key, models=list) or []
|
|
321
|
+
|
|
322
|
+
# Add new message with timestamp
|
|
323
|
+
message_entry = {
|
|
324
|
+
**message_data,
|
|
325
|
+
"stored_at": datetime.now().isoformat()
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
history.append(message_entry)
|
|
329
|
+
|
|
330
|
+
# Keep only recent messages
|
|
331
|
+
if len(history) > max_history:
|
|
332
|
+
history = history[-max_history:]
|
|
333
|
+
|
|
334
|
+
# Save updated history
|
|
335
|
+
await self.table_cache.set(history_key, history, ttl=604800) # 7 days
|
|
336
|
+
return True
|
|
337
|
+
|
|
338
|
+
except Exception as e:
|
|
339
|
+
print(f"Error storing message history {user_id}: {e}")
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
async def get_message_history(self, user_id: str, limit: int = 20) -> List[Dict[str, Any]]:
|
|
343
|
+
"""
|
|
344
|
+
Get user's message history.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
user_id: User phone number/ID
|
|
348
|
+
limit: Maximum number of messages to return
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
List of message history entries
|
|
352
|
+
"""
|
|
353
|
+
try:
|
|
354
|
+
history_key = f"history_{user_id}"
|
|
355
|
+
history = await self.table_cache.get(history_key, models=list) or []
|
|
356
|
+
|
|
357
|
+
# Return recent messages
|
|
358
|
+
return history[-limit:] if history else []
|
|
359
|
+
|
|
360
|
+
except Exception as e:
|
|
361
|
+
print(f"Error getting message history {user_id}: {e}")
|
|
362
|
+
return []
|
|
363
|
+
|
|
364
|
+
async def store_application_data(self, key: str, data: Any, ttl_seconds: int|None = None) -> bool:
|
|
365
|
+
"""
|
|
366
|
+
Store application-specific data.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
key: Cache key
|
|
370
|
+
data: Data to store
|
|
371
|
+
ttl_seconds: Optional TTL in seconds
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
True if successful, False otherwise
|
|
375
|
+
"""
|
|
376
|
+
try:
|
|
377
|
+
await self.table_cache.set(key, data, ttl=ttl_seconds)
|
|
378
|
+
return True
|
|
379
|
+
except Exception as e:
|
|
380
|
+
print(f"Error storing application data {key}: {e}")
|
|
381
|
+
return False
|
|
382
|
+
|
|
383
|
+
async def get_application_data(self, key: str, model_class: Type[T]|None = None) -> Any:
|
|
384
|
+
"""
|
|
385
|
+
Get application-specific data.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
key: Cache key
|
|
389
|
+
model_class: Optional Pydantic model class for validation
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Stored data or None if not found
|
|
393
|
+
"""
|
|
394
|
+
try:
|
|
395
|
+
return await self.table_cache.get(key, models=model_class)
|
|
396
|
+
except Exception as e:
|
|
397
|
+
print(f"Error getting application data {key}: {e}")
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class CacheKeys:
|
|
402
|
+
"""Centralized cache key management."""
|
|
403
|
+
|
|
404
|
+
@staticmethod
|
|
405
|
+
def user_profile(user_id: str) -> str:
|
|
406
|
+
"""Get cache key for user profile."""
|
|
407
|
+
return f"user_profile_{user_id}"
|
|
408
|
+
|
|
409
|
+
@staticmethod
|
|
410
|
+
def user_state(user_id: str, state_type: str) -> str:
|
|
411
|
+
"""Get cache key for user state."""
|
|
412
|
+
return f"state_{state_type}"
|
|
413
|
+
|
|
414
|
+
@staticmethod
|
|
415
|
+
def message_history(user_id: str) -> str:
|
|
416
|
+
"""Get cache key for message history."""
|
|
417
|
+
return f"history_{user_id}"
|
|
418
|
+
|
|
419
|
+
@staticmethod
|
|
420
|
+
def user_session(user_id: str) -> str:
|
|
421
|
+
"""Get cache key for user session."""
|
|
422
|
+
return f"session_{user_id}"
|
|
423
|
+
|
|
424
|
+
@staticmethod
|
|
425
|
+
def application_stats() -> str:
|
|
426
|
+
"""Get cache key for application statistics."""
|
|
427
|
+
return "app_stats"
|
|
428
|
+
|
|
429
|
+
@staticmethod
|
|
430
|
+
def daily_stats(date_str: str|None = None) -> str:
|
|
431
|
+
"""Get cache key for daily statistics."""
|
|
432
|
+
if not date_str:
|
|
433
|
+
date_str = datetime.now().strftime("%Y-%m-%d")
|
|
434
|
+
return f"daily_stats_{date_str}"
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# Convenience functions for direct use
|
|
438
|
+
async def get_user_from_cache(cache_factory, user_id: str) -> Optional[UserProfile]:
|
|
439
|
+
"""
|
|
440
|
+
Get user profile from cache (convenience function).
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
cache_factory: Wappa cache factory
|
|
444
|
+
user_id: User phone number/ID
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
UserProfile or None
|
|
448
|
+
"""
|
|
449
|
+
helper = CacheHelper(cache_factory)
|
|
450
|
+
return await helper.get_user_profile(user_id)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
async def save_user_to_cache(cache_factory, user_profile: UserProfile) -> bool:
|
|
454
|
+
"""
|
|
455
|
+
Save user profile to cache (convenience function).
|
|
456
|
+
|
|
457
|
+
Args:
|
|
458
|
+
cache_factory: Wappa cache factory
|
|
459
|
+
user_profile: UserProfile to save
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
True if successful, False otherwise
|
|
463
|
+
"""
|
|
464
|
+
helper = CacheHelper(cache_factory)
|
|
465
|
+
return await helper.save_user_profile(user_profile)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
async def update_user_stats(cache_factory, user_id: str, message_type: str = "text",
|
|
469
|
+
command: str|None = None) -> Optional[UserProfile]:
|
|
470
|
+
"""
|
|
471
|
+
Update user statistics (convenience function).
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
cache_factory: Wappa cache factory
|
|
475
|
+
user_id: User phone number/ID
|
|
476
|
+
message_type: Message type
|
|
477
|
+
command: Optional command
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
Updated UserProfile or None
|
|
481
|
+
"""
|
|
482
|
+
helper = CacheHelper(cache_factory)
|
|
483
|
+
return await helper.update_user_activity(user_id, message_type, command)
|