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
@@ -6,7 +6,6 @@ IncomingMessageWebhook objects based on message type.
6
6
  """
7
7
 
8
8
  import time
9
- from typing import Optional
10
9
 
11
10
  from wappa.webhooks import IncomingMessageWebhook
12
11
 
@@ -24,16 +23,18 @@ from ..models.webhook_metadata import (
24
23
 
25
24
  class MetadataExtractor:
26
25
  """Utility class for extracting metadata from webhooks."""
27
-
26
+
28
27
  @staticmethod
29
- def extract_metadata(webhook: IncomingMessageWebhook, start_time: float = None) -> WebhookMetadata:
28
+ def extract_metadata(
29
+ webhook: IncomingMessageWebhook, start_time: float = None
30
+ ) -> WebhookMetadata:
30
31
  """
31
32
  Extract appropriate metadata from webhook based on message type.
32
-
33
+
33
34
  Args:
34
35
  webhook: IncomingMessageWebhook to extract metadata from
35
36
  start_time: Optional start time for processing time calculation
36
-
37
+
37
38
  Returns:
38
39
  WebhookMetadata object with extracted information
39
40
  """
@@ -41,70 +42,84 @@ class MetadataExtractor:
41
42
  processing_time_ms = None
42
43
  if start_time is not None:
43
44
  processing_time_ms = int((time.time() - start_time) * 1000)
44
-
45
+
45
46
  # Get message type from webhook
46
47
  message_type_name = webhook.get_message_type_name().lower()
47
-
48
+
48
49
  try:
49
50
  # Map message type string to enum
50
51
  message_type = MessageType(message_type_name)
51
52
  except ValueError:
52
53
  # Handle unknown message types
53
54
  return UnknownMessageMetadata.from_webhook(webhook, processing_time_ms)
54
-
55
+
55
56
  # Extract metadata based on message type
56
57
  if message_type == MessageType.TEXT:
57
58
  return TextMessageMetadata.from_webhook(webhook, processing_time_ms)
58
-
59
- elif message_type in [MessageType.IMAGE, MessageType.VIDEO, MessageType.AUDIO,
60
- MessageType.VOICE, MessageType.DOCUMENT, MessageType.STICKER]:
61
- return MediaMessageMetadata.from_webhook(webhook, message_type, processing_time_ms)
62
-
59
+
60
+ elif message_type in [
61
+ MessageType.IMAGE,
62
+ MessageType.VIDEO,
63
+ MessageType.AUDIO,
64
+ MessageType.VOICE,
65
+ MessageType.DOCUMENT,
66
+ MessageType.STICKER,
67
+ ]:
68
+ return MediaMessageMetadata.from_webhook(
69
+ webhook, message_type, processing_time_ms
70
+ )
71
+
63
72
  elif message_type == MessageType.LOCATION:
64
73
  return LocationMessageMetadata.from_webhook(webhook, processing_time_ms)
65
-
74
+
66
75
  elif message_type in [MessageType.CONTACT, MessageType.CONTACTS]:
67
76
  return ContactMessageMetadata.from_webhook(webhook, processing_time_ms)
68
-
77
+
69
78
  elif message_type == MessageType.INTERACTIVE:
70
79
  return InteractiveMessageMetadata.from_webhook(webhook, processing_time_ms)
71
-
80
+
72
81
  else:
73
82
  # Fallback for unsupported types
74
83
  return UnknownMessageMetadata.from_webhook(webhook, processing_time_ms)
75
-
84
+
76
85
  @staticmethod
77
86
  def format_metadata_for_user(metadata: WebhookMetadata) -> str:
78
87
  """
79
88
  Format metadata for display to user.
80
-
89
+
81
90
  Args:
82
91
  metadata: WebhookMetadata object to format
83
-
92
+
84
93
  Returns:
85
94
  Formatted string for user display
