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.

Files changed (126) hide show
  1. wappa/__init__.py +4 -5
  2. wappa/api/controllers/webhook_controller.py +5 -2
  3. wappa/api/dependencies/__init__.py +0 -5
  4. wappa/api/middleware/error_handler.py +4 -4
  5. wappa/api/middleware/owner.py +11 -5
  6. wappa/api/routes/webhooks.py +2 -2
  7. wappa/cli/__init__.py +1 -1
  8. wappa/cli/examples/init/app/main.py +2 -1
  9. wappa/cli/examples/init/app/master_event.py +5 -3
  10. wappa/cli/examples/json_cache_example/app/__init__.py +1 -1
  11. wappa/cli/examples/json_cache_example/app/main.py +56 -44
  12. wappa/cli/examples/json_cache_example/app/master_event.py +181 -145
  13. wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -1
  14. wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +32 -51
  15. wappa/cli/examples/json_cache_example/app/scores/__init__.py +2 -2
  16. wappa/cli/examples/json_cache_example/app/scores/score_base.py +52 -46
  17. wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +70 -62
  18. wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +41 -44
  19. wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +83 -71
  20. wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +73 -57
  21. wappa/cli/examples/json_cache_example/app/utils/__init__.py +2 -2
  22. wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +54 -56
  23. wappa/cli/examples/json_cache_example/app/utils/message_utils.py +85 -80
  24. wappa/cli/examples/openai_transcript/app/main.py +2 -1
  25. wappa/cli/examples/openai_transcript/app/master_event.py +31 -22
  26. wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +1 -1
  27. wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +37 -24
  28. wappa/cli/examples/redis_cache_example/app/__init__.py +1 -1
  29. wappa/cli/examples/redis_cache_example/app/main.py +56 -44
  30. wappa/cli/examples/redis_cache_example/app/master_event.py +181 -145
  31. wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +31 -50
  32. wappa/cli/examples/redis_cache_example/app/scores/__init__.py +2 -2
  33. wappa/cli/examples/redis_cache_example/app/scores/score_base.py +52 -46
  34. wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +70 -62
  35. wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +41 -44
  36. wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +83 -71
  37. wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +73 -57
  38. wappa/cli/examples/redis_cache_example/app/utils/__init__.py +2 -2
  39. wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +54 -56
  40. wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +85 -80
  41. wappa/cli/examples/simple_echo_example/app/__init__.py +1 -1
  42. wappa/cli/examples/simple_echo_example/app/main.py +41 -33
  43. wappa/cli/examples/simple_echo_example/app/master_event.py +78 -57
  44. wappa/cli/examples/wappa_full_example/app/__init__.py +1 -1
  45. wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +1 -1
  46. wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +134 -126
  47. wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +237 -229
  48. wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +170 -148
  49. wappa/cli/examples/wappa_full_example/app/main.py +51 -39
  50. wappa/cli/examples/wappa_full_example/app/master_event.py +179 -120
  51. wappa/cli/examples/wappa_full_example/app/models/__init__.py +1 -1
  52. wappa/cli/examples/wappa_full_example/app/models/state_models.py +113 -104
  53. wappa/cli/examples/wappa_full_example/app/models/user_models.py +92 -76
  54. wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +109 -83
  55. wappa/cli/examples/wappa_full_example/app/utils/__init__.py +1 -1
  56. wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +132 -113
  57. wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +175 -132
  58. wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +126 -87
  59. wappa/cli/main.py +9 -4
  60. wappa/core/__init__.py +18 -23
  61. wappa/core/config/settings.py +7 -5
  62. wappa/core/events/default_handlers.py +1 -1
  63. wappa/core/factory/wappa_builder.py +38 -25
  64. wappa/core/plugins/redis_plugin.py +1 -3
  65. wappa/core/plugins/wappa_core_plugin.py +7 -6
  66. wappa/core/types.py +12 -12
  67. wappa/core/wappa_app.py +10 -8
  68. wappa/database/__init__.py +3 -4
  69. wappa/domain/enums/messenger_platform.py +1 -2
  70. wappa/domain/factories/media_factory.py +5 -20
  71. wappa/domain/factories/message_factory.py +5 -20
  72. wappa/domain/factories/messenger_factory.py +2 -4
  73. wappa/domain/interfaces/cache_interface.py +7 -7
  74. wappa/domain/interfaces/media_interface.py +2 -5
  75. wappa/domain/models/media_result.py +1 -3
  76. wappa/domain/models/platforms/platform_config.py +1 -3
  77. wappa/messaging/__init__.py +9 -12
  78. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
  79. wappa/models/__init__.py +27 -35
  80. wappa/persistence/__init__.py +12 -15
  81. wappa/persistence/cache_factory.py +0 -1
  82. wappa/persistence/json/__init__.py +1 -1
  83. wappa/persistence/json/cache_adapters.py +37 -25
  84. wappa/persistence/json/handlers/state_handler.py +60 -52
  85. wappa/persistence/json/handlers/table_handler.py +51 -49
  86. wappa/persistence/json/handlers/user_handler.py +71 -55
  87. wappa/persistence/json/handlers/utils/file_manager.py +42 -39
  88. wappa/persistence/json/handlers/utils/key_factory.py +1 -1
  89. wappa/persistence/json/handlers/utils/serialization.py +13 -11
  90. wappa/persistence/json/json_cache_factory.py +4 -8
  91. wappa/persistence/json/storage_manager.py +66 -79
  92. wappa/persistence/memory/__init__.py +1 -1
  93. wappa/persistence/memory/cache_adapters.py +37 -25
  94. wappa/persistence/memory/handlers/state_handler.py +62 -52
  95. wappa/persistence/memory/handlers/table_handler.py +59 -53
  96. wappa/persistence/memory/handlers/user_handler.py +75 -55
  97. wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
  98. wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
  99. wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
  100. wappa/persistence/memory/memory_cache_factory.py +3 -7
  101. wappa/persistence/memory/storage_manager.py +52 -62
  102. wappa/persistence/redis/cache_adapters.py +27 -21
  103. wappa/persistence/redis/ops.py +11 -11
  104. wappa/persistence/redis/redis_client.py +4 -6
  105. wappa/persistence/redis/redis_manager.py +12 -4
  106. wappa/processors/factory.py +5 -5
  107. wappa/schemas/factory.py +2 -5
  108. wappa/schemas/whatsapp/message_types/errors.py +3 -12
  109. wappa/schemas/whatsapp/validators.py +3 -3
  110. wappa/webhooks/__init__.py +17 -18
  111. wappa/webhooks/factory.py +3 -5
  112. wappa/webhooks/whatsapp/__init__.py +10 -13
  113. wappa/webhooks/whatsapp/message_types/audio.py +0 -4
  114. wappa/webhooks/whatsapp/message_types/document.py +1 -9
  115. wappa/webhooks/whatsapp/message_types/errors.py +3 -12
  116. wappa/webhooks/whatsapp/message_types/location.py +1 -21
  117. wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
  118. wappa/webhooks/whatsapp/message_types/text.py +0 -6
  119. wappa/webhooks/whatsapp/message_types/video.py +1 -20
  120. wappa/webhooks/whatsapp/status_models.py +2 -2
  121. wappa/webhooks/whatsapp/validators.py +3 -3
  122. {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
  123. {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/RECORD +126 -126
  124. {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
  125. {wappa-0.1.9.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
  126. {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 json
9
- from datetime import datetime, timedelta
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('T', bound=BaseModel)
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) -> Optional[UserProfile]:
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(f"user_profile_{user_id}", models=UserProfile)
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(self, user_profile: UserProfile, ttl_seconds: int = 86400) -> bool:
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(self, user_id: str, user_name: str|None = None,
93
- profile_name: str|None = None) -> UserProfile:
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(self, user_id: str, message_type: str = "text",
127
- command: str|None = None, interaction_type: str|None = None) -> Optional[UserProfile]:
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(self, user_id: str, state_type: StateType) -> Optional[InteractiveState]:
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) -> Dict[str, Any]:
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(self, user_id: str, message_data: Dict[str, Any],
304
- max_history: int = 50) -> bool:
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
- **message_data,
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(self, user_id: str, limit: int = 20) -> List[Dict[str, Any]]:
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(self, key: str, data: Any, ttl_seconds: int|None = None) -> bool:
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(self, key: str, model_class: Type[T]|None = None) -> Any:
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) -> Optional[UserProfile]:
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(cache_factory, user_id: str, message_type: str = "text",
469
- command: str|None = None) -> Optional[UserProfile]:
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)