wappa 0.1.0__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 (211) hide show
  1. wappa/__init__.py +85 -0
  2. wappa/api/__init__.py +1 -0
  3. wappa/api/controllers/__init__.py +10 -0
  4. wappa/api/controllers/webhook_controller.py +441 -0
  5. wappa/api/dependencies/__init__.py +15 -0
  6. wappa/api/dependencies/whatsapp_dependencies.py +220 -0
  7. wappa/api/dependencies/whatsapp_media_dependencies.py +26 -0
  8. wappa/api/middleware/__init__.py +7 -0
  9. wappa/api/middleware/error_handler.py +158 -0
  10. wappa/api/middleware/owner.py +99 -0
  11. wappa/api/middleware/request_logging.py +184 -0
  12. wappa/api/routes/__init__.py +6 -0
  13. wappa/api/routes/health.py +102 -0
  14. wappa/api/routes/webhooks.py +211 -0
  15. wappa/api/routes/whatsapp/__init__.py +15 -0
  16. wappa/api/routes/whatsapp/whatsapp_interactive.py +429 -0
  17. wappa/api/routes/whatsapp/whatsapp_media.py +440 -0
  18. wappa/api/routes/whatsapp/whatsapp_messages.py +195 -0
  19. wappa/api/routes/whatsapp/whatsapp_specialized.py +516 -0
  20. wappa/api/routes/whatsapp/whatsapp_templates.py +431 -0
  21. wappa/api/routes/whatsapp_combined.py +35 -0
  22. wappa/cli/__init__.py +9 -0
  23. wappa/cli/main.py +199 -0
  24. wappa/core/__init__.py +6 -0
  25. wappa/core/config/__init__.py +5 -0
  26. wappa/core/config/settings.py +161 -0
  27. wappa/core/events/__init__.py +41 -0
  28. wappa/core/events/default_handlers.py +642 -0
  29. wappa/core/events/event_dispatcher.py +244 -0
  30. wappa/core/events/event_handler.py +247 -0
  31. wappa/core/events/webhook_factory.py +219 -0
  32. wappa/core/factory/__init__.py +15 -0
  33. wappa/core/factory/plugin.py +68 -0
  34. wappa/core/factory/wappa_builder.py +326 -0
  35. wappa/core/logging/__init__.py +5 -0
  36. wappa/core/logging/context.py +100 -0
  37. wappa/core/logging/logger.py +343 -0
  38. wappa/core/plugins/__init__.py +34 -0
  39. wappa/core/plugins/auth_plugin.py +169 -0
  40. wappa/core/plugins/cors_plugin.py +128 -0
  41. wappa/core/plugins/custom_middleware_plugin.py +182 -0
  42. wappa/core/plugins/database_plugin.py +235 -0
  43. wappa/core/plugins/rate_limit_plugin.py +183 -0
  44. wappa/core/plugins/redis_plugin.py +224 -0
  45. wappa/core/plugins/wappa_core_plugin.py +261 -0
  46. wappa/core/plugins/webhook_plugin.py +253 -0
  47. wappa/core/types.py +108 -0
  48. wappa/core/wappa_app.py +546 -0
  49. wappa/database/__init__.py +18 -0
  50. wappa/database/adapter.py +107 -0
  51. wappa/database/adapters/__init__.py +17 -0
  52. wappa/database/adapters/mysql_adapter.py +187 -0
  53. wappa/database/adapters/postgresql_adapter.py +169 -0
  54. wappa/database/adapters/sqlite_adapter.py +174 -0
  55. wappa/domain/__init__.py +28 -0
  56. wappa/domain/builders/__init__.py +5 -0
  57. wappa/domain/builders/message_builder.py +189 -0
  58. wappa/domain/entities/__init__.py +5 -0
  59. wappa/domain/enums/messenger_platform.py +123 -0
  60. wappa/domain/factories/__init__.py +6 -0
  61. wappa/domain/factories/media_factory.py +450 -0
  62. wappa/domain/factories/message_factory.py +497 -0
  63. wappa/domain/factories/messenger_factory.py +244 -0
  64. wappa/domain/interfaces/__init__.py +32 -0
  65. wappa/domain/interfaces/base_repository.py +94 -0
  66. wappa/domain/interfaces/cache_factory.py +85 -0
  67. wappa/domain/interfaces/cache_interface.py +199 -0
  68. wappa/domain/interfaces/expiry_repository.py +68 -0
  69. wappa/domain/interfaces/media_interface.py +311 -0
  70. wappa/domain/interfaces/messaging_interface.py +523 -0
  71. wappa/domain/interfaces/pubsub_repository.py +151 -0
  72. wappa/domain/interfaces/repository_factory.py +108 -0
  73. wappa/domain/interfaces/shared_state_repository.py +122 -0
  74. wappa/domain/interfaces/state_repository.py +123 -0
  75. wappa/domain/interfaces/tables_repository.py +215 -0
  76. wappa/domain/interfaces/user_repository.py +114 -0
  77. wappa/domain/interfaces/webhooks/__init__.py +1 -0
  78. wappa/domain/models/media_result.py +110 -0
  79. wappa/domain/models/platforms/__init__.py +15 -0
  80. wappa/domain/models/platforms/platform_config.py +104 -0
  81. wappa/domain/services/__init__.py +11 -0
  82. wappa/domain/services/tenant_credentials_service.py +56 -0
  83. wappa/messaging/__init__.py +7 -0
  84. wappa/messaging/whatsapp/__init__.py +1 -0
  85. wappa/messaging/whatsapp/client/__init__.py +5 -0
  86. wappa/messaging/whatsapp/client/whatsapp_client.py +417 -0
  87. wappa/messaging/whatsapp/handlers/__init__.py +13 -0
  88. wappa/messaging/whatsapp/handlers/whatsapp_interactive_handler.py +653 -0
  89. wappa/messaging/whatsapp/handlers/whatsapp_media_handler.py +579 -0
  90. wappa/messaging/whatsapp/handlers/whatsapp_specialized_handler.py +434 -0
  91. wappa/messaging/whatsapp/handlers/whatsapp_template_handler.py +416 -0
  92. wappa/messaging/whatsapp/messenger/__init__.py +5 -0
  93. wappa/messaging/whatsapp/messenger/whatsapp_messenger.py +904 -0
  94. wappa/messaging/whatsapp/models/__init__.py +61 -0
  95. wappa/messaging/whatsapp/models/basic_models.py +65 -0
  96. wappa/messaging/whatsapp/models/interactive_models.py +287 -0
  97. wappa/messaging/whatsapp/models/media_models.py +215 -0
  98. wappa/messaging/whatsapp/models/specialized_models.py +304 -0
  99. wappa/messaging/whatsapp/models/template_models.py +261 -0
  100. wappa/persistence/cache_factory.py +93 -0
  101. wappa/persistence/json/__init__.py +14 -0
  102. wappa/persistence/json/cache_adapters.py +271 -0
  103. wappa/persistence/json/handlers/__init__.py +1 -0
  104. wappa/persistence/json/handlers/state_handler.py +250 -0
  105. wappa/persistence/json/handlers/table_handler.py +263 -0
  106. wappa/persistence/json/handlers/user_handler.py +213 -0
  107. wappa/persistence/json/handlers/utils/__init__.py +1 -0
  108. wappa/persistence/json/handlers/utils/file_manager.py +153 -0
  109. wappa/persistence/json/handlers/utils/key_factory.py +11 -0
  110. wappa/persistence/json/handlers/utils/serialization.py +121 -0
  111. wappa/persistence/json/json_cache_factory.py +76 -0
  112. wappa/persistence/json/storage_manager.py +285 -0
  113. wappa/persistence/memory/__init__.py +14 -0
  114. wappa/persistence/memory/cache_adapters.py +271 -0
  115. wappa/persistence/memory/handlers/__init__.py +1 -0
  116. wappa/persistence/memory/handlers/state_handler.py +250 -0
  117. wappa/persistence/memory/handlers/table_handler.py +280 -0
  118. wappa/persistence/memory/handlers/user_handler.py +213 -0
  119. wappa/persistence/memory/handlers/utils/__init__.py +1 -0
  120. wappa/persistence/memory/handlers/utils/key_factory.py +11 -0
  121. wappa/persistence/memory/handlers/utils/memory_store.py +317 -0
  122. wappa/persistence/memory/handlers/utils/ttl_manager.py +235 -0
  123. wappa/persistence/memory/memory_cache_factory.py +76 -0
  124. wappa/persistence/memory/storage_manager.py +235 -0
  125. wappa/persistence/redis/README.md +699 -0
  126. wappa/persistence/redis/__init__.py +11 -0
  127. wappa/persistence/redis/cache_adapters.py +285 -0
  128. wappa/persistence/redis/ops.py +880 -0
  129. wappa/persistence/redis/redis_cache_factory.py +71 -0
  130. wappa/persistence/redis/redis_client.py +231 -0
  131. wappa/persistence/redis/redis_handler/__init__.py +26 -0
  132. wappa/persistence/redis/redis_handler/state_handler.py +176 -0
  133. wappa/persistence/redis/redis_handler/table.py +158 -0
  134. wappa/persistence/redis/redis_handler/user.py +138 -0
  135. wappa/persistence/redis/redis_handler/utils/__init__.py +12 -0
  136. wappa/persistence/redis/redis_handler/utils/key_factory.py +32 -0
  137. wappa/persistence/redis/redis_handler/utils/serde.py +146 -0
  138. wappa/persistence/redis/redis_handler/utils/tenant_cache.py +268 -0
  139. wappa/persistence/redis/redis_manager.py +189 -0
  140. wappa/processors/__init__.py +6 -0
  141. wappa/processors/base_processor.py +262 -0
  142. wappa/processors/factory.py +550 -0
  143. wappa/processors/whatsapp_processor.py +810 -0
  144. wappa/schemas/__init__.py +6 -0
  145. wappa/schemas/core/__init__.py +71 -0
  146. wappa/schemas/core/base_message.py +499 -0
  147. wappa/schemas/core/base_status.py +322 -0
  148. wappa/schemas/core/base_webhook.py +312 -0
  149. wappa/schemas/core/types.py +253 -0
  150. wappa/schemas/core/webhook_interfaces/__init__.py +48 -0
  151. wappa/schemas/core/webhook_interfaces/base_components.py +293 -0
  152. wappa/schemas/core/webhook_interfaces/universal_webhooks.py +348 -0
  153. wappa/schemas/factory.py +754 -0
  154. wappa/schemas/webhooks/__init__.py +3 -0
  155. wappa/schemas/whatsapp/__init__.py +6 -0
  156. wappa/schemas/whatsapp/base_models.py +285 -0
  157. wappa/schemas/whatsapp/message_types/__init__.py +93 -0
  158. wappa/schemas/whatsapp/message_types/audio.py +350 -0
  159. wappa/schemas/whatsapp/message_types/button.py +267 -0
  160. wappa/schemas/whatsapp/message_types/contact.py +464 -0
  161. wappa/schemas/whatsapp/message_types/document.py +421 -0
  162. wappa/schemas/whatsapp/message_types/errors.py +195 -0
  163. wappa/schemas/whatsapp/message_types/image.py +424 -0
  164. wappa/schemas/whatsapp/message_types/interactive.py +430 -0
  165. wappa/schemas/whatsapp/message_types/location.py +416 -0
  166. wappa/schemas/whatsapp/message_types/order.py +372 -0
  167. wappa/schemas/whatsapp/message_types/reaction.py +271 -0
  168. wappa/schemas/whatsapp/message_types/sticker.py +328 -0
  169. wappa/schemas/whatsapp/message_types/system.py +317 -0
  170. wappa/schemas/whatsapp/message_types/text.py +411 -0
  171. wappa/schemas/whatsapp/message_types/unsupported.py +273 -0
  172. wappa/schemas/whatsapp/message_types/video.py +344 -0
  173. wappa/schemas/whatsapp/status_models.py +479 -0
  174. wappa/schemas/whatsapp/validators.py +454 -0
  175. wappa/schemas/whatsapp/webhook_container.py +438 -0
  176. wappa/webhooks/__init__.py +17 -0
  177. wappa/webhooks/core/__init__.py +71 -0
  178. wappa/webhooks/core/base_message.py +499 -0
  179. wappa/webhooks/core/base_status.py +322 -0
  180. wappa/webhooks/core/base_webhook.py +312 -0
  181. wappa/webhooks/core/types.py +253 -0
  182. wappa/webhooks/core/webhook_interfaces/__init__.py +48 -0
  183. wappa/webhooks/core/webhook_interfaces/base_components.py +293 -0
  184. wappa/webhooks/core/webhook_interfaces/universal_webhooks.py +441 -0
  185. wappa/webhooks/factory.py +754 -0
  186. wappa/webhooks/whatsapp/__init__.py +6 -0
  187. wappa/webhooks/whatsapp/base_models.py +285 -0
  188. wappa/webhooks/whatsapp/message_types/__init__.py +93 -0
  189. wappa/webhooks/whatsapp/message_types/audio.py +350 -0
  190. wappa/webhooks/whatsapp/message_types/button.py +267 -0
  191. wappa/webhooks/whatsapp/message_types/contact.py +464 -0
  192. wappa/webhooks/whatsapp/message_types/document.py +421 -0
  193. wappa/webhooks/whatsapp/message_types/errors.py +195 -0
  194. wappa/webhooks/whatsapp/message_types/image.py +424 -0
  195. wappa/webhooks/whatsapp/message_types/interactive.py +430 -0
  196. wappa/webhooks/whatsapp/message_types/location.py +416 -0
  197. wappa/webhooks/whatsapp/message_types/order.py +372 -0
  198. wappa/webhooks/whatsapp/message_types/reaction.py +271 -0
  199. wappa/webhooks/whatsapp/message_types/sticker.py +328 -0
  200. wappa/webhooks/whatsapp/message_types/system.py +317 -0
  201. wappa/webhooks/whatsapp/message_types/text.py +411 -0
  202. wappa/webhooks/whatsapp/message_types/unsupported.py +273 -0
  203. wappa/webhooks/whatsapp/message_types/video.py +344 -0
  204. wappa/webhooks/whatsapp/status_models.py +479 -0
  205. wappa/webhooks/whatsapp/validators.py +454 -0
  206. wappa/webhooks/whatsapp/webhook_container.py +438 -0
  207. wappa-0.1.0.dist-info/METADATA +269 -0
  208. wappa-0.1.0.dist-info/RECORD +211 -0
  209. wappa-0.1.0.dist-info/WHEEL +4 -0
  210. wappa-0.1.0.dist-info/entry_points.txt +2 -0
  211. wappa-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,121 @@