86
95
  """
87
96
  lines = []
88
97
  lines.append("📊 *Message Metadata*")
89
98
  lines.append("─" * 30)
90
-
99
+
91
100
  # Basic information
92
101
  lines.append(f"🆔 Message ID: `{metadata.message_id[:20]}...`")
93
102
  # Handle both enum and string values for message_type
94
- message_type_str = metadata.message_type.value if hasattr(metadata.message_type, 'value') else str(metadata.message_type)
103
+ message_type_str = (
104
+ metadata.message_type.value
105
+ if hasattr(metadata.message_type, "value")
106
+ else str(metadata.message_type)
107
+ )
95
108
  lines.append(f"📱 Message Type: `{message_type_str}`")
96
109
  lines.append(f"👤 User: {metadata.user_name or metadata.user_id}")
97
- lines.append(f"🕐 Timestamp: {metadata.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
110
+ lines.append(
111
+ f"🕐 Timestamp: {metadata.timestamp.strftime('%Y-%m-%d %H:%M:%S')}"
112
+ )
98
113
  lines.append(f"🏢 Tenant: `{metadata.tenant_id}`")
99
114
  lines.append(f"🌐 Platform: `{metadata.platform}`")
100
-
115
+
101
116
  # Processing metadata
102
117
  if metadata.processing_time_ms:
103
118
  lines.append(f"⚡ Processing Time: {metadata.processing_time_ms}ms")
104
-
119
+
105
120
  if metadata.cache_hit:
106
121
  lines.append("💾 Cache: Hit ✅")
107
-
122
+
108
123
  # Type-specific metadata
109
124
  if isinstance(metadata, TextMessageMetadata):
110
125
  lines.append("")
@@ -116,7 +131,7 @@ class MetadataExtractor:
116
131
  lines.append("👥 Contains Mentions: Yes")
117
132
  if metadata.is_forwarded:
118
133
  lines.append("↪️ Forwarded: Yes")
119
-
134
+
120
135
  elif isinstance(metadata, MediaMessageMetadata):
121
136
  lines.append("")
122
137
  lines.append(f"🎬 *{metadata.media_type.title()} Media Details:*")
@@ -124,9 +139,13 @@ class MetadataExtractor:
124
139
  if metadata.mime_type:
125
140
  lines.append(f"📄 MIME Type: `{metadata.mime_type}`")
126
141
  if metadata.file_size:
127
- lines.append(f"📦 File Size: {MetadataExtractor._format_file_size(metadata.file_size)}")
142
+ lines.append(
143
+ f"📦 File Size: {MetadataExtractor._format_file_size(metadata.file_size)}"
144
+ )
128
145
  if metadata.caption:
129
- lines.append(f"💬 Caption: {metadata.caption[:50]}{'...' if len(metadata.caption) > 50 else ''}")
146
+ lines.append(
147
+ f"💬 Caption: {metadata.caption[:50]}{'...' if len(metadata.caption) > 50 else ''}"
148
+ )
130
149
  if metadata.width and metadata.height:
131
150
  lines.append(f"📐 Dimensions: {metadata.width}x{metadata.height}")
132
151
  if metadata.duration:
@@ -135,7 +154,7 @@ class MetadataExtractor:
135
154
  lines.append(f"📎 Filename: `{metadata.filename}`")
136
155
  if metadata.is_forwarded:
137
156
  lines.append("↪️ Forwarded: Yes")
138
-
157
+
139
158
  elif isinstance(metadata, LocationMessageMetadata):
140
159
  lines.append("")
141
160
  lines.append("📍 *Location Details:*")
@@ -146,20 +165,22 @@ class MetadataExtractor:
146
165
  lines.append(f"🏠 Address: {metadata.location_address}")
147
166
  if metadata.is_forwarded:
148
167
  lines.append("↪️ Forwarded: Yes")
149
-
168
+
150
169
  elif isinstance(metadata, ContactMessageMetadata):
151
170
  lines.append("")
152
171
  lines.append("👥 *Contact Details:*")
153
172
  lines.append(f"📇 Contact Count: {metadata.contacts_count}")
154
173
  if metadata.contact_names:
155
- lines.append(f"👤 Names: {', '.join(metadata.contact_names[:3])}{'...' if len(metadata.contact_names) > 3 else ''}")
174
+ lines.append(
175
+ f"👤 Names: {', '.join(metadata.contact_names[:3])}{'...' if len(metadata.contact_names) > 3 else ''}"
176
+ )
156
177
  if metadata.has_phone_numbers:
157
178
  lines.append("📞 Has Phone Numbers: Yes")
158
179
  if metadata.has_emails:
159
180
  lines.append("✉️ Has Emails: Yes")
160
181
  if metadata.is_forwarded:
161
182
  lines.append("↪️ Forwarded: Yes")
162
-
183
+
163
184
  elif isinstance(metadata, InteractiveMessageMetadata):
164
185
  lines.append("")
165
186
  lines.append("🔘 *Interactive Details:*")
@@ -168,82 +189,98 @@ class MetadataExtractor:
168
189
  if metadata.selection_title:
169
190
  lines.append(f"🏷️ Selection: {metadata.selection_title}")
170
191
  if metadata.context_message_id:
171
- lines.append(f"💬 Context Message: `{metadata.context_message_id[:20]}...`")
172
-
192
+ lines.append(
193
+ f"💬 Context Message: `{metadata.context_message_id[:20]}...`"
194
+ )
195
+
173
196
  elif isinstance(metadata, UnknownMessageMetadata):
174
197
  lines.append("")
175
198
  lines.append("❓ *Unknown Message Type*")
176
199
  lines.append("🔍 Raw data captured for debugging")
177
-
200
+
178
201
  return "\n".join(lines)
179
-
202
+
180
203
  @staticmethod
181
204
  def get_metadata_summary(metadata: WebhookMetadata) -> dict:
182
205
  """
