wappa 0.1.8__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 (147) 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/.env.example +33 -0
  9. wappa/cli/examples/init/app/__init__.py +0 -0
  10. wappa/cli/examples/init/app/main.py +9 -0
  11. wappa/cli/examples/init/app/master_event.py +10 -0
  12. wappa/cli/examples/json_cache_example/.env.example +33 -0
  13. wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
  14. wappa/cli/examples/json_cache_example/app/main.py +247 -0
  15. wappa/cli/examples/json_cache_example/app/master_event.py +455 -0
  16. wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
  17. wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +256 -0
  18. wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
  19. wappa/cli/examples/json_cache_example/app/scores/score_base.py +192 -0
  20. wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +256 -0
  21. wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +187 -0
  22. wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +272 -0
  23. wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +239 -0
  24. wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
  25. wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +174 -0
  26. wappa/cli/examples/json_cache_example/app/utils/message_utils.py +251 -0
  27. wappa/cli/examples/openai_transcript/.gitignore +63 -4
  28. wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
  29. wappa/cli/examples/openai_transcript/app/main.py +9 -0
  30. wappa/cli/examples/openai_transcript/app/master_event.py +62 -0
  31. wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
  32. wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +89 -0
  33. wappa/cli/examples/redis_cache_example/.env.example +33 -0
  34. wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
  35. wappa/cli/examples/redis_cache_example/app/main.py +246 -0
  36. wappa/cli/examples/redis_cache_example/app/master_event.py +455 -0
  37. wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +256 -0
  38. wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
  39. wappa/cli/examples/redis_cache_example/app/scores/score_base.py +192 -0
  40. wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +256 -0
  41. wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +187 -0
  42. wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +272 -0
  43. wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +239 -0
  44. wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
  45. wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +174 -0
  46. wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +251 -0
  47. wappa/cli/examples/simple_echo_example/.env.example +33 -0
  48. wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
  49. wappa/cli/examples/simple_echo_example/app/main.py +191 -0
  50. wappa/cli/examples/simple_echo_example/app/master_event.py +230 -0
  51. wappa/cli/examples/wappa_full_example/.env.example +33 -0
  52. wappa/cli/examples/wappa_full_example/.gitignore +63 -4
  53. wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
  54. wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
  55. wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +492 -0
  56. wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +559 -0
  57. wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +514 -0
  58. wappa/cli/examples/wappa_full_example/app/main.py +269 -0
  59. wappa/cli/examples/wappa_full_example/app/master_event.py +504 -0
  60. wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
  61. wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
  62. wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
  63. wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
  64. wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
  65. wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
  66. wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
  67. wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
  68. wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
  69. wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
  70. wappa/cli/examples/wappa_full_example/app/models/state_models.py +434 -0
  71. wappa/cli/examples/wappa_full_example/app/models/user_models.py +303 -0
  72. wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +327 -0
  73. wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
  74. wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +502 -0
  75. wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +516 -0
  76. wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +337 -0
  77. wappa/cli/main.py +14 -5
  78. wappa/core/__init__.py +18 -23
  79. wappa/core/config/settings.py +7 -5
  80. wappa/core/events/default_handlers.py +1 -1
  81. wappa/core/factory/wappa_builder.py +38 -25
  82. wappa/core/plugins/redis_plugin.py +1 -3
  83. wappa/core/plugins/wappa_core_plugin.py +7 -6
  84. wappa/core/types.py +12 -12
  85. wappa/core/wappa_app.py +10 -8
  86. wappa/database/__init__.py +3 -4
  87. wappa/domain/enums/messenger_platform.py +1 -2
  88. wappa/domain/factories/media_factory.py +5 -20
  89. wappa/domain/factories/message_factory.py +5 -20
  90. wappa/domain/factories/messenger_factory.py +2 -4
  91. wappa/domain/interfaces/cache_interface.py +7 -7
  92. wappa/domain/interfaces/media_interface.py +2 -5
  93. wappa/domain/models/media_result.py +1 -3
  94. wappa/domain/models/platforms/platform_config.py +1 -3
  95. wappa/messaging/__init__.py +9 -12
  96. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
  97. wappa/models/__init__.py +27 -35
  98. wappa/persistence/__init__.py +12 -15
  99. wappa/persistence/cache_factory.py +0 -1
  100. wappa/persistence/json/__init__.py +1 -1
  101. wappa/persistence/json/cache_adapters.py +37 -25
  102. wappa/persistence/json/handlers/state_handler.py +60 -52
  103. wappa/persistence/json/handlers/table_handler.py +51 -49
  104. wappa/persistence/json/handlers/user_handler.py +71 -55
  105. wappa/persistence/json/handlers/utils/file_manager.py +42 -39
  106. wappa/persistence/json/handlers/utils/key_factory.py +1 -1
  107. wappa/persistence/json/handlers/utils/serialization.py +13 -11
  108. wappa/persistence/json/json_cache_factory.py +4 -8
  109. wappa/persistence/json/storage_manager.py +66 -79
  110. wappa/persistence/memory/__init__.py +1 -1
  111. wappa/persistence/memory/cache_adapters.py +37 -25
  112. wappa/persistence/memory/handlers/state_handler.py +62 -52
  113. wappa/persistence/memory/handlers/table_handler.py +59 -53
  114. wappa/persistence/memory/handlers/user_handler.py +75 -55
  115. wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
  116. wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
  117. wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
  118. wappa/persistence/memory/memory_cache_factory.py +3 -7
  119. wappa/persistence/memory/storage_manager.py +52 -62
  120. wappa/persistence/redis/cache_adapters.py +27 -21
  121. wappa/persistence/redis/ops.py +11 -11
  122. wappa/persistence/redis/redis_client.py +4 -6
  123. wappa/persistence/redis/redis_manager.py +12 -4
  124. wappa/processors/factory.py +5 -5
  125. wappa/schemas/factory.py +2 -5
  126. wappa/schemas/whatsapp/message_types/errors.py +3 -12
  127. wappa/schemas/whatsapp/validators.py +3 -3
  128. wappa/webhooks/__init__.py +17 -18
  129. wappa/webhooks/factory.py +3 -5
  130. wappa/webhooks/whatsapp/__init__.py +10 -13
  131. wappa/webhooks/whatsapp/message_types/audio.py +0 -4
  132. wappa/webhooks/whatsapp/message_types/document.py +1 -9
  133. wappa/webhooks/whatsapp/message_types/errors.py +3 -12
  134. wappa/webhooks/whatsapp/message_types/location.py +1 -21
  135. wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
  136. wappa/webhooks/whatsapp/message_types/text.py +0 -6
  137. wappa/webhooks/whatsapp/message_types/video.py +1 -20
  138. wappa/webhooks/whatsapp/status_models.py +2 -2
  139. wappa/webhooks/whatsapp/validators.py +3 -3
  140. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
  141. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/RECORD +144 -80
  142. wappa/cli/examples/init/pyproject.toml +0 -7
  143. wappa/cli/examples/simple_echo_example/.python-version +0 -1
  144. wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
  145. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
  146. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
  147. {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/licenses/LICENSE +0 -0
@@ -33,7 +33,7 @@ async def set(
33
33
  try:
34
34
  # Ensure value is a type redis-py can handle directly (str, bytes, int, float)
35
35
  # Assuming decode_responses=True, strings are preferred.
36
- if not isinstance(value, (str, bytes, int, float)):
36
+ if not isinstance(value, str | bytes | int | float):
37
37
  # Log a warning if a complex type is passed unexpectedly
38
38
  logger.warning(
39
39
  f"RedisCoreMethods.set received non-primitive type for key '{key}'. Attempting str conversion. Type: {type(value)}"
@@ -62,7 +62,7 @@ async def setex(
62
62
  """
63
63
  async with RedisClient.connection(alias=alias) as redis:
64
64
  try:
65
- if not isinstance(value, (str, bytes, int, float)):
65
+ if not isinstance(value, str | bytes | int | float):
66
66
  logger.warning(
67
67
  f"RedisCoreMethods.setex received non-primitive type for key '{key}'. Attempting str conversion. Type: {type(value)}"
68
68
  )
@@ -185,7 +185,7 @@ async def hset_with_expire(
185
185
  # Ensure mapping values are suitable primitive types
186
186
  checked_mapping = {}
187
187
  for k, v in mapping.items():
188
- if not isinstance(v, (str, bytes, int, float)):
188
+ if not isinstance(v, str | bytes | int | float):
189
189
  logger.warning(
190
190
  f"RedisCoreMethods.hset_with_expire received non-primitive type for field '{k}' in mapping for key '{key}'. Attempting str conversion. Type: {type(v)}"
191
191
  )
@@ -288,7 +288,7 @@ async def rpush_and_sadd(
288
288
  # Ensure values/members are primitive types
289
289
  checked_list_values = []
290
290
  for v in list_values:
291
- if not isinstance(v, (str, bytes, int, float)):
291
+ if not isinstance(v, str | bytes | int | float):
292
292
  logger.warning(
293
293
  f"RedisCoreMethods.rpush_and_sadd received non-primitive type in list_values for key '{list_key}'. Attempting str conversion. Type: {type(v)}"
294
294
  )
@@ -298,7 +298,7 @@ async def rpush_and_sadd(
298
298
 
299
299
  checked_set_members = []
300
300
  for m in set_members:
301
- if not isinstance(m, (str, bytes, int, float)):
301
+ if not isinstance(m, str | bytes | int | float):
302
302
  logger.warning(
303
303
  f"RedisCoreMethods.rpush_and_sadd received non-primitive type in set_members for key '{set_key}'. Attempting str conversion. Type: {type(m)}"
304
304
  )
@@ -355,7 +355,7 @@ async def scan_keys(
355
355
  # redis-py >= 4.2 prefers int cursor, older versions might use bytes/str
356
356
  # Let's try to stick to int/str representation for broader compatibility
357
357
  current_cursor: str | int = (
358
- cursor if isinstance(cursor, (str, int)) else str(int(cursor))
358
+ cursor if isinstance(cursor, str | int) else str(int(cursor))
359
359
  ) # Prefer string '0' if bytes 'b0' is passed. Assume int 0 is start.
360
360
 
361
361
  async with RedisClient.connection(alias=alias) as redis:
@@ -482,7 +482,7 @@ async def hset(
482
482
  # Ensure mapping values are strings (or bytes/int/float)
483
483
  checked_mapping = {}
484
484
  for k, v in mapping.items():
485
- if not isinstance(v, (str, bytes, int, float)):
485
+ if not isinstance(v, str | bytes | int | float):
486
486
  logger.warning(
487
487
  f"RedisCoreMethods.hset received non-primitive type for field '{k}' in mapping for key '{key}'. Attempting str conversion. Type: {type(v)}"
488
488
  )
@@ -492,7 +492,7 @@ async def hset(
492
492
  return await redis.hset(key, mapping=checked_mapping)
493
493
  else:
494
494
  # Handle single field/value
495
- if not isinstance(value, (str, bytes, int, float)):
495
+ if not isinstance(value, str | bytes | int | float):
496
496
  logger.warning(
497
497
  f"RedisCoreMethods.hset received non-primitive type for field '{field}' for key '{key}'. Attempting str conversion. Type: {type(value)}"
498
498
  )
@@ -646,7 +646,7 @@ async def rpush(key: str, *values: str, alias: PoolAlias = "default") -> int:
646
646
  # Ensure values are primitive types suitable for redis-py
647
647
  checked_values = []
648
648
  for v in values:
649
- if not isinstance(v, (str, bytes, int, float)):
649
+ if not isinstance(v, str | bytes | int | float):
650
650
  logger.warning(
651
651
  f"RedisCoreMethods.rpush received non-primitive type in values for key '{key}'. Attempting str conversion. Type: {type(v)}"
652
652
  )
@@ -780,7 +780,7 @@ async def sadd(key: str, *members: str, alias: PoolAlias = "default") -> int:
780
780
  # Ensure members are primitive types suitable for redis-py
781
781
  checked_members = []
782
782
  for m in members:
783
- if not isinstance(m, (str, bytes, int, float)):
783
+ if not isinstance(m, str | bytes | int | float):
784
784
  logger.warning(
785
785
  f"RedisCoreMethods.sadd received non-primitive type in members for key '{key}'. Attempting str conversion. Type: {type(m)}"
786
786
  )
@@ -834,7 +834,7 @@ async def srem(key: str, *members: str, alias: PoolAlias = "default") -> int:
834
834
  # Ensure members are primitive types suitable for redis-py
835
835
  checked_members = []
836
836
  for m in members:
837
- if not isinstance(m, (str, bytes, int, float)):
837
+ if not isinstance(m, str | bytes | int | float):
838
838
  logger.warning(
839
839
  f"RedisCoreMethods.srem received non-primitive type in members for key '{key}'. Attempting str conversion. Type: {type(m)}"
840
840
  )
@@ -102,9 +102,7 @@ class RedisClient:
102
102
  if missing:
103
103
  raise ValueError(f"Missing required pool aliases: {missing}")
104
104
 
105
- extra = set(str(k) for k in urls) - set(
106
- str(k) for k in POOL_DB_MAPPING
107
- )
105
+ extra = {str(k) for k in urls} - {str(k) for k in POOL_DB_MAPPING}
108
106
  if extra:
109
107
  raise ValueError(
110
108
  f"Unknown pool aliases: {extra}. Only {list(POOL_DB_MAPPING.keys())} are allowed."
@@ -202,7 +200,7 @@ A fork-safe, asyncio-native helper with **3 predefined Redis pools** for Wappa c
202
200
 
203
201
  | Pool | Database | Purpose |
204
202
  |--------------|----------|----------------------------|
205
- | users | 0 | User-specific cache data |
203
+ | users | 0 | User-specific cache data |
206
204
  | state_handler| 1 | Handler state cache data |
207
205
  | table | 2 | Table/data cache |
208
206
 
@@ -215,14 +213,14 @@ RedisClient.setup_single_url("redis://localhost:6379")
215
213
  # Option 2: Explicit URLs per pool
216
214
  RedisClient.setup_multiple_urls({
217
215
  "users": "redis://localhost:6379/0",
218
- "state_handler": "redis://cache:6379/1",
216
+ "state_handler": "redis://cache:6379/1",
219
217
  "table": "redis://localhost:6379/2"
220
218
  })
221
219
 
222
220
  # Usage
223
221
  async with RedisClient.connection("state_handler") as r:
224
222
  await r.set("key", "value")
225
-
223
+
226
224
  redis = await RedisClient.get("users")
227
225
  await redis.hset("user:123", "name", "Alice")
228
226
  ```
@@ -50,7 +50,9 @@ class RedisManager:
50
50
  url = redis_url or settings.redis_url
51
51
  connections = max_connections or settings.redis_max_connections
52
52
 
53
- logger.info(f"Setting up Redis pools from {url} (max_connections: {connections})")
53
+ logger.info(
54
+ f"Setting up Redis pools from {url} (max_connections: {connections})"
55
+ )
54
56
 
55
57
  # Use Wappa's RedisClient.setup_single_url
56
58
  RedisClient.setup_single_url(
@@ -66,7 +68,9 @@ class RedisManager:
66
68
 
67
69
  # Success confirmation with pool details
68
70
  pool_count = len(POOL_DB_MAPPING)
69
- pool_details = ", ".join(f"{alias}:db{db}" for alias, db in POOL_DB_MAPPING.items())
71
+ pool_details = ", ".join(
72
+ f"{alias}:db{db}" for alias, db in POOL_DB_MAPPING.items()
73
+ )
70
74
  logger.info(f"✅ Redis pools ready: {pool_count} pools ({pool_details})")
71
75
 
72
76
  except Exception as e:
@@ -89,10 +93,14 @@ class RedisManager:
89
93
  redis = await RedisClient.get(pool_alias)
90
94
  await redis.ping()
91
95
  successful_pools.append(f"{alias}:db{POOL_DB_MAPPING[alias]}")
92
- logger.debug(f"✅ Redis pool '{alias}' (db{POOL_DB_MAPPING[alias]}) health check passed")
96
+ logger.debug(
97
+ f"✅ Redis pool '{alias}' (db{POOL_DB_MAPPING[alias]}) health check passed"
98
+ )
93
99
  except Exception as e:
94
100
  failed_pools.append(f"{alias}:db{POOL_DB_MAPPING[alias]}")
95
- logger.error(f"❌ Redis pool '{alias}' (db{POOL_DB_MAPPING[alias]}) health check failed: {e}")
101
+ logger.error(
102
+ f"❌ Redis pool '{alias}' (db{POOL_DB_MAPPING[alias]}) health check failed: {e}"
103
+ )
96
104
 
97
105
  if failed_pools:
98
106
  raise ConnectionError(f"Failed Redis pools: {', '.join(failed_pools)}")
@@ -116,7 +116,7 @@ class PlatformDetector:
116
116
 
117
117
  @classmethod
118
118
  def detect_platform(
119
- self,
119
+ cls,
120
120
  payload: dict[str, Any],
121
121
  url_path: str | None = None,
122
122
  headers: dict[str, str] | None = None,
@@ -136,19 +136,19 @@ class PlatformDetector:
136
136
  ProcessorError: If platform cannot be detected
137
137
  """
138
138
  # Try payload detection first (most reliable)
139
- platform = self.detect_from_payload(payload)
139
+ platform = cls.detect_from_payload(payload)
140
140
  if platform:
141
141
  return platform
142
142
 
143
143
  # Try URL path detection
144
144
  if url_path:
145
- platform = self.detect_from_url(url_path)
145
+ platform = cls.detect_from_url(url_path)
146
146
  if platform:
147
147
  return platform
148
148
 
149
149
  # Try header detection
150
150
  if headers:
151
- platform = self.detect_from_headers(headers)
151
+ platform = cls.detect_from_headers(headers)
152
152
  if platform:
153
153
  return platform
154
154
 
@@ -371,7 +371,7 @@ class ProcessorFactory:
371
371
  """
372
372
  return {
373
373
  "cached_processors": len(self._processors),
374
- "cached_platforms": [p.value for p in self._processors.keys()],
374
+ "cached_platforms": [p.value for p in self._processors],
375
375
  "supported_platforms": [p.value for p in self.get_supported_platforms()],
376
376
  "cache_size_bytes": sum(
377
377
  len(str(processor)) for processor in self._processors.values()
wappa/schemas/factory.py CHANGED
@@ -183,7 +183,7 @@ class MessageSchemaRegistry:
183
183
  for platform, schemas in self._message_schemas.items():
184
184
  stats["platforms"][platform.value] = {
185
185
  "message_types": len(schemas),
186
- "supported_types": [mt.value for mt in schemas.keys()],
186
+ "supported_types": [mt.value for mt in schemas],
187
187
  }
188
188
 
189
189
  return stats
@@ -720,10 +720,7 @@ class SchemaFactory:
720
720
  return False
721
721
 
722
722
  # Should have metadata at minimum
723
- if "metadata" not in value:
724
- return False
725
-
726
- return True
723
+ return "metadata" in value
727
724
 
728
725
  except (KeyError, IndexError, TypeError):
729
726
  return False
@@ -91,10 +91,7 @@ class WhatsAppWebhookError(BaseModel):
91
91
  This is a heuristic and may need adjustment based on documentation.
92
92
  """
93
93
  # System errors often start with 1, API errors with other digits
94
- for code in self.error_codes:
95
- if 100000 <= code <= 199999: # System error range (heuristic)
96
- return True
97
- return False
94
+ return any(100000 <= code <= 199999 for code in self.error_codes)
98
95
 
99
96
  def is_app_error(self) -> bool:
100
97
  """
@@ -103,10 +100,7 @@ class WhatsAppWebhookError(BaseModel):
103
100
  App errors typically relate to configuration or permissions.
104
101
  """
105
102
  # App/permission errors often in different ranges
106
- for code in self.error_codes:
107
- if 200000 <= code <= 299999: # App error range (heuristic)
108
- return True
109
- return False
103
+ return any(200000 <= code <= 299999 for code in self.error_codes)
110
104
 
111
105
  def is_account_error(self) -> bool:
112
106
  """
@@ -115,10 +109,7 @@ class WhatsAppWebhookError(BaseModel):
115
109
  Account errors typically relate to quotas, limits, or account status.
116
110
  """
117
111
  # Account errors often in different ranges
118
- for code in self.error_codes:
119
- if 300000 <= code <= 399999: # Account error range (heuristic)
120
- return True
121
- return False
112
+ return any(300000 <= code <= 399999 for code in self.error_codes)
122
113
 
123
114
  def get_error_severity(self) -> str:
124
115
  """
@@ -417,11 +417,11 @@ class WhatsAppErrorHandler:
417
417
  True if error is recoverable, False otherwise
418
418
  """
419
419
  # Validation errors are not recoverable
420
- if isinstance(error, (ValidationError, WhatsAppValidationError)):
420
+ if isinstance(error, ValidationError | WhatsAppValidationError):
421
421
  return False
422
422
 
423
423
  # Network/timeout errors might be recoverable
424
- if isinstance(error, (ConnectionError, TimeoutError)):
424
+ if isinstance(error, ConnectionError | TimeoutError):
425
425
  return True
426
426
 
427
427
  # Generic exceptions might be recoverable
@@ -446,7 +446,7 @@ class WhatsAppErrorHandler:
446
446
  # WhatsApp-specific validation errors are medium priority
447
447
  return "medium"
448
448
 
449
- if isinstance(error, (ConnectionError, TimeoutError)):
449
+ if isinstance(error, ConnectionError | TimeoutError):
450
450
  # Network issues are high priority - service availability
451
451
  return "high"
452
452
 
@@ -1,7 +1,7 @@
1
1
  """
2
2
  Wappa Webhook Schemas and Universal Interfaces
3
3
 
4
- Provides clean access to webhook schemas and universal interfaces for
4
+ Provides clean access to webhook schemas and universal interfaces for
5
5
  processing incoming webhooks from different messaging platforms.
6
6
 
7
7
  Clean Architecture: Domain models and platform-agnostic interfaces.
@@ -9,41 +9,41 @@ Clean Architecture: Domain models and platform-agnostic interfaces.
9
9
  Usage:
10
10
  # Universal webhook interfaces (user requested: quick access to webhook schemas)
11
11
  from wappa.webhooks import IncomingMessageWebhook, StatusWebhook, ErrorWebhook
12
-
13
- # WhatsApp webhook schemas
12
+
13
+ # WhatsApp webhook schemas
14
14
  from wappa.webhooks.whatsapp import WhatsAppWebhook, WhatsAppMetadata
15
-
15
+
16
16
  # Message type schemas
17
17
  from wappa.webhooks.whatsapp.message_types import TextMessage, ImageMessage
18
18
  """
19
19
 
20
20
  # Universal Webhook Interfaces (User Request: Quick access to these)
21
- from .core.types import UniversalMessageData, PlatformType, WebhookType
21
+ from .core.base_webhook import BaseContact, BaseWebhook, BaseWebhookMetadata
22
+ from .core.types import PlatformType, UniversalMessageData, WebhookType
22
23
  from .core.webhook_interfaces.universal_webhooks import (
23
24
  ErrorWebhook,
24
25
  IncomingMessageWebhook,
25
- StatusWebhook,
26
+ StatusWebhook,
26
27
  UniversalWebhook,
27
28
  )
28
- from .core.base_webhook import BaseWebhook, BaseWebhookMetadata, BaseContact
29
-
30
- # WhatsApp Webhook Schemas
31
- from .whatsapp.webhook_container import WhatsAppWebhook
32
29
  from .whatsapp.base_models import (
33
- WhatsAppMetadata,
34
- WhatsAppContact,
35
30
  ContactProfile,
31
+ Conversation,
32
+ ErrorData,
36
33
  MessageContext,
37
34
  MessageError,
38
- ErrorData,
39
35
  Pricing,
40
- Conversation,
36
+ WhatsAppContact,
37
+ WhatsAppMetadata,
41
38
  )
42
39
 
40
+ # WhatsApp Webhook Schemas
41
+ from .whatsapp.webhook_container import WhatsAppWebhook
42
+
43
43
  __all__ = [
44
44
  # Universal Interfaces (Clean Access as Requested)
45
45
  "UniversalWebhook",
46
- "IncomingMessageWebhook",
46
+ "IncomingMessageWebhook",
47
47
  "StatusWebhook",
48
48
  "ErrorWebhook",
49
49
  "UniversalMessageData",
@@ -52,13 +52,12 @@ __all__ = [
52
52
  "BaseWebhook",
53
53
  "BaseWebhookMetadata",
54
54
  "BaseContact",
55
-
56
- # WhatsApp Core Schemas
55
+ # WhatsApp Core Schemas
57
56
  "WhatsAppWebhook",
58
57
  "WhatsAppMetadata",
59
58
  "WhatsAppContact",
60
59
  "ContactProfile",
61
- "MessageContext",
60
+ "MessageContext",
62
61
  "MessageError",
63
62
  "ErrorData",
64
63
  "Pricing",
wappa/webhooks/factory.py CHANGED
@@ -11,6 +11,7 @@ from wappa.core.logging.logger import get_logger
11
11
  from wappa.webhooks.core.base_message import BaseMessage
12
12
  from wappa.webhooks.core.base_webhook import BaseWebhook
13
13
  from wappa.webhooks.core.types import MessageType, PlatformType
14
+ from wappa.webhooks.core.webhook_interfaces.universal_webhooks import UniversalWebhook
14
15
 
15
16
 
16
17
  class SchemaRegistryError(Exception):
@@ -183,7 +184,7 @@ class MessageSchemaRegistry:
183
184
  for platform, schemas in self._message_schemas.items():
184
185
  stats["platforms"][platform.value] = {
185
186
  "message_types": len(schemas),
186
- "supported_types": [mt.value for mt in schemas.keys()],
187
+ "supported_types": [mt.value for mt in schemas],
187
188
  }
188
189
 
189
190
  return stats
@@ -720,10 +721,7 @@ class SchemaFactory:
720
721
  return False
721
722
 
722
723
  # Should have metadata at minimum
723
- if "metadata" not in value:
724
- return False
725
-
726
- return True
724
+ return "metadata" in value
727
725
 
728
726
  except (KeyError, IndexError, TypeError):
729
727
  return False
@@ -12,47 +12,44 @@ Usage:
12
12
  """
13
13
 
14
14
  # Main webhook container
15
- from .webhook_container import WhatsAppWebhook
16
-
17
15
  # Base models and metadata
18
16
  from .base_models import (
19
- WhatsAppMetadata,
20
- WhatsAppContact,
21
17
  ContactProfile,
18
+ Conversation,
19
+ ErrorData,
22
20
  MessageContext,
23
21
  MessageError,
24
- ErrorData,
25
22
  Pricing,
26
- Conversation,
23
+ WhatsAppContact,
24
+ WhatsAppMetadata,
27
25
  )
28
26
 
29
27
  # Status models
30
28
  from .status_models import (
31
- MessageStatus,
32
- StatusType,
33
29
  DeliveryStatus,
30
+ FailedStatus,
31
+ MessageStatus,
34
32
  ReadStatus,
35
33
  SentStatus,
36
- FailedStatus,
34
+ StatusType,
37
35
  )
36
+ from .webhook_container import WhatsAppWebhook
38
37
 
39
38
  __all__ = [
40
39
  # Main webhook
41
40
  "WhatsAppWebhook",
42
-
43
41
  # Base models
44
42
  "WhatsAppMetadata",
45
- "WhatsAppContact",
43
+ "WhatsAppContact",
46
44
  "ContactProfile",
47
45
  "MessageContext",
48
46
  "MessageError",
49
47
  "ErrorData",
50
48
  "Pricing",
51
49
  "Conversation",
52
-
53
50
  # Status models
54
51
  "MessageStatus",
55
- "StatusType",
52
+ "StatusType",
56
53
  "DeliveryStatus",
57
54
  "ReadStatus",
58
55
  "SentStatus",
@@ -303,10 +303,6 @@ class WhatsAppAudioMessage(BaseAudioMessage):
303
303
 
304
304
  # Implement abstract methods from BaseMediaMessage
305
305
 
306
- @property
307
- def media_id(self) -> str:
308
- return self.audio.id
309
-
310
306
  @property
311
307
  def media_type(self) -> MediaType:
312
308
  mime_str = self.audio.mime_type
@@ -372,11 +372,7 @@ class WhatsAppDocumentMessage(BaseDocumentMessage):
372
372
  },
373
373
  }
374
374
 
375
- # Implement abstract methods from BaseMediaMessage
376
-
377
- @property
378
- def media_id(self) -> str:
379
- return self.document.id
375
+ # Abstract methods already implemented above
380
376
 
381
377
  @property
382
378
  def media_type(self) -> MediaType:
@@ -396,10 +392,6 @@ class WhatsAppDocumentMessage(BaseDocumentMessage):
396
392
  def file_size(self) -> int | None:
397
393
  return None # WhatsApp doesn't provide file size in webhooks
398
394
 
399
- @property
400
- def caption(self) -> str | None:
401
- return self.document.caption
402
-
403
395
  def get_download_info(self) -> dict[str, Any]:
404
396
  return {
405
397
  "media_id": self.media_id,
@@ -91,10 +91,7 @@ class WhatsAppWebhookError(BaseModel):
91
91
  This is a heuristic and may need adjustment based on documentation.
92
92
  """
93
93
  # System errors often start with 1, API errors with other digits
94
- for code in self.error_codes:
95
- if 100000 <= code <= 199999: # System error range (heuristic)
96
- return True
97
- return False
94
+ return any(100000 <= code <= 199999 for code in self.error_codes)
98
95
 
99
96
  def is_app_error(self) -> bool:
100
97
  """
@@ -103,10 +100,7 @@ class WhatsAppWebhookError(BaseModel):
103
100
  App errors typically relate to configuration or permissions.
104
101
  """
105
102
  # App/permission errors often in different ranges
106
- for code in self.error_codes:
107
- if 200000 <= code <= 299999: # App error range (heuristic)
108
- return True
109
- return False
103
+ return any(200000 <= code <= 299999 for code in self.error_codes)
110
104
 
111
105
  def is_account_error(self) -> bool:
112
106
  """
@@ -115,10 +109,7 @@ class WhatsAppWebhookError(BaseModel):
115
109
  Account errors typically relate to quotas, limits, or account status.
116
110
  """
117
111
  # Account errors often in different ranges
118
- for code in self.error_codes:
119
- if 300000 <= code <= 399999: # Account error range (heuristic)
120
- return True
121
- return False
112
+ return any(300000 <= code <= 399999 for code in self.error_codes)
122
113
 
123
114
  def get_error_severity(self) -> str:
124
115
  """
@@ -382,27 +382,7 @@ class WhatsAppLocationMessage(BaseLocationMessage):
382
382
  },
383
383
  }
384
384
 
385
- # Implement abstract methods from BaseLocationMessage
386
-
387
- @property
388
- def latitude(self) -> float:
389
- return self.location.latitude
390
-
391
- @property
392
- def longitude(self) -> float:
393
- return self.location.longitude
394
-
395
- @property
396
- def address(self) -> str | None:
397
- return self.location.address
398
-
399
- @property
400
- def name(self) -> str | None:
401
- return self.location.name
402
-
403
- @property
404
- def url(self) -> str | None:
405
- return self.location.url
385
+ # Abstract methods already implemented above
406
386
 
407
387
  @property
408
388
  def location_name(self) -> str | None:
@@ -288,11 +288,7 @@ class WhatsAppStickerMessage(BaseMediaMessage):
288
288
  },
289
289
  }
290
290
 
291
- # Implement abstract methods from BaseMediaMessage
292
-
293
- @property
294
- def media_id(self) -> str:
295
- return self.sticker.id
291
+ # Abstract methods already implemented above
296
292
 
297
293
  @property
298
294
  def media_type(self) -> MediaType:
@@ -397,12 +397,6 @@ class WhatsAppTextMessage(BaseTextMessage):
397
397
  """Get the text content of the message."""
398
398
  return self.text.body
399
399
 
400
- def get_reply_context(self) -> tuple[str | None, str | None]:
401
- """Get reply context information."""
402
- if self.is_reply and self.context:
403
- return (self.context.from_, self.context.id)
404
- return (None, None)
405
-
406
400
  @classmethod
407
401
  def from_platform_data(
408
402
  cls, data: dict[str, Any], **kwargs
@@ -12,7 +12,6 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_valida
12
12
  from wappa.webhooks.core.base_message import BaseMessageContext, BaseVideoMessage
13
13
  from wappa.webhooks.core.types import (
14
14
  ConversationType,
15
- MediaType,
16
15
  PlatformType,
17
16
  UniversalMessageData,
18
17
  )
@@ -300,25 +299,7 @@ class WhatsAppVideoMessage(BaseVideoMessage):
300
299
  "video_content": self.video.model_dump(),
301
300
  }
302
301
 
303
- # Implement abstract methods from BaseMediaMessage
304
- @property
305
- def media_id(self) -> str:
306
- return self.video.id
307
-
308
- @property
309
- def media_type(self) -> MediaType:
310
- try:
311
- return MediaType(self.video.mime_type)
312
- except ValueError:
313
- return MediaType.VIDEO_MP4
314
-
315
- @property
316
- def file_size(self) -> int | None:
317
- return None
318
-
319
- @property
320
- def caption(self) -> str | None:
321
- return self.video.caption
302
+ # Abstract methods already implemented above
322
303
 
323
304
  def get_download_info(self) -> dict[str, Any]:
324
305
  return {
@@ -322,9 +322,9 @@ class WhatsAppMessageStatus(BaseMessageStatus):
322
322
  )
323
323
  return (None, None, None)
324
324
 
325
- def get_error_info(self) -> list[dict[str, str | int]]:
325
+ def get_all_errors(self) -> list[dict[str, str | int]]:
326
326
  """
327
- Get error information for failed messages.
327
+ Get all error information for failed messages.
328
328
 
329
329
  Returns:
330
330
  List of error dictionaries with code, title, message, and details.
@@ -417,11 +417,11 @@ class WhatsAppErrorHandler:
417
417
  True if error is recoverable, False otherwise
418
418
  """
419
419
  # Validation errors are not recoverable
420
- if isinstance(error, (ValidationError, WhatsAppValidationError)):
420
+ if isinstance(error, ValidationError | WhatsAppValidationError):
421
421
  return False
422
422
 
423
423
  # Network/timeout errors might be recoverable
424
- if isinstance(error, (ConnectionError, TimeoutError)):
424
+ if isinstance(error, ConnectionError | TimeoutError):
425
425
  return True
426
426
 
427
427
  # Generic exceptions might be recoverable
@@ -446,7 +446,7 @@ class WhatsAppErrorHandler:
446
446
  # WhatsApp-specific validation errors are medium priority
447
447
  return "medium"
448
448
 
449
- if isinstance(error, (ConnectionError, TimeoutError)):
449
+ if isinstance(error, ConnectionError | TimeoutError):
450
450
  # Network issues are high priority - service availability
451
451
  return "high"
452
452