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
@@ -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, BinaryIO, AsyncContextManager
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(response_content_type, "")
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(suffix=extension, prefix="wappa_media_")
424
+ temp_fd, temp_path = tempfile.mkstemp(
425
+ suffix=extension, prefix="wappa_media_"
426
+ )
423
427
  try:
424
- with os.fdopen(temp_fd, 'wb') as temp_file:
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
- try:
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
- MessageResult,
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
- BusinessContact,
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
+ ]
@@ -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.shared_state_repository import ISharedStateRepository
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
+ ]
@@ -4,7 +4,6 @@ Main cache factory selector for Wappa framework.
4
4
  Provides factory selector based on cache type configuration.
5
5
  """
6
6
 
7
-
8
7
  from ..domain.interfaces.cache_factory import ICacheFactory
9
8
 
10
9
 
@@ -11,4 +11,4 @@ Usage:
11
11
 
12
12
  from .json_cache_factory import JSONCacheFactory
13
13
 
14
- __all__ = ["JSONCacheFactory"]
14
+ __all__ = ["JSONCacheFactory"]
@@ -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(self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None) -> bool:
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(self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None) -> bool:
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(self, key: str, data: dict[str, Any] | BaseModel, ttl: int | None = None) -> bool:
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(f"Invalid table cache key format: '{key}'. Expected exactly one ':' separator.")
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(f"Invalid table cache key: '{key}'. Table name cannot be empty.")
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(f"Invalid table cache key: '{key}'. Primary key ID cannot be empty.")
270
-
271
- return table_name.strip(), pkid.strip()
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()