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,24 +5,23 @@ Contains models for user profiles, message history, and interaction statistics.
5
5
  """
6
6
 
7
7
  from datetime import datetime
8
- from typing import Dict, List, Optional
9
8
 
10
9
  from pydantic import BaseModel, Field, field_validator
11
10
 
12
11
 
13
12
  class UserProfile(BaseModel):
14
13
  """User profile model for tracking user information and statistics."""
15
-
14
+
16
15
  # Basic user information
17
16
  phone_number: str
18
- user_name: Optional[str] = None
19
- profile_name: Optional[str] = None
20
-
17
+ user_name: str | None = None
18
+ profile_name: str | None = None
19
+
21
20
  # Timestamps
22
21
  first_seen: datetime = Field(default_factory=datetime.now)
23
22
  last_seen: datetime = Field(default_factory=datetime.now)
24
- last_message_timestamp: Optional[datetime] = None
25
-
23
+ last_message_timestamp: datetime | None = None
24
+
26
25
  # Statistics
27
26
  total_messages: int = 0
28
27
  message_count: int = 0 # Alias for compatibility
@@ -31,29 +30,29 @@ class UserProfile(BaseModel):
31
30
  interactive_messages: int = 0
32
31
  location_messages: int = 0
33
32
  contact_messages: int = 0
34
-
33
+
35
34
  # Interaction history
36
35
  total_interactions: int = 0
37
36
  button_clicks: int = 0
38
37
  list_selections: int = 0
39
-
38
+
40
39
  # Special command usage
41
- commands_used: Dict[str, int] = Field(default_factory=dict)
42
-
40
+ commands_used: dict[str, int] = Field(default_factory=dict)
41
+
43
42
  # User preferences
44
43
  preferred_language: str = "en"
45
- timezone: Optional[str] = None
46
-
44
+ timezone: str | None = None
45
+
47
46
  # Flags
48
47
  is_first_time_user: bool = True
49
48
  has_received_welcome: bool = False
50
-
51
- @field_validator('phone_number', mode='before')
49
+
50
+ @field_validator("phone_number", mode="before")
52
51
  @classmethod
53
52
  def validate_phone_number(cls, v):
54
53
  """Convert phone number to string if it's an integer."""
55
54
  return str(v) if v is not None else v
56
-
55
+
57
56
  def increment_message_count(self, message_type: str = "text") -> None:
58
57
  """Increment the message count and update statistics."""
59
58
  self.total_messages += 1
@@ -61,11 +60,18 @@ class UserProfile(BaseModel):
61
60
  self.last_seen = datetime.now()
62
61
  self.last_message_timestamp = datetime.now()
63
62
  self.is_first_time_user = False
64
-
63
+
65
64
  # Update message type counters
66
65
  if message_type in ["text"]:
67
66
  self.text_messages += 1
68
- elif message_type in ["image", "video", "audio", "voice", "document", "sticker"]:
67
+ elif message_type in [
68
+ "image",
69
+ "video",
70
+ "audio",
71
+ "voice",
72
+ "document",
73
+ "sticker",
74
+ ]:
69
75
  self.media_messages += 1
70
76
  elif message_type in ["interactive"]:
71
77
  self.interactive_messages += 1
@@ -73,40 +79,42 @@ class UserProfile(BaseModel):
73
79
  self.location_messages += 1
74
80
  elif message_type in ["contact", "contacts"]:
75
81
  self.contact_messages += 1
76
-
82
+
77
83
  def increment_interactions(self, interaction_type: str = "general") -> None:
78
84
  """Increment total interactions and specific interaction counters."""
79
85
  self.total_interactions += 1
80
86
  self.last_seen = datetime.now()
81
-
87
+
82
88
  if interaction_type == "button":
83
89
  self.button_clicks += 1
84
90
  elif interaction_type == "list":
85
91
  self.list_selections += 1
86
-
92
+
87
93
  def increment_command_usage(self, command: str) -> None:
88
94
  """Increment usage counter for a specific command."""
89
95
  if command not in self.commands_used:
90
96
  self.commands_used[command] = 0
91
97
  self.commands_used[command] += 1
92
98
  self.last_seen = datetime.now()
93
-
94
- def update_profile_info(self, user_name: str|None = None, profile_name: str|None = None) -> None:
99
+
100
+ def update_profile_info(
101
+ self, user_name: str | None = None, profile_name: str | None = None
102
+ ) -> None:
95
103
  """Update user profile information if new data is available."""
96
104
  if user_name and user_name != self.user_name:
97
105
  self.user_name = user_name