183
206
  Get a summary of metadata for logging/analytics.
184
-
207
+
185
208
  Args:
186
209
  metadata: WebhookMetadata object to summarize
187
-
210
+
188
211
  Returns:
189
212
  Dictionary with metadata summary
190
213
  """
191
214
  # Handle both enum and string values for message_type
192
- message_type_value = metadata.message_type.value if hasattr(metadata.message_type, 'value') else str(metadata.message_type)
193
-
215
+ message_type_value = (
216
+ metadata.message_type.value
217
+ if hasattr(metadata.message_type, "value")
218
+ else str(metadata.message_type)
219
+ )
220
+
194
221
  summary = {
195
222
  "message_id": metadata.message_id,
196
223
  "message_type": message_type_value,
197
224
  "user_id": metadata.user_id,
198
225
  "timestamp": metadata.timestamp.isoformat(),
199
226
  "processing_time_ms": metadata.processing_time_ms,
200
- "cache_hit": metadata.cache_hit
227
+ "cache_hit": metadata.cache_hit,
201
228
  }
202
-
229
+
203
230
  # Add type-specific summary data
204
231
  if isinstance(metadata, TextMessageMetadata):
205
- summary.update({
206
- "text_length": metadata.text_length,
207
- "has_urls": metadata.has_urls,
208
- "has_mentions": metadata.has_mentions,
209
- "is_forwarded": metadata.is_forwarded
210
- })
211
-
232
+ summary.update(
233
+ {
234
+ "text_length": metadata.text_length,
235
+ "has_urls": metadata.has_urls,
236
+ "has_mentions": metadata.has_mentions,
237
+ "is_forwarded": metadata.is_forwarded,
238
+ }
239
+ )
240
+
212
241
  elif isinstance(metadata, MediaMessageMetadata):
213
- summary.update({
214
- "media_id": metadata.media_id,
215
- "media_type": metadata.media_type,
216
- "file_size": metadata.file_size,
217
- "has_caption": bool(metadata.caption),
218
- "is_forwarded": metadata.is_forwarded
219
- })
220
-
242
+ summary.update(
243
+ {
244
+ "media_id": metadata.media_id,
245
+ "media_type": metadata.media_type,
246
+ "file_size": metadata.file_size,
247
+ "has_caption": bool(metadata.caption),
248
+ "is_forwarded": metadata.is_forwarded,
249
+ }
250
+ )
251
+
221
252
  elif isinstance(metadata, LocationMessageMetadata):
222
- summary.update({
223
- "latitude": metadata.latitude,
224
- "longitude": metadata.longitude,
225
- "has_name": bool(metadata.location_name),
226
- "has_address": bool(metadata.location_address),
227
- "is_forwarded": metadata.is_forwarded
228
- })
229
-
253
+ summary.update(
254
+ {
255
+ "latitude": metadata.latitude,
256
+ "longitude": metadata.longitude,
257
+ "has_name": bool(metadata.location_name),
258
+ "has_address": bool(metadata.location_address),
259
+ "is_forwarded": metadata.is_forwarded,
260
+ }
261
+ )
262
+
230
263
  elif isinstance(metadata, ContactMessageMetadata):
231
- summary.update({
232
- "contacts_count": metadata.contacts_count,
233
- "has_phone_numbers": metadata.has_phone_numbers,
234
- "has_emails": metadata.has_emails,
235
- "is_forwarded": metadata.is_forwarded
236
- })
237
-
264
+ summary.update(
265
+ {
266
+ "contacts_count": metadata.contacts_count,
267
+ "has_phone_numbers": metadata.has_phone_numbers,
268
+ "has_emails": metadata.has_emails,
269
+ "is_forwarded": metadata.is_forwarded,
270
+ }
271
+ )
272
+
238
273
  elif isinstance(metadata, InteractiveMessageMetadata):
239
- summary.update({
240
- "interaction_type": metadata.interaction_type,
241
- "selection_id": metadata.selection_id,
242
- "has_title": bool(metadata.selection_title)
243
- })
244
-
274
+ summary.update(
275
+ {
276
+ "interaction_type": metadata.interaction_type,
277
+ "selection_id": metadata.selection_id,
278
+ "has_title": bool(metadata.selection_title),
279
+ }
280
+ )
281
+
245
282
  return summary
246
-
283
+
247
284
  @staticmethod
248
285
  def _format_file_size(size_bytes: int) -> str:
249
286
  """Format file size in human-readable format."""
@@ -258,14 +295,16 @@ class MetadataExtractor:
258
295
 
259
296
 
260
297
  # Convenience functions for direct use
261
- def extract_webhook_metadata(webhook: IncomingMessageWebhook, start_time: float = None) -> WebhookMetadata:
298
+ def extract_webhook_metadata(
299
+ webhook: IncomingMessageWebhook, start_time: float = None
300
+ ) -> WebhookMetadata:
262
301
  """
