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