98
-
106
+
99
107
  if profile_name and profile_name != self.profile_name:
100
108
  self.profile_name = profile_name
101
-
109
+
102
110
  self.last_seen = datetime.now()
103
-
111
+
104
112
  def mark_welcome_sent(self) -> None:
105
113
  """Mark that the welcome message has been sent to this user."""
106
114
  self.has_received_welcome = True
107
115
  self.is_first_time_user = False
108
116
  self.last_seen = datetime.now()
109
-
117
+
110
118
  def get_display_name(self) -> str:
111
119
  """Get the best available display name for this user."""
112
120
  if self.user_name:
@@ -115,8 +123,8 @@ class UserProfile(BaseModel):
115
123
  return self.profile_name
116
124
  else:
117
125
  return self.phone_number
118
-
119
- def get_activity_summary(self) -> Dict[str, any]:
126
+
127
+ def get_activity_summary(self) -> dict[str, any]:
120
128
  """Get a summary of user activity."""
121
129
  return {
122
130
  "user_id": self.phone_number,
@@ -127,75 +135,79 @@ class UserProfile(BaseModel):
127
135
  "media": self.media_messages,
128
136
  "interactive": self.interactive_messages,
129
137
  "location": self.location_messages,
130
- "contact": self.contact_messages
138
+ "contact": self.contact_messages,
131
139
  },
132
140
  "interactions": {
133
141
  "total": self.total_interactions,
134
142
  "buttons": self.button_clicks,
135
- "lists": self.list_selections
143
+ "lists": self.list_selections,
136
144
  },
137
145
  "commands_used": self.commands_used,
138
146
  "first_seen": self.first_seen.isoformat(),
139
147
  "last_seen": self.last_seen.isoformat(),
140
148
  "is_active_user": self.total_messages >= 5,
141
- "is_new_user": self.is_first_time_user or self.total_messages <= 3
149
+ "is_new_user": self.is_first_time_user or self.total_messages <= 3,
142
150
  }
143
151
 
144
152
 
145
153
  class UserSession(BaseModel):
146
154
  """User session model for tracking current conversation context."""
147
-
155
+
148
156
  user_id: str