263
302
  Extract metadata from webhook (convenience function).
264
-
303
+
265
304
  Args:
266
305
  webhook: IncomingMessageWebhook to process
267
306
  start_time: Optional start time for processing measurement
268
-
307
+
269
308
  Returns:
270
309
  WebhookMetadata object
271
310
  """
@@ -275,10 +314,10 @@ def extract_webhook_metadata(webhook: IncomingMessageWebhook, start_time: float
275
314
  def format_metadata_message(metadata: WebhookMetadata) -> str:
276
315
  """
277
316
  Format metadata for user display (convenience function).
278
-
317
+
279
318
  Args:
280
319
  metadata: WebhookMetadata to format
281
-
320
+
282
321
  Returns:
283
322
  Formatted string for user
284
323
  """
@@ -288,11 +327,11 @@ def format_metadata_message(metadata: WebhookMetadata) -> str:
288
327
  def get_processing_summary(metadata: WebhookMetadata) -> dict:
289
328
  """
290
329
  Get processing summary (convenience function).
291
-
330
+
292
331
  Args:
293
332
  metadata: WebhookMetadata to summarize
294
-
333
+
295
334
  Returns:
296
335
  Summary dictionary
297
336
  """
298
- return MetadataExtractor.get_metadata_summary(metadata)
337
+ return MetadataExtractor.get_metadata_summary(metadata)
wappa/cli/main.py CHANGED
@@ -398,7 +398,7 @@ def _show_examples_menu(target_directory: str) -> None:
398
398
  table.add_column("Key Features", style="yellow")
399
399
 
400
400
  example_keys = list(EXAMPLES.keys())
401
- for i, (key, example) in enumerate(EXAMPLES.items(), 1):
401
+ for i, (_key, example) in enumerate(EXAMPLES.items(), 1):
402
402
  features_text = ", ".join(example["features"][:3]) # Show first 3 features
403
403
  if len(example["features"]) > 3:
404
404
  features_text += "..."
@@ -496,14 +496,19 @@ def _copy_example(example_key: str, target_directory: str) -> None:
496
496
  # Copy all files from the example (including hidden files, excluding .git and __pycache__)
497
497
  for item in source_path.iterdir():
498
498
  # Skip .git and __pycache__ directories
499
- if item.name in {'.git', '__pycache__'}:
499
+ if item.name in {".git", "__pycache__"}:
500
500
  continue
501
-
501
+
502
502
  if item.is_file():
503
503
  shutil.copy2(item, target_path / item.name)
504
504
  console.print(f"📝 Copied: {item.name}")
505
505
  elif item.is_dir():
506
- shutil.copytree(item, target_path / item.name, dirs_exist_ok=True, ignore=shutil.ignore_patterns('__pycache__', '*.pyc'))
506
+ shutil.copytree(
507
+ item,
508
+ target_path / item.name,
509
+ dirs_exist_ok=True,
510
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
511
+ )
507
512
  console.print(f"📁 Copied: {item.name}/")
508
513
 
509
514
  console.print("\n✅ Example copied successfully!")
wappa/core/__init__.py CHANGED
@@ -10,37 +10,37 @@ Clean Architecture: Core application logic and framework components.
10
10
  # Configuration & Settings
11
11
  from .config.settings import settings
12
12
 
13
- # Logging System
14
- from .logging import get_logger, get_app_logger, setup_app_logging
15
-
16
13
  # Event System
17
14
  from .events import (
18
- WappaEventHandler,
19
- WappaEventDispatcher,
20
- DefaultMessageHandler,
21
- DefaultStatusHandler,
22
15
  DefaultErrorHandler,
23
16
  DefaultHandlerFactory,
24
- MessageLogStrategy,
25
- StatusLogStrategy,
17
+ DefaultMessageHandler,
18
+ DefaultStatusHandler,
26
19
  ErrorLogStrategy,
27
- WebhookURLFactory,
20
+ MessageLogStrategy,
21
+ StatusLogStrategy,
22
+ WappaEventDispatcher,
23
+ WappaEventHandler,
28
24
  WebhookEndpointType,
25
+ WebhookURLFactory,
29
26
  webhook_url_factory,
30
27
  )
31
28
 
32
29
  # Factory System
33
30
  from .factory import WappaBuilder, WappaPlugin
34
31
 
32
+ # Logging System
33
+ from .logging import get_app_logger, get_logger, setup_app_logging
34
+
35
35
  # Plugin System
36
36
  from .plugins import (
37
- WappaCorePlugin,
38
37
  AuthPlugin,
39
38
  CORSPlugin,
39
+ CustomMiddlewarePlugin,
40
40
  DatabasePlugin,
41
- RedisPlugin,
42
41
  RateLimitPlugin,
43
- CustomMiddlewarePlugin,
42
+ RedisPlugin,
43
+ WappaCorePlugin,
44
44
  WebhookPlugin,
45
45
  )
46
46
 
@@ -50,16 +50,14 @@ from .wappa_app import Wappa
50
50
  __all__ = [
51
51
  # Configuration
52
52
  "settings",
53
-
54
53
  # Logging
55
- "get_logger",
54
+ "get_logger",
56
55
  "get_app_logger",
57
56
  "setup_app_logging",
58
-
59
57
  # Event System
60
58
  "WappaEventHandler",
61
59
  "WappaEventDispatcher",
62
- "DefaultMessageHandler",
60
+ "DefaultMessageHandler",
63
61
  "DefaultStatusHandler",
64
62
  "DefaultErrorHandler",
65
63
  "DefaultHandlerFactory",
@@ -67,23 +65,20 @@ __all__ = [
67
65
  "StatusLogStrategy",
68
66
  "ErrorLogStrategy",
69
67
  "WebhookURLFactory",
70
- "WebhookEndpointType",
68
+ "WebhookEndpointType",
71
69
  "webhook_url_factory",
72
-
73
70
  # Factory System
74
71
  "WappaBuilder",
75
- "WappaPlugin",
76
-
72
+ "WappaPlugin",
77
73
  # Plugin System
78
74
  "WappaCorePlugin",
79
75
  "AuthPlugin",
80
- "CORSPlugin",
76
+ "CORSPlugin",
81
77
  "DatabasePlugin",
82
78
  "RedisPlugin",
83
79
  "RateLimitPlugin",
84
80
  "CustomMiddlewarePlugin",
85
81
  "WebhookPlugin",
86
-
87
82
  # Core Application
88
83
  "Wappa",
89
84
  ]
@@ -45,7 +45,7 @@ def _get_version_from_pyproject() -> str:
45
45
  def _is_cli_context() -> bool:
46
46
  """