1
+ """
2
+ JSON serialization utilities with BaseModel support.
3
+
4
+ Provides JSON-based serialization compatible with Redis patterns
5
+ while optimizing for file storage.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ from datetime import datetime
11
+ from typing import Any
12
+
13
+ from pydantic import BaseModel
14
+
15
+ logger = logging.getLogger("JSONSerde")
16
+
17
+
18
+ def _datetime_handler(obj: Any) -> str:
19
+ """Handle datetime objects during JSON serialization."""
20
+ if isinstance(obj, datetime):
21
+ return obj.isoformat()
22
+ raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
23
+
24
+
25
+ def _convert_iso_strings_to_datetime(obj: Any) -> Any:
26
+ """Recursively convert ISO datetime strings back to datetime objects."""
27
+ if isinstance(obj, str):
28
+ # Try to parse as ISO datetime
29
+ try:
30
+ # Check if it looks like an ISO datetime (basic heuristic)
31
+ if "T" in obj and len(obj) >= 19: # YYYY-MM-DDTHH:MM:SS minimum
32
+ return datetime.fromisoformat(obj.replace("Z", "+00:00"))
33
+ except (ValueError, AttributeError):
34
+ pass
35
+ return obj
36
+ elif isinstance(obj, dict):
37
+ return {k: _convert_iso_strings_to_datetime(v) for k, v in obj.items()}
38
+ elif isinstance(obj, list):
39
+ return [_convert_iso_strings_to_datetime(item) for item in obj]
40
+ else:
41
+ return obj
42
+
43
+
44
+ def serialize_for_json(obj: Any) -> Any:
45
+ """Serialize Python object for JSON storage."""
46
+ if obj is None:
47
+ return None
48
+ if isinstance(obj, BaseModel):
49
+ return obj.model_dump()
50
+ if isinstance(obj, datetime):
51
+ return obj.isoformat()
52
+ if isinstance(obj, dict):
53
+ return {k: serialize_for_json(v) for k, v in obj.items()}
54
+ if isinstance(obj, list):
55
+ return [serialize_for_json(item) for item in obj]
56
+ return obj
57
+
58
+
59
+ def deserialize_from_json(data: Any, model: type[BaseModel] | None = None) -> Any:
60
+ """Deserialize data from JSON storage."""
61
+ if data is None:
62
+ return None
63
+
64
+ # Convert datetime strings back to datetime objects
65
+ data = _convert_iso_strings_to_datetime(data)
66
+
67
+ if model is not None:
68
+ return model.model_validate(data)
69
+
70
+ return data
71
+
72
+
73
+ def create_cache_file_data(data: dict[str, Any], ttl: int | None = None) -> dict[str, Any]:
74
+ """Create JSON cache file structure with metadata."""
75
+ now = datetime.now()
76
+ expires_at = None
77
+ if ttl:
78
+ expires_at = datetime.fromtimestamp(now.timestamp() + ttl)
79
+
80
+ return {
81
+ "_metadata": {
82
+ "created_at": now.isoformat(),
83
+ "expires_at": expires_at.isoformat() if expires_at else None,
84
+ "version": "1.0"
85
+ },
86
+ "data": serialize_for_json(data)
87
+ }
88
+
89
+
90
+ def extract_cache_file_data(file_data: dict[str, Any]) -> dict[str, Any] | None:
91
+ """Extract data from JSON cache file, checking expiration."""
92
+ if not isinstance(file_data, dict):
93
+ return None
94
+
95
+ metadata = file_data.get("_metadata", {})
96
+ expires_at_str = metadata.get("expires_at")
97
+
98
+ # Check expiration
99
+ if expires_at_str:
100
+ try:
101
+ expires_at = datetime.fromisoformat(expires_at_str)
102
+ if datetime.now() > expires_at:
103
+ return None # Expired
104
+ except ValueError:
105
+ logger.warning(f"Invalid expires_at format: {expires_at_str}")
106
+
107
+ return file_data.get("data", {})
108
+
109
+
110
+ def to_json_string(data: Any) -> str:
111
+ """Convert data to JSON string."""
112
+ return json.dumps(data, ensure_ascii=False, indent=2, default=_datetime_handler)
113
+
114
+
115
+ def from_json_string(json_str: str) -> Any:
116
+ """Convert JSON string to data."""
117
+ try:
118
+ return json.loads(json_str)
119
+ except json.JSONDecodeError as e:
120
+ logger.error(f"Failed to parse JSON: {e}")
121
+ raise
@@ -0,0 +1,76 @@
1
+ """
2
+ JSON cache factory implementation for Wappa framework.
3
+
4
+ Creates JSON-backed cache instances using file-based storage
5
+ with ICache adapters for uniform interface.
6
+ """
7
+
8
+ from ..domain.interfaces.cache_factory import ICacheFactory
9
+ from ..domain.interfaces.cache_interface import ICache
10
+ from .cache_adapters import (
11
+ JSONStateCacheAdapter,
12
+ JSONTableCacheAdapter,
13
+ JSONUserCacheAdapter,
14
+ )
15
+
16
+
17
+ class JSONCacheFactory(ICacheFactory):
18
+ """
19
+ Factory for creating JSON-backed cache instances.
20
+
21
+ Uses file-based JSON storage with proper file management:
22
+ - State cache: Uses states subdirectory
23
+ - User cache: Uses users subdirectory
24
+ - Table cache: Uses tables subdirectory
25
+
26
+ All instances implement the ICache interface through adapters.
27
+
28
+ Context (tenant_id, user_id) is injected at construction time, eliminating
29
+ manual parameter passing.
30
+
31
+ Cache files are automatically created in {project_root}/cache/ directory structure.
32
+ """
33
+
34
+ def __init__(self, tenant_id: str, user_id: str):
35
+ """Initialize JSON cache factory with context injection."""
36
+ super().__init__(tenant_id, user_id)
37
+
38
+ def create_state_cache(self) -> ICache:
39
+ """
40
+ Create JSON state cache instance.
41
+
42
+ Uses context (tenant_id, user_id) injected at construction time.
43
+ Stores data in {project_root}/cache/states/ directory.
44
+
45
+ Returns:
46
+ ICache adapter wrapping JSONStateHandler
47
+ """
48
+ return JSONStateCacheAdapter(
49
+ tenant_id=self.tenant_id, user_id=self.user_id
50
+ )
51
+
52
+ def create_user_cache(self) -> ICache:
53
+ """
54
+ Create JSON user cache instance.
55
+
56
+ Uses context (tenant_id, user_id) injected at construction time.
57
+ Stores data in {project_root}/cache/users/ directory.
58
+
59
+ Returns:
60
+ ICache adapter wrapping JSONUser
61
+ """
62
+ return JSONUserCacheAdapter(
63
+ tenant_id=self.tenant_id, user_id=self.user_id
64
+ )
65
+
66
+ def create_table_cache(self) -> ICache:
67
+ """
68
+ Create JSON table cache instance.
69
+
70
+ Uses context (tenant_id) injected at construction time.
71
+ Stores data in {project_root}/cache/tables/ directory.
72
+
73
+ Returns:
74
+ ICache adapter wrapping JSONTable
75
+ """
76
+ return JSONTableCacheAdapter(tenant_id=self.tenant_id)
@@ -0,0 +1,285 @@
1
+ """
2
+ JSON storage manager for coordinating cache operations.
3
+
4
+ Provides high-level interface for JSON cache operations with TTL support,
5
+ BaseModel serialization, and atomic file operations.
6
+ """
7
+
8
+ import logging
9
+ from datetime import datetime
10
+ from typing import Any, Dict, Optional
11
+
12
+ from pydantic import BaseModel
13
+
14
+ from .handlers.utils.file_manager import file_manager
15
+ from .handlers.utils.serialization import (
16
+ create_cache_file_data,
17
+ deserialize_from_json,
18
+ extract_cache_file_data,
19
+ serialize_for_json,
20
+ )
21
+
22
+ logger = logging.getLogger("JSONStorageManager")
23
+
24
+
25
+ class JSONStorageManager:
26
+ """High-level JSON storage operations manager."""
27
+
28
+ def __init__(self):
29
+ # Ensure cache directories exist on initialization
30
+ file_manager.ensure_cache_directories()
31
+
32
+ async def get(
33
+ self,
34
+ cache_type: str,
35
+ tenant_id: str,
36
+ user_id: Optional[str],
37
+ key: str,
38
+ model: type[BaseModel] | None = None
39
+ ) -> Any:
40
+ """
41
+ Get value from JSON cache.
42
+
43
+ Args:
44
+ cache_type: "users", "tables", or "states"
45
+ tenant_id: Tenant identifier
46
+ user_id: User identifier (required for users/states)
47
+ key: Cache key
48
+ model: Optional BaseModel for deserialization
49
+
50
+ Returns:
51
+ Cached value or None if not found/expired
52
+ """
53
+ try:
54
+ file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
55
+ file_data = await file_manager.read_file(file_path)
56
+
57
+ if not file_data:
58
+ return None
59
+
60
+ # Extract data and check expiration
61
+ cache_data = extract_cache_file_data(file_data)
62
+ if cache_data is None:
63
+ # Expired - delete the file
64
+ await file_manager.delete_file(file_path)
65
+ return None
66
+
67
+ # Get specific key data
68
+ if key not in cache_data:
69
+ return None
70
+
71
+ value_data = cache_data[key]
72
+ return deserialize_from_json(value_data, model)
73
+
74
+ except Exception as e:
75
+ logger.error(f"Failed to get key '{key}' from {cache_type} cache: {e}")
76
+ return None
77
+
78
+ async def set(
79
+ self,
80
+ cache_type: str,
81
+ tenant_id: str,
82
+ user_id: Optional[str],
83
+ key: str,
84
+ value: Any,
85
+ ttl: Optional[int] = None
86
+ ) -> bool:
87
+ """
88
+ Set value in JSON cache.
89
+
90
+ Args:
91
+ cache_type: "users", "tables", or "states"
92
+ tenant_id: Tenant identifier
93
+ user_id: User identifier (required for users/states)
94
+ key: Cache key
95
+ value: Value to cache
96
+ ttl: Time to live in seconds
97
+
98
+ Returns:
99
+ True if successful, False otherwise
100
+ """
101
+ try:
102
+ file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
103
+
104
+ # Read existing data
105
+ file_data = await file_manager.read_file(file_path)
106
+ cache_data = extract_cache_file_data(file_data) if file_data else {}
107
+ if cache_data is None:
108
+ cache_data = {}
109
+
110
+ # Update key
111
+ cache_data[key] = serialize_for_json(value)
112
+
113
+ # Create new file data with TTL
114
+ new_file_data = create_cache_file_data(cache_data, ttl)
115
+
116
+ # Write file
117
+ return await file_manager.write_file(file_path, new_file_data)
118
+
119
+ except Exception as e:
120
+ logger.error(f"Failed to set key '{key}' in {cache_type} cache: {e}")
121
+ return False
122
+
123
+ async def delete(
124
+ self,
125
+ cache_type: str,
126
+ tenant_id: str,
127
+ user_id: Optional[str],
128
+ key: str
129
+ ) -> bool:
130
+ """
131
+ Delete key from JSON cache.
132
+
133
+ Args:
134
+ cache_type: "users", "tables", or "states"
135
+ tenant_id: Tenant identifier
136
+ user_id: User identifier (required for users/states)
137
+ key: Cache key to delete
138
+
139
+ Returns:
140
+ True if deleted or didn't exist, False on error
141
+ """
142
+ try:
143
+ file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
144
+
145
+ # Read existing data
146
+ file_data = await file_manager.read_file(file_path)
147
+ if not file_data:
148
+ return True # Already doesn't exist
149
+
150
+ cache_data = extract_cache_file_data(file_data)
151
+ if cache_data is None or key not in cache_data:
152
+ return True # Already doesn't exist
153
+
154
+ # Remove key
155
+ del cache_data[key]
156
+
157
+ # If no keys left, delete the file
158
+ if not cache_data:
159
+ return await file_manager.delete_file(file_path)
160
+
161
+ # Otherwise update file
162
+ new_file_data = create_cache_file_data(cache_data)
163
+ return await file_manager.write_file(file_path, new_file_data)
164
+
165
+ except Exception as e:
166
+ logger.error(f"Failed to delete key '{key}' from {cache_type} cache: {e}")
167
+ return False
168
+
169
+ async def exists(
170
+ self,
171
+ cache_type: str,
172
+ tenant_id: str,
173
+ user_id: Optional[str],
174
+ key: str
175
+ ) -> bool:
176
+ """
177
+ Check if key exists in JSON cache.
178
+
179
+ Args:
180
+ cache_type: "users", "tables", or "states"
181
+ tenant_id: Tenant identifier
182
+ user_id: User identifier (required for users/states)
183
+ key: Cache key to check
184
+
185
+ Returns:
186
+ True if exists and not expired, False otherwise
187
+ """
188
+ try:
189
+ file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
190
+ file_data = await file_manager.read_file(file_path)
191
+
192
+ if not file_data:
193
+ return False
194
+
195
+ cache_data = extract_cache_file_data(file_data)
196
+ if cache_data is None:
197
+ # Expired - delete the file
198
+ await file_manager.delete_file(file_path)
199
+ return False
200
+
201
+ return key in cache_data
202
+
203
+ except Exception as e:
204
+ logger.error(f"Failed to check existence of key '{key}' in {cache_type} cache: {e}")
205
+ return False
206
+
207
+ async def get_ttl(
208
+ self,
209
+ cache_type: str,
210
+ tenant_id: str,
211
+ user_id: Optional[str]
212
+ ) -> int:
213
+ """
214
+ Get remaining TTL for cache file.
215
+
216
+ Returns:
217
+ Remaining TTL in seconds, -1 if no expiry, -2 if doesn't exist
218
+ """
219
+ try:
220
+ file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
221
+ file_data = await file_manager.read_file(file_path)
222
+
223
+ if not file_data:
224
+ return -2 # Doesn't exist
225
+
226
+ metadata = file_data.get("_metadata", {})
227
+ expires_at_str = metadata.get("expires_at")
228
+
229
+ if not expires_at_str:
230
+ return -1 # No expiry
231
+
232
+ try:
233
+ expires_at = datetime.fromisoformat(expires_at_str)
234
+ now = datetime.now()
235
+
236
+ if now >= expires_at:
237
+ return -2 # Already expired
238
+
239
+ return int((expires_at - now).total_seconds())
240
+
241
+ except ValueError:
242
+ return -1 # Invalid expiry format, treat as no expiry
243
+
244
+ except Exception as e:
245
+ logger.error(f"Failed to get TTL for {cache_type} cache: {e}")
246
+ return -2
247
+
248
+ async def set_ttl(
249
+ self,
250
+ cache_type: str,
251
+ tenant_id: str,
252
+ user_id: Optional[str],
253
+ ttl: int
254
+ ) -> bool:
255
+ """
256
+ Set TTL for cache file.
257
+
258
+ Args:
259
+ ttl: Time to live in seconds
260
+
261
+ Returns:
262
+ True if successful, False otherwise
263
+ """
264
+ try:
265
+ file_path = file_manager.get_cache_file_path(cache_type, tenant_id, user_id)
266
+ file_data = await file_manager.read_file(file_path)
267
+
268
+ if not file_data:
269
+ return False # File doesn't exist
270
+
271
+ cache_data = extract_cache_file_data(file_data)
272
+ if cache_data is None:
273
+ return False # Already expired
274
+
275
+ # Create new file data with updated TTL
276
+ new_file_data = create_cache_file_data(cache_data, ttl)
277
+ return await file_manager.write_file(file_path, new_file_data)
278
+
279
+ except Exception as e:
280
+ logger.error(f"Failed to set TTL for {cache_type} cache: {e}")
281
+ return False
282
+
283
+
284
+ # Global storage manager instance
285
+ storage_manager = JSONStorageManager()
@@ -0,0 +1,14 @@
1
+ """
2
+ Memory-based cache implementation for Wappa framework.
3
+
4
+ Provides high-speed in-memory cache storage with TTL support.
5
+ Suitable for development, testing, and single-process deployments.
6
+
7
+ Usage:
8
+ wappa = Wappa(cache="memory")
9
+ # Data will be stored in memory with automatic TTL cleanup
10
+ """
11
+
12
+ from .memory_cache_factory import MemoryCacheFactory
13
+
14
+ __all__ = ["MemoryCacheFactory"]