149
- session_id: str = Field(default_factory=lambda: f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
150
-
157
+ session_id: str = Field(
158
+ default_factory=lambda: f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
159
+ )
160
+
151
161
  # Session timestamps
152
162
  session_start: datetime = Field(default_factory=datetime.now)
153
163
  last_activity: datetime = Field(default_factory=datetime.now)
154
-
164
+
155
165
  # Session statistics
156
166
  messages_in_session: int = 0
157
- commands_in_session: List[str] = Field(default_factory=list)
167
+ commands_in_session: list[str] = Field(default_factory=list)
158
168
  interactions_in_session: int = 0
159
-
169
+
160
170
  # Current context
161
- last_message_type: Optional[str] = None
162
- last_command_used: Optional[str] = None
163
- current_state: Optional[str] = None # For tracking active interactive states
164
-
171
+ last_message_type: str | None = None
172
+ last_command_used: str | None = None
173
+ current_state: str | None = None # For tracking active interactive states
174
+
165
175
  # Session metadata
166
- user_agent: Optional[str] = None
167
- platform_version: Optional[str] = None
168
-
169
- def update_activity(self, message_type: str = None, command: str = None, interaction: bool = False) -> None:
176
+ user_agent: str | None = None
177
+ platform_version: str | None = None
178
+
179
+ def update_activity(
180
+ self, message_type: str = None, command: str = None, interaction: bool = False
181
+ ) -> None:
170
182
  """Update session activity."""
171
183
  self.last_activity = datetime.now()
172
184
  self.messages_in_session += 1
173
-
185
+
174
186
  if message_type:
175
187
  self.last_message_type = message_type
176
-
188
+
177
189
  if command:
178
190
  self.last_command_used = command
179
191
  self.commands_in_session.append(command)
180
-
192
+
181
193
  if interaction:
182
194
  self.interactions_in_session += 1
183
-
195
+
184
196
  def set_current_state(self, state: str = None) -> None:
185
197
  """Set the current interactive state."""
186
198
  self.current_state = state
187
199
  self.last_activity = datetime.now()
188
-
200
+
189
201
  def is_session_active(self, timeout_minutes: int = 30) -> bool:
190
202
  """Check if the session is still active based on last activity."""
191
203
  time_diff = datetime.now() - self.last_activity
192
204
  return time_diff.total_seconds() < (timeout_minutes * 60)
193
-
205
+
194
206
  def get_session_duration_seconds(self) -> int:
195
207
  """Get the current session duration in seconds."""
196
208
  return int((self.last_activity - self.session_start).total_seconds())
197
-
198
- def get_session_summary(self) -> Dict[str, any]:
209
+
210
+ def get_session_summary(self) -> dict[str, any]:
199
211
  """Get a summary of the current session."""
200
212
  return {
201
213
  "session_id": self.session_id,
@@ -209,64 +221,64 @@ class UserSession(BaseModel):
209
221
  "current_state": self.current_state,
210
222
  "is_active": self.is_session_active(),
211
223
  "session_start": self.session_start.isoformat(),
212
- "last_activity": self.last_activity.isoformat()
224
+ "last_activity": self.last_activity.isoformat(),
213
225
  }
214
226
 
215
227
 
216
228
  class UserStatistics(BaseModel):
217
229
  """Aggregate statistics for all users."""
218
-
230
+
219
231
  total_users: int = 0
220
232
  active_users_today: int = 0
221
233
  active_users_week: int = 0
222
234
  new_users_today: int = 0
223
-
235
+
224
236
  total_messages: int = 0
225
237
  messages_today: int = 0
226
-
238
+
227
239
  # Message type distribution
228
- message_type_stats: Dict[str, int] = Field(default_factory=dict)
229
-
240
+ message_type_stats: dict[str, int] = Field(default_factory=dict)
241
+
230
242
  # Command usage statistics
231
- command_usage_stats: Dict[str, int] = Field(default_factory=dict)
232
-
243
+ command_usage_stats: dict[str, int] = Field(default_factory=dict)
244
+
233
245
  # Interactive feature usage
234
246
  button_usage: int = 0
235
247
  list_usage: int = 0
236
-
248
+
237
249
  # Timestamps
238
250
  last_updated: datetime = Field(default_factory=datetime.now)
239
-
251
+
240
252
  def update_stats(self, user_profile: UserProfile) -> None:
241
253
  """Update statistics with data from a user profile."""
242
254
  self.total_users += 1 if user_profile.is_first_time_user else 0
243
255
  self.total_messages += user_profile.total_messages
244
-
256
+
245
257
  # Update message type stats
246
258
  for msg_type, count in {
247
259
  "text": user_profile.text_messages,
248
260
  "media": user_profile.media_messages,
249
261
  "interactive": user_profile.interactive_messages,
250
262
  "location": user_profile.location_messages,
251
- "contact": user_profile.contact_messages
263
+ "contact": user_profile.contact_messages,
252
264
  }.items():
253
265
  if msg_type not in self.message_type_stats:
254
266
  self.message_type_stats[msg_type] = 0
255
267
  self.message_type_stats[msg_type] += count
256
-
268
+
257
269
  # Update command usage stats
258
270
  for command, count in user_profile.commands_used.items():
259
271
  if command not in self.command_usage_stats:
260
272
  self.command_usage_stats[command] = 0
261
273
  self.command_usage_stats[command] += count
262
-
274
+
263
275
  # Update interaction stats
264
276
  self.button_usage += user_profile.button_clicks
265
277
  self.list_usage += user_profile.list_selections
266
-
278
+
267
279
  self.last_updated = datetime.now()
268
-
269
- def get_summary(self) -> Dict[str, any]:
280
+
281
+ def get_summary(self) -> dict[str, any]:
270
282
  """Get a comprehensive statistics summary."""
271
283
  return {
272
284
  "overview": {
@@ -274,14 +286,18 @@ class UserStatistics(BaseModel):
274
286
  "active_users_today": self.active_users_today,
275
287
  "new_users_today": self.new_users_today,
276
288
  "total_messages": self.total_messages,
277
- "messages_today": self.messages_today
289
+ "messages_today": self.messages_today,
278
290
  },
279
291
  "message_distribution": self.message_type_stats,
280
- "popular_commands": dict(sorted(self.command_usage_stats.items(), key=lambda x: x[1], reverse=True)),
292
+ "popular_commands": dict(
293
+ sorted(
294
+ self.command_usage_stats.items(), key=lambda x: x[1], reverse=True
295
+ )
296
+ ),
281
297
  "interactive_usage": {
282
298
  "button_clicks": self.button_usage,
283
299
  "list_selections": self.list_usage,
284
- "total_interactions": self.button_usage + self.list_usage
300
+ "total_interactions": self.button_usage + self.list_usage,
285
301
  },
286
- "last_updated": self.last_updated.isoformat()
287
- }
302
+ "last_updated": self.last_updated.isoformat(),
303
+ }