47
47
  Detect if we're running in CLI context (help, init, examples) vs server context (dev, prod).
48
-
48
+
49
49
  Returns:
50
50
  True if running CLI commands that don't need WhatsApp credentials
51
51
  """
@@ -53,12 +53,12 @@ def _is_cli_context() -> bool:
53
53
  if len(sys.argv) > 1:
54
54
  # Direct CLI commands that don't need credentials
55
55
  cli_only_commands = {"--help", "-h", "init", "examples"}
56
-
56
+
57
57
  # Check for help flag or CLI-only commands
58
58
  for arg in sys.argv[1:]:
59
59
  if arg in cli_only_commands:
60
60
  return True
61
-
61
+
62
62
  # Check if we're running wappa command directly (not through uvicorn)
63
63
  if any("wappa" in arg for arg in sys.argv):
64
64
  # If no server commands (dev/prod) are present, assume CLI context
@@ -66,7 +66,7 @@ def _is_cli_context() -> bool:
66
66
  has_server_command = any(cmd in sys.argv for cmd in server_commands)
67
67
  if not has_server_command:
68
68
  return True
69
-
69
+
70
70
  return False
71
71
 
72
72
 
@@ -129,7 +129,7 @@ class Settings:
129
129
 
130
130
  # Apply validation (skip WhatsApp validation for CLI-only commands)
131
131
  self._validate_settings()
132
-
132
+
133
133
  # Only validate WhatsApp credentials for server operations
134
134
  if not _is_cli_context():
135
135
  self._validate_whatsapp_credentials()
@@ -156,6 +156,8 @@ class Settings:
156
156
  raise ValueError("WP_PHONE_ID is required")
157
157
  if not self.wp_bid:
158
158
  raise ValueError("WP_BID is required")
159
+ if not self.whatsapp_webhook_verify_token:
160
+ raise ValueError("WHATSAPP_WEBHOOK_VERIFY_TOKEN is required")
159
161
 
160
162
  @property
161
163
  def owner_id(self) -> str:
@@ -116,7 +116,7 @@ class DefaultMessageHandler:
116
116
  self._update_stats(webhook)
117
117
 
118
118
  # Get logger with tenant context
119
- tenant_id = webhook.tenant.get_tenant_key() if webhook.tenant else "unknown"
119
+ webhook.tenant.get_tenant_key() if webhook.tenant else "unknown"
120
120
  logger = get_logger(__name__)
121
121
 
122
122
  # Log based on strategy