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.
- wappa/__init__.py +4 -5
- wappa/api/controllers/webhook_controller.py +5 -2
- wappa/api/dependencies/__init__.py +0 -5
- wappa/api/middleware/error_handler.py +4 -4
- wappa/api/middleware/owner.py +11 -5
- wappa/api/routes/webhooks.py +2 -2
- wappa/cli/__init__.py +1 -1
- wappa/cli/examples/init/.env.example +33 -0
- wappa/cli/examples/init/app/__init__.py +0 -0
- wappa/cli/examples/init/app/main.py +9 -0
- wappa/cli/examples/init/app/master_event.py +10 -0
- wappa/cli/examples/json_cache_example/.env.example +33 -0
- wappa/cli/examples/json_cache_example/app/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/main.py +247 -0
- wappa/cli/examples/json_cache_example/app/master_event.py +455 -0
- wappa/cli/examples/json_cache_example/app/models/__init__.py +1 -0
- wappa/cli/examples/json_cache_example/app/models/json_demo_models.py +256 -0
- wappa/cli/examples/json_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/json_cache_example/app/scores/score_base.py +192 -0
- wappa/cli/examples/json_cache_example/app/scores/score_cache_statistics.py +256 -0
- wappa/cli/examples/json_cache_example/app/scores/score_message_history.py +187 -0
- wappa/cli/examples/json_cache_example/app/scores/score_state_commands.py +272 -0
- wappa/cli/examples/json_cache_example/app/scores/score_user_management.py +239 -0
- wappa/cli/examples/json_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/json_cache_example/app/utils/cache_utils.py +174 -0
- wappa/cli/examples/json_cache_example/app/utils/message_utils.py +251 -0
- wappa/cli/examples/openai_transcript/.gitignore +63 -4
- wappa/cli/examples/openai_transcript/app/__init__.py +0 -0
- wappa/cli/examples/openai_transcript/app/main.py +9 -0
- wappa/cli/examples/openai_transcript/app/master_event.py +62 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/__init__.py +3 -0
- wappa/cli/examples/openai_transcript/app/openai_utils/audio_processing.py +89 -0
- wappa/cli/examples/redis_cache_example/.env.example +33 -0
- wappa/cli/examples/redis_cache_example/app/__init__.py +6 -0
- wappa/cli/examples/redis_cache_example/app/main.py +246 -0
- wappa/cli/examples/redis_cache_example/app/master_event.py +455 -0
- wappa/cli/examples/redis_cache_example/app/models/redis_demo_models.py +256 -0
- wappa/cli/examples/redis_cache_example/app/scores/__init__.py +35 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_base.py +192 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_cache_statistics.py +256 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_message_history.py +187 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_state_commands.py +272 -0
- wappa/cli/examples/redis_cache_example/app/scores/score_user_management.py +239 -0
- wappa/cli/examples/redis_cache_example/app/utils/__init__.py +26 -0
- wappa/cli/examples/redis_cache_example/app/utils/cache_utils.py +174 -0
- wappa/cli/examples/redis_cache_example/app/utils/message_utils.py +251 -0
- wappa/cli/examples/simple_echo_example/.env.example +33 -0
- wappa/cli/examples/simple_echo_example/app/__init__.py +7 -0
- wappa/cli/examples/simple_echo_example/app/main.py +191 -0
- wappa/cli/examples/simple_echo_example/app/master_event.py +230 -0
- wappa/cli/examples/wappa_full_example/.env.example +33 -0
- wappa/cli/examples/wappa_full_example/.gitignore +63 -4
- wappa/cli/examples/wappa_full_example/app/__init__.py +6 -0
- wappa/cli/examples/wappa_full_example/app/handlers/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/handlers/command_handlers.py +492 -0
- wappa/cli/examples/wappa_full_example/app/handlers/message_handlers.py +559 -0
- wappa/cli/examples/wappa_full_example/app/handlers/state_handlers.py +514 -0
- wappa/cli/examples/wappa_full_example/app/main.py +269 -0
- wappa/cli/examples/wappa_full_example/app/master_event.py +504 -0
- wappa/cli/examples/wappa_full_example/app/media/README.md +54 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/README.md +62 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/kitty.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/buttons/puppy.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/README.md +110 -0
- wappa/cli/examples/wappa_full_example/app/media/list/audio.mp3 +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/document.pdf +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/image.png +0 -0
- wappa/cli/examples/wappa_full_example/app/media/list/video.mp4 +0 -0
- wappa/cli/examples/wappa_full_example/app/models/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/models/state_models.py +434 -0
- wappa/cli/examples/wappa_full_example/app/models/user_models.py +303 -0
- wappa/cli/examples/wappa_full_example/app/models/webhook_metadata.py +327 -0
- wappa/cli/examples/wappa_full_example/app/utils/__init__.py +5 -0
- wappa/cli/examples/wappa_full_example/app/utils/cache_utils.py +502 -0
- wappa/cli/examples/wappa_full_example/app/utils/media_handler.py +516 -0
- wappa/cli/examples/wappa_full_example/app/utils/metadata_extractor.py +337 -0
- wappa/cli/main.py +14 -5
- wappa/core/__init__.py +18 -23
- wappa/core/config/settings.py +7 -5
- wappa/core/events/default_handlers.py +1 -1
- wappa/core/factory/wappa_builder.py +38 -25
- wappa/core/plugins/redis_plugin.py +1 -3
- wappa/core/plugins/wappa_core_plugin.py +7 -6
- wappa/core/types.py +12 -12
- wappa/core/wappa_app.py +10 -8
- wappa/database/__init__.py +3 -4
- wappa/domain/enums/messenger_platform.py +1 -2
- wappa/domain/factories/media_factory.py +5 -20
- wappa/domain/factories/message_factory.py +5 -20
- wappa/domain/factories/messenger_factory.py +2 -4
- wappa/domain/interfaces/cache_interface.py +7 -7
- wappa/domain/interfaces/media_interface.py +2 -5
- wappa/domain/models/media_result.py +1 -3
- wappa/domain/models/platforms/platform_config.py +1 -3
- wappa/messaging/__init__.py +9 -12
- wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +20 -22
- wappa/models/__init__.py +27 -35
- wappa/persistence/__init__.py +12 -15
- wappa/persistence/cache_factory.py +0 -1
- wappa/persistence/json/__init__.py +1 -1
- wappa/persistence/json/cache_adapters.py +37 -25
- wappa/persistence/json/handlers/state_handler.py +60 -52
- wappa/persistence/json/handlers/table_handler.py +51 -49
- wappa/persistence/json/handlers/user_handler.py +71 -55
- wappa/persistence/json/handlers/utils/file_manager.py +42 -39
- wappa/persistence/json/handlers/utils/key_factory.py +1 -1
- wappa/persistence/json/handlers/utils/serialization.py +13 -11
- wappa/persistence/json/json_cache_factory.py +4 -8
- wappa/persistence/json/storage_manager.py +66 -79
- wappa/persistence/memory/__init__.py +1 -1
- wappa/persistence/memory/cache_adapters.py +37 -25
- wappa/persistence/memory/handlers/state_handler.py +62 -52
- wappa/persistence/memory/handlers/table_handler.py +59 -53
- wappa/persistence/memory/handlers/user_handler.py +75 -55
- wappa/persistence/memory/handlers/utils/key_factory.py +1 -1
- wappa/persistence/memory/handlers/utils/memory_store.py +75 -71
- wappa/persistence/memory/handlers/utils/ttl_manager.py +59 -67
- wappa/persistence/memory/memory_cache_factory.py +3 -7
- wappa/persistence/memory/storage_manager.py +52 -62
- wappa/persistence/redis/cache_adapters.py +27 -21
- wappa/persistence/redis/ops.py +11 -11
- wappa/persistence/redis/redis_client.py +4 -6
- wappa/persistence/redis/redis_manager.py +12 -4
- wappa/processors/factory.py +5 -5
- wappa/schemas/factory.py +2 -5
- wappa/schemas/whatsapp/message_types/errors.py +3 -12
- wappa/schemas/whatsapp/validators.py +3 -3
- wappa/webhooks/__init__.py +17 -18
- wappa/webhooks/factory.py +3 -5
- wappa/webhooks/whatsapp/__init__.py +10 -13
- wappa/webhooks/whatsapp/message_types/audio.py +0 -4
- wappa/webhooks/whatsapp/message_types/document.py +1 -9
- wappa/webhooks/whatsapp/message_types/errors.py +3 -12
- wappa/webhooks/whatsapp/message_types/location.py +1 -21
- wappa/webhooks/whatsapp/message_types/sticker.py +1 -5
- wappa/webhooks/whatsapp/message_types/text.py +0 -6
- wappa/webhooks/whatsapp/message_types/video.py +1 -20
- wappa/webhooks/whatsapp/status_models.py +2 -2
- wappa/webhooks/whatsapp/validators.py +3 -3
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/METADATA +362 -8
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/RECORD +144 -80
- wappa/cli/examples/init/pyproject.toml +0 -7
- wappa/cli/examples/simple_echo_example/.python-version +0 -1
- wappa/cli/examples/simple_echo_example/pyproject.toml +0 -9
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/WHEEL +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/entry_points.txt +0 -0
- {wappa-0.1.8.dist-info → wappa-0.1.10.dist-info}/licenses/LICENSE +0 -0
wappa/persistence/redis/ops.py
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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)}")
|
wappa/processors/factory.py
CHANGED
|
@@ -116,7 +116,7 @@ class PlatformDetector:
|
|
|
116
116
|
|
|
117
117
|
@classmethod
|
|
118
118
|
def detect_platform(
|
|
119
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
420
|
+
if isinstance(error, ValidationError | WhatsAppValidationError):
|
|
421
421
|
return False
|
|
422
422
|
|
|
423
423
|
# Network/timeout errors might be recoverable
|
|
424
|
-
if isinstance(error,
|
|
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,
|
|
449
|
+
if isinstance(error, ConnectionError | TimeoutError):
|
|
450
450
|
# Network issues are high priority - service availability
|
|
451
451
|
return "high"
|
|
452
452
|
|
wappa/webhooks/__init__.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
|
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,
|
|
420
|
+
if isinstance(error, ValidationError | WhatsAppValidationError):
|
|
421
421
|
return False
|
|
422
422
|
|
|
423
423
|
# Network/timeout errors might be recoverable
|
|
424
|
-
if isinstance(error,
|
|
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,
|
|
449
|
+
if isinstance(error, ConnectionError | TimeoutError):
|
|
450
450
|
# Network issues are high priority - service availability
|
|
451
451
|
return "high"
|
|
452
452
|
|