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
|
@@ -10,9 +10,9 @@ import os
|
|
|
10
10
|
import tempfile
|
|
11
11
|
import time
|
|
12
12
|
from collections.abc import AsyncIterator
|
|
13
|
-
from contextlib import asynccontextmanager
|
|
13
|
+
from contextlib import asynccontextmanager, suppress
|
|
14
14
|
from pathlib import Path
|
|
15
|
-
from typing import Any,
|
|
15
|
+
from typing import Any, AsyncContextManager, BinaryIO
|
|
16
16
|
|
|
17
17
|
from wappa.core.logging.logger import get_logger
|
|
18
18
|
from wappa.domain.interfaces.media_interface import IMediaHandler
|
|
@@ -331,7 +331,7 @@ class WhatsAppMediaHandler(IMediaHandler):
|
|
|
331
331
|
|
|
332
332
|
Based on existing WhatsAppServiceMedia.download_media() method.
|
|
333
333
|
Implements workflow: GET /MEDIA_ID -> GET /MEDIA_URL
|
|
334
|
-
|
|
334
|
+
|
|
335
335
|
Args:
|
|
336
336
|
media_id: Platform-specific media identifier
|
|
337
337
|
destination_path: Optional path to save file (ignored if use_tempfile=True)
|
|
@@ -412,16 +412,20 @@ class WhatsAppMediaHandler(IMediaHandler):
|
|
|
412
412
|
# Save to file if destination_path provided or tempfile requested
|
|
413
413
|
final_path = None
|
|
414
414
|
is_temp_file = False
|
|
415
|
-
|
|
415
|
+
|
|
416
416
|
if use_tempfile:
|
|
417
417
|
# Create temporary file
|
|
418
418
|
extension_map = self._get_extension_map()
|
|
419
|
-
extension = temp_suffix or extension_map.get(
|
|
420
|
-
|
|
419
|
+
extension = temp_suffix or extension_map.get(
|
|
420
|
+
response_content_type, ""
|
|
421
|
+
)
|
|
422
|
+
|
|
421
423
|
# Create named temporary file
|
|
422
|
-
temp_fd, temp_path = tempfile.mkstemp(
|
|
424
|
+
temp_fd, temp_path = tempfile.mkstemp(
|
|
425
|
+
suffix=extension, prefix="wappa_media_"
|
|
426
|
+
)
|
|
423
427
|
try:
|
|
424
|
-
with os.fdopen(temp_fd,
|
|
428
|
+
with os.fdopen(temp_fd, "wb") as temp_file:
|
|
425
429
|
temp_file.write(data)
|
|
426
430
|
final_path = Path(temp_path)
|
|
427
431
|
is_temp_file = True
|
|
@@ -430,12 +434,10 @@ class WhatsAppMediaHandler(IMediaHandler):
|
|
|
430
434
|
)
|
|
431
435
|
except Exception:
|
|
432
436
|
# Clean up on error
|
|
433
|
-
|
|
437
|
+
with suppress(Exception):
|
|
434
438
|
os.unlink(temp_path)
|
|
435
|
-
except Exception:
|
|
436
|
-
pass
|
|
437
439
|
raise
|
|
438
|
-
|
|
440
|
+
|
|
439
441
|
elif destination_path:
|
|
440
442
|
# Original destination path logic
|
|
441
443
|
extension_map = self._get_extension_map()
|
|
@@ -466,11 +468,11 @@ class WhatsAppMediaHandler(IMediaHandler):
|
|
|
466
468
|
platform=PlatformType.WHATSAPP,
|
|
467
469
|
tenant_id=self._tenant_id,
|
|
468
470
|
)
|
|
469
|
-
|
|
471
|
+
|
|
470
472
|
# Mark as temp file if needed
|
|
471
473
|
if is_temp_file:
|
|
472
474
|
result.mark_as_temp_file(cleanup_on_exit=auto_cleanup)
|
|
473
|
-
|
|
475
|
+
|
|
474
476
|
return result
|
|
475
477
|
|
|
476
478
|
finally:
|
|
@@ -519,18 +521,16 @@ class WhatsAppMediaHandler(IMediaHandler):
|
|
|
519
521
|
use_tempfile=True,
|
|
520
522
|
temp_suffix=temp_suffix,
|
|
521
523
|
sender_id=sender_id,
|
|
522
|
-
auto_cleanup=True
|
|
524
|
+
auto_cleanup=True,
|
|
523
525
|
)
|
|
524
|
-
|
|
526
|
+
|
|
525
527
|
try:
|
|
526
528
|
yield result
|
|
527
529
|
finally:
|
|
528
530
|
# Context manager cleanup handled by MediaDownloadResult
|
|
529
531
|
result._cleanup_temp_file()
|
|
530
532
|
|
|
531
|
-
async def get_media_as_bytes(
|
|
532
|
-
self, media_id: str
|
|
533
|
-
) -> MediaDownloadResult:
|
|
533
|
+
async def get_media_as_bytes(self, media_id: str) -> MediaDownloadResult:
|
|
534
534
|
"""
|
|
535
535
|
Download media as bytes without creating any files.
|
|
536
536
|
|
|
@@ -543,9 +543,7 @@ class WhatsAppMediaHandler(IMediaHandler):
|
|
|
543
543
|
MediaDownloadResult with file_data bytes (file_path will be None)
|
|
544
544
|
"""
|
|
545
545
|
return await self.download_media(
|
|
546
|
-
media_id=media_id,
|
|
547
|
-
destination_path=None,
|
|
548
|
-
use_tempfile=False
|
|
546
|
+
media_id=media_id, destination_path=None, use_tempfile=False
|
|
549
547
|
)
|
|
550
548
|
|
|
551
549
|
async def stream_media(
|
wappa/models/__init__.py
CHANGED
|
@@ -9,80 +9,73 @@ Clean Architecture: Domain entities and data transfer objects.
|
|
|
9
9
|
Usage (User Request: Quick access to WhatsApp models for endpoint schemas):
|
|
10
10
|
# Basic message models
|
|
11
11
|
from wappa.models import BasicTextMessage, MessageResult
|
|
12
|
-
|
|
13
|
-
# Interactive models
|
|
12
|
+
|
|
13
|
+
# Interactive models
|
|
14
14
|
from wappa.models import ButtonMessage, ListMessage, CTAMessage
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
# Media models
|
|
17
17
|
from wappa.models import ImageMessage, VideoMessage, AudioMessage
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
# Specialized models
|
|
20
20
|
from wappa.models import ContactMessage, LocationMessage
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# Template models
|
|
23
23
|
from wappa.models import TextTemplateMessage, MediaTemplateMessage
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
26
|
# Re-export WhatsApp models with cleaner path (User Request: Quick access)
|
|
27
27
|
from ..messaging.whatsapp.models import (
|
|
28
|
+
AudioMessage,
|
|
28
29
|
# Basic Models
|
|
29
30
|
BasicTextMessage,
|
|
30
|
-
|
|
31
|
-
ReadStatusMessage,
|
|
32
|
-
|
|
31
|
+
BusinessContact,
|
|
33
32
|
# Interactive Models
|
|
34
33
|
ButtonMessage,
|
|
35
|
-
CTAMessage,
|
|
36
|
-
ListMessage,
|
|
37
|
-
|
|
38
|
-
# Media Models
|
|
39
|
-
MediaType,
|
|
40
|
-
ImageMessage,
|
|
41
|
-
VideoMessage,
|
|
42
|
-
AudioMessage,
|
|
43
|
-
DocumentMessage,
|
|
44
|
-
StickerMessage,
|
|
45
|
-
|
|
46
34
|
# Specialized Models
|
|
47
35
|
ContactCard,
|
|
48
36
|
ContactMessage,
|
|
49
37
|
ContactValidationResult,
|
|
38
|
+
CTAMessage,
|
|
39
|
+
DocumentMessage,
|
|
40
|
+
ImageMessage,
|
|
41
|
+
ListMessage,
|
|
50
42
|
LocationMessage,
|
|
51
43
|
LocationRequestMessage,
|
|
44
|
+
LocationTemplateMessage,
|
|
52
45
|
LocationValidationResult,
|
|
53
|
-
|
|
46
|
+
MediaTemplateMessage,
|
|
47
|
+
# Media Models
|
|
48
|
+
MediaType,
|
|
49
|
+
MessageResult,
|
|
54
50
|
PersonalContact,
|
|
55
|
-
|
|
51
|
+
ReadStatusMessage,
|
|
52
|
+
StickerMessage,
|
|
53
|
+
TemplateMessageStatus,
|
|
54
|
+
TemplateParameter,
|
|
56
55
|
# Template Models
|
|
57
56
|
TemplateType,
|
|
58
|
-
TemplateParameter,
|
|
59
|
-
TextTemplateMessage,
|
|
60
|
-
MediaTemplateMessage,
|
|
61
|
-
LocationTemplateMessage,
|
|
62
|
-
TemplateMessageStatus,
|
|
63
57
|
TemplateValidationResult,
|
|
58
|
+
TextTemplateMessage,
|
|
59
|
+
VideoMessage,
|
|
64
60
|
)
|
|
65
61
|
|
|
66
62
|
__all__ = [
|
|
67
63
|
# Basic Models (User Request: Clean access for endpoint schemas)
|
|
68
64
|
"BasicTextMessage",
|
|
69
|
-
"MessageResult",
|
|
65
|
+
"MessageResult",
|
|
70
66
|
"ReadStatusMessage",
|
|
71
|
-
|
|
72
67
|
# Interactive Models
|
|
73
68
|
"ButtonMessage",
|
|
74
69
|
"CTAMessage",
|
|
75
70
|
"ListMessage",
|
|
76
|
-
|
|
77
71
|
# Media Models
|
|
78
72
|
"MediaType",
|
|
79
73
|
"ImageMessage",
|
|
80
|
-
"VideoMessage",
|
|
74
|
+
"VideoMessage",
|
|
81
75
|
"AudioMessage",
|
|
82
76
|
"DocumentMessage",
|
|
83
77
|
"StickerMessage",
|
|
84
|
-
|
|
85
|
-
# Specialized Models
|
|
78
|
+
# Specialized Models
|
|
86
79
|
"ContactCard",
|
|
87
80
|
"ContactMessage",
|
|
88
81
|
"ContactValidationResult",
|
|
@@ -91,13 +84,12 @@ __all__ = [
|
|
|
91
84
|
"LocationValidationResult",
|
|
92
85
|
"BusinessContact",
|
|
93
86
|
"PersonalContact",
|
|
94
|
-
|
|
95
87
|
# Template Models
|
|
96
88
|
"TemplateType",
|
|
97
89
|
"TemplateParameter",
|
|
98
90
|
"TextTemplateMessage",
|
|
99
|
-
"MediaTemplateMessage",
|
|
91
|
+
"MediaTemplateMessage",
|
|
100
92
|
"LocationTemplateMessage",
|
|
101
93
|
"TemplateMessageStatus",
|
|
102
94
|
"TemplateValidationResult",
|
|
103
|
-
]
|
|
95
|
+
]
|
wappa/persistence/__init__.py
CHANGED
|
@@ -9,47 +9,44 @@ Clean Architecture: Infrastructure layer with cache and repository abstractions.
|
|
|
9
9
|
Usage (User Request: Quick access to create_cache_factory):
|
|
10
10
|
# Cache factory (main request)
|
|
11
11
|
from wappa.persistence import create_cache_factory, get_cache_factory
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
# Cache interfaces
|
|
14
14
|
from wappa.persistence import ICacheFactory, IStateRepository
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
# Specific implementations
|
|
17
17
|
from wappa.persistence.redis import RedisCacheFactory, RedisClient
|
|
18
|
-
from wappa.persistence.json import JSONCacheFactory
|
|
18
|
+
from wappa.persistence.json import JSONCacheFactory
|
|
19
19
|
from wappa.persistence.memory import MemoryCacheFactory
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
# Cache Factory Functions (User Request: Quick access to create_cache_factory)
|
|
23
|
-
from .cache_factory import create_cache_factory, get_cache_factory
|
|
24
|
-
|
|
25
23
|
# Cache Interfaces
|
|
26
24
|
from ..domain.interfaces.cache_factory import ICacheFactory
|
|
27
25
|
from ..domain.interfaces.cache_interface import ICache
|
|
28
|
-
from ..domain.interfaces.state_repository import IStateRepository
|
|
29
|
-
from ..domain.interfaces.user_repository import IUserRepository
|
|
30
|
-
from ..domain.interfaces.tables_repository import ITablesRepository
|
|
31
|
-
from ..domain.interfaces.pubsub_repository import IPubSubRepository
|
|
32
26
|
from ..domain.interfaces.expiry_repository import IExpiryRepository
|
|
33
|
-
from ..domain.interfaces.
|
|
27
|
+
from ..domain.interfaces.pubsub_repository import IPubSubRepository
|
|
34
28
|
|
|
35
29
|
# Repository Factory Interface
|
|
36
30
|
from ..domain.interfaces.repository_factory import IRepositoryFactory
|
|
31
|
+
from ..domain.interfaces.shared_state_repository import ISharedStateRepository
|
|
32
|
+
from ..domain.interfaces.state_repository import IStateRepository
|
|
33
|
+
from ..domain.interfaces.tables_repository import ITablesRepository
|
|
34
|
+
from ..domain.interfaces.user_repository import IUserRepository
|
|
35
|
+
from .cache_factory import create_cache_factory, get_cache_factory
|
|
37
36
|
|
|
38
37
|
__all__ = [
|
|
39
38
|
# Cache Factory Functions (User Request: Main access point)
|
|
40
39
|
"create_cache_factory",
|
|
41
40
|
"get_cache_factory",
|
|
42
|
-
|
|
43
41
|
# Core Interfaces
|
|
44
42
|
"ICacheFactory",
|
|
45
|
-
"ICache",
|
|
43
|
+
"ICache",
|
|
46
44
|
"IRepositoryFactory",
|
|
47
|
-
|
|
48
45
|
# Repository Interfaces
|
|
49
46
|
"IStateRepository",
|
|
50
47
|
"IUserRepository",
|
|
51
|
-
"ITablesRepository",
|
|
48
|
+
"ITablesRepository",
|
|
52
49
|
"IPubSubRepository",
|
|
53
50
|
"IExpiryRepository",
|
|
54
51
|
"ISharedStateRepository",
|
|
55
|
-
]
|
|
52
|
+
]
|
|
@@ -28,7 +28,9 @@ class JSONStateCacheAdapter(ICache):
|
|
|
28
28
|
"""Get cached data by key."""
|
|
29
29
|
return await self._handler.get(key, models=models)
|
|
30
30
|
|
|
31
|
-
async def set(
|
|
31
|
+
async def set(
|
|
32
|
+
self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None
|
|
33
|
+
) -> bool:
|
|
32
34
|
"""Set cached data with optional TTL."""
|
|
33
35
|
return await self._handler.upsert(key, data, ttl=ttl)
|
|
34
36
|
|
|
@@ -84,7 +86,9 @@ class JSONUserCacheAdapter(ICache):
|
|
|
84
86
|
"""Get cached data by key. For user cache, key is ignored as it uses user_id."""
|
|
85
87
|
return await self._handler.get(models=models)
|
|
86
88
|
|
|
87
|
-
async def set(
|
|
89
|
+
async def set(
|
|
90
|
+
self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None
|
|
91
|
+
) -> bool:
|
|
88
92
|
"""Set cached data with optional TTL."""
|
|
89
93
|
return await self._handler.upsert(data, ttl=ttl)
|
|
90
94
|
|
|
@@ -131,15 +135,15 @@ class JSONUserCacheAdapter(ICache):
|
|
|
131
135
|
class JSONTableCacheAdapter(ICache):
|
|
132
136
|
"""
|
|
133
137
|
Adapter that makes JSONTable implement ICache interface.
|
|
134
|
-
|
|
138
|
+
|
|
135
139
|
Table Key Format Guide:
|
|
136
140
|
Use create_table_key(table_name, pkid) to generate proper keys.
|
|
137
|
-
|
|
141
|
+
|
|
138
142
|
Examples:
|
|
139
143
|
# Good - using helper method
|
|
140
144
|
key = cache.create_table_key("user_profiles", "12345")
|
|
141
145
|
await cache.set(key, user_data)
|
|
142
|
-
|
|
146
|
+
|
|
143
147
|
# Also supported - manual format
|
|
144
148
|
key = "user_profiles:12345"
|
|
145
149
|
await cache.set(key, user_data)
|
|
@@ -147,29 +151,29 @@ class JSONTableCacheAdapter(ICache):
|
|
|
147
151
|
|
|
148
152
|
def __init__(self, tenant_id: str):
|
|
149
153
|
self._handler = JSONTable(tenant=tenant_id)
|
|
150
|
-
|
|
154
|
+
|
|
151
155
|
def create_table_key(self, table_name: str, pkid: str) -> str:
|
|
152
156
|
"""
|
|
153
157
|
Create a properly formatted table cache key.
|
|
154
|
-
|
|
158
|
+
|
|
155
159
|
Args:
|
|
156
160
|
table_name: Name of the table (e.g., "user_profiles", "message_logs")
|
|
157
161
|
pkid: Primary key ID (e.g., user_id, message_id)
|
|
158
|
-
|
|
162
|
+
|
|
159
163
|
Returns:
|
|
160
164
|
Formatted key string for use with cache methods
|
|
161
|
-
|
|
165
|
+
|
|
162
166
|
Example:
|
|
163
167
|
key = cache.create_table_key("user_profiles", "12345")
|
|
164
168
|
# Returns: "user_profiles:12345"
|
|
165
169
|
"""
|
|
166
170
|
if not table_name or not pkid:
|
|
167
171
|
raise ValueError("Both table_name and pkid must be provided and non-empty")
|
|
168
|
-
|
|
172
|
+
|
|
169
173
|
# Sanitize inputs to avoid conflicts
|
|
170
174
|
safe_table_name = str(table_name).replace(":", "_")
|
|
171
175
|
safe_pkid = str(pkid).replace(":", "_")
|
|
172
|
-
|
|
176
|
+
|
|
173
177
|
return f"{safe_table_name}:{safe_pkid}"
|
|
174
178
|
|
|
175
179
|
async def get(
|
|
@@ -179,7 +183,9 @@ class JSONTableCacheAdapter(ICache):
|
|
|
179
183
|
table_name, pkid = self._parse_key(key)
|
|
180
184
|
return await self._handler.get(table_name, pkid, models=models)
|
|
181
185
|
|
|
182
|
-
async def set(
|
|
186
|
+
async def set(
|
|
187
|
+
self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None
|
|
188
|
+
) -> bool:
|
|
183
189
|
"""Set cached data with optional TTL."""
|
|
184
190
|
table_name, pkid = self._parse_key(key)
|
|
185
191
|
return await self._handler.upsert(table_name, pkid, data, ttl=ttl)
|
|
@@ -236,36 +242,42 @@ class JSONTableCacheAdapter(ICache):
|
|
|
236
242
|
def _parse_key(self, key: str) -> tuple[str, str]:
|
|
237
243
|
"""
|
|
238
244
|
Parse key into table_name and pkid with validation.
|
|
239
|
-
|
|
245
|
+
|
|
240
246
|
Args:
|
|
241
247
|
key: Cache key in format "table_name:pkid"
|
|
242
|
-
|
|
248
|
+
|
|
243
249
|
Returns:
|
|
244
250
|
Tuple of (table_name, pkid)
|
|
245
|
-
|
|
251
|
+
|
|
246
252
|
Raises:
|
|
247
253
|
ValueError: If key format is invalid
|
|
248
254
|
"""
|
|
249
255
|
if not key:
|
|
250
256
|
raise ValueError("Key cannot be empty")
|
|
251
|
-
|
|
257
|
+
|
|
252
258
|
if ":" not in key:
|
|
253
259
|
raise ValueError(
|
|
254
260
|
f"Invalid table cache key format: '{key}'. "
|
|
255
261
|
f"Expected format: 'table_name:pkid'. "
|
|
256
262
|
f"Use create_table_key(table_name, pkid) to generate proper keys."
|
|
257
263
|
)
|
|
258
|
-
|
|
264
|
+
|
|
259
265
|
parts = key.split(":", 1)
|
|
260
266
|
if len(parts) != 2:
|
|
261
|
-
raise ValueError(
|
|
262
|
-
|
|
267
|
+
raise ValueError(
|
|
268
|
+
f"Invalid table cache key format: '{key}'. Expected exactly one ':' separator."
|
|
269
|
+
)
|
|
270
|
+
|
|
263
271
|
table_name, pkid = parts
|
|
264
|
-
|
|
272
|
+
|
|
265
273
|
if not table_name.strip():
|
|
266
|
-
raise ValueError(
|
|
267
|
-
|
|
274
|
+
raise ValueError(
|
|
275
|
+
f"Invalid table cache key: '{key}'. Table name cannot be empty."
|
|
276
|
+
)
|
|
277
|
+
|
|
268
278
|
if not pkid.strip():
|
|
269
|
-
raise ValueError(
|
|
270
|
-
|
|
271
|
-
|
|
279
|
+
raise ValueError(
|
|
280
|
+
f"Invalid table cache key: '{key}'. Primary key ID cannot be empty."
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
return table_name.strip(), pkid.strip()
|