django-nativemojo 0.1.15__py3-none-any.whl → 0.1.17__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.
Files changed (221) hide show
  1. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/METADATA +3 -2
  2. django_nativemojo-0.1.17.dist-info/RECORD +302 -0
  3. mojo/__init__.py +1 -1
  4. mojo/apps/account/management/commands/serializer_admin.py +121 -1
  5. mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
  6. mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
  7. mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
  8. mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
  9. mojo/apps/account/migrations/0010_group_avatar.py +20 -0
  10. mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
  11. mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
  12. mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
  13. mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
  14. mojo/apps/account/models/__init__.py +2 -0
  15. mojo/apps/account/models/device.py +279 -0
  16. mojo/apps/account/models/group.py +294 -8
  17. mojo/apps/account/models/member.py +14 -1
  18. mojo/apps/account/models/push/__init__.py +4 -0
  19. mojo/apps/account/models/push/config.py +112 -0
  20. mojo/apps/account/models/push/delivery.py +93 -0
  21. mojo/apps/account/models/push/device.py +66 -0
  22. mojo/apps/account/models/push/template.py +99 -0
  23. mojo/apps/account/models/user.py +190 -17
  24. mojo/apps/account/rest/__init__.py +2 -0
  25. mojo/apps/account/rest/device.py +39 -0
  26. mojo/apps/account/rest/group.py +8 -0
  27. mojo/apps/account/rest/push.py +187 -0
  28. mojo/apps/account/rest/user.py +95 -5
  29. mojo/apps/account/services/__init__.py +1 -0
  30. mojo/apps/account/services/push.py +363 -0
  31. mojo/apps/aws/migrations/0001_initial.py +206 -0
  32. mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
  33. mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
  34. mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
  35. mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
  36. mojo/apps/aws/models/__init__.py +19 -0
  37. mojo/apps/aws/models/email_attachment.py +99 -0
  38. mojo/apps/aws/models/email_domain.py +218 -0
  39. mojo/apps/aws/models/email_template.py +132 -0
  40. mojo/apps/aws/models/incoming_email.py +197 -0
  41. mojo/apps/aws/models/mailbox.py +288 -0
  42. mojo/apps/aws/models/sent_message.py +175 -0
  43. mojo/apps/aws/rest/__init__.py +6 -0
  44. mojo/apps/aws/rest/email.py +33 -0
  45. mojo/apps/aws/rest/email_ops.py +183 -0
  46. mojo/apps/aws/rest/messages.py +32 -0
  47. mojo/apps/aws/rest/send.py +101 -0
  48. mojo/apps/aws/rest/sns.py +403 -0
  49. mojo/apps/aws/rest/templates.py +19 -0
  50. mojo/apps/aws/services/__init__.py +32 -0
  51. mojo/apps/aws/services/email.py +390 -0
  52. mojo/apps/aws/services/email_ops.py +548 -0
  53. mojo/apps/docit/__init__.py +6 -0
  54. mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
  55. mojo/apps/docit/markdown_plugins/toc.py +12 -0
  56. mojo/apps/docit/migrations/0001_initial.py +113 -0
  57. mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
  58. mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
  59. mojo/apps/docit/models/__init__.py +17 -0
  60. mojo/apps/docit/models/asset.py +231 -0
  61. mojo/apps/docit/models/book.py +227 -0
  62. mojo/apps/docit/models/page.py +319 -0
  63. mojo/apps/docit/models/page_revision.py +203 -0
  64. mojo/apps/docit/rest/__init__.py +10 -0
  65. mojo/apps/docit/rest/asset.py +17 -0
  66. mojo/apps/docit/rest/book.py +22 -0
  67. mojo/apps/docit/rest/page.py +22 -0
  68. mojo/apps/docit/rest/page_revision.py +17 -0
  69. mojo/apps/docit/services/__init__.py +11 -0
  70. mojo/apps/docit/services/docit.py +315 -0
  71. mojo/apps/docit/services/markdown.py +44 -0
  72. mojo/apps/fileman/backends/s3.py +209 -0
  73. mojo/apps/fileman/models/file.py +45 -9
  74. mojo/apps/fileman/models/manager.py +269 -3
  75. mojo/apps/incident/migrations/0007_event_uid.py +18 -0
  76. mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
  77. mojo/apps/incident/migrations/0009_incident_status.py +18 -0
  78. mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
  79. mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
  80. mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
  81. mojo/apps/incident/models/__init__.py +1 -0
  82. mojo/apps/incident/models/event.py +35 -0
  83. mojo/apps/incident/models/incident.py +2 -0
  84. mojo/apps/incident/models/ticket.py +62 -0
  85. mojo/apps/incident/reporter.py +21 -3
  86. mojo/apps/incident/rest/__init__.py +1 -0
  87. mojo/apps/incident/rest/ticket.py +43 -0
  88. mojo/apps/jobs/__init__.py +489 -0
  89. mojo/apps/jobs/adapters.py +24 -0
  90. mojo/apps/jobs/cli.py +616 -0
  91. mojo/apps/jobs/daemon.py +370 -0
  92. mojo/apps/jobs/examples/sample_jobs.py +376 -0
  93. mojo/apps/jobs/examples/webhook_examples.py +203 -0
  94. mojo/apps/jobs/handlers/__init__.py +5 -0
  95. mojo/apps/jobs/handlers/webhook.py +317 -0
  96. mojo/apps/jobs/job_engine.py +734 -0
  97. mojo/apps/jobs/keys.py +203 -0
  98. mojo/apps/jobs/local_queue.py +363 -0
  99. mojo/apps/jobs/management/__init__.py +3 -0
  100. mojo/apps/jobs/management/commands/__init__.py +3 -0
  101. mojo/apps/jobs/manager.py +1327 -0
  102. mojo/apps/jobs/migrations/0001_initial.py +97 -0
  103. mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
  104. mojo/apps/jobs/models/__init__.py +6 -0
  105. mojo/apps/jobs/models/job.py +441 -0
  106. mojo/apps/jobs/rest/__init__.py +2 -0
  107. mojo/apps/jobs/rest/control.py +466 -0
  108. mojo/apps/jobs/rest/jobs.py +421 -0
  109. mojo/apps/jobs/scheduler.py +571 -0
  110. mojo/apps/jobs/services/__init__.py +6 -0
  111. mojo/apps/jobs/services/job_actions.py +465 -0
  112. mojo/apps/jobs/settings.py +209 -0
  113. mojo/apps/logit/models/log.py +3 -0
  114. mojo/apps/metrics/__init__.py +8 -1
  115. mojo/apps/metrics/redis_metrics.py +198 -0
  116. mojo/apps/metrics/rest/__init__.py +3 -0
  117. mojo/apps/metrics/rest/categories.py +266 -0
  118. mojo/apps/metrics/rest/helpers.py +48 -0
  119. mojo/apps/metrics/rest/permissions.py +99 -0
  120. mojo/apps/metrics/rest/values.py +277 -0
  121. mojo/apps/metrics/utils.py +17 -0
  122. mojo/decorators/http.py +40 -1
  123. mojo/helpers/aws/__init__.py +11 -7
  124. mojo/helpers/aws/inbound_email.py +309 -0
  125. mojo/helpers/aws/kms.py +413 -0
  126. mojo/helpers/aws/ses_domain.py +959 -0
  127. mojo/helpers/crypto/__init__.py +1 -1
  128. mojo/helpers/crypto/utils.py +15 -0
  129. mojo/helpers/location/__init__.py +2 -0
  130. mojo/helpers/location/countries.py +262 -0
  131. mojo/helpers/location/geolocation.py +196 -0
  132. mojo/helpers/logit.py +37 -0
  133. mojo/helpers/redis/__init__.py +2 -0
  134. mojo/helpers/redis/adapter.py +606 -0
  135. mojo/helpers/redis/client.py +48 -0
  136. mojo/helpers/redis/pool.py +225 -0
  137. mojo/helpers/request.py +8 -0
  138. mojo/helpers/response.py +8 -0
  139. mojo/middleware/auth.py +1 -1
  140. mojo/middleware/cors.py +40 -0
  141. mojo/middleware/logging.py +131 -12
  142. mojo/middleware/mojo.py +5 -0
  143. mojo/models/rest.py +271 -57
  144. mojo/models/secrets.py +86 -0
  145. mojo/serializers/__init__.py +16 -10
  146. mojo/serializers/core/__init__.py +90 -0
  147. mojo/serializers/core/cache/__init__.py +121 -0
  148. mojo/serializers/core/cache/backends.py +518 -0
  149. mojo/serializers/core/cache/base.py +102 -0
  150. mojo/serializers/core/cache/disabled.py +181 -0
  151. mojo/serializers/core/cache/memory.py +287 -0
  152. mojo/serializers/core/cache/redis.py +533 -0
  153. mojo/serializers/core/cache/utils.py +454 -0
  154. mojo/serializers/{manager.py → core/manager.py} +53 -4
  155. mojo/serializers/core/serializer.py +475 -0
  156. mojo/serializers/{advanced/formats → formats}/csv.py +116 -139
  157. mojo/serializers/suggested_improvements.md +388 -0
  158. testit/client.py +1 -1
  159. testit/helpers.py +14 -0
  160. testit/runner.py +23 -6
  161. django_nativemojo-0.1.15.dist-info/RECORD +0 -234
  162. mojo/apps/notify/README.md +0 -91
  163. mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
  164. mojo/apps/notify/admin.py +0 -52
  165. mojo/apps/notify/handlers/example_handlers.py +0 -516
  166. mojo/apps/notify/handlers/ses/__init__.py +0 -25
  167. mojo/apps/notify/handlers/ses/complaint.py +0 -25
  168. mojo/apps/notify/handlers/ses/message.py +0 -86
  169. mojo/apps/notify/management/commands/__init__.py +0 -1
  170. mojo/apps/notify/management/commands/process_notifications.py +0 -370
  171. mojo/apps/notify/mod +0 -0
  172. mojo/apps/notify/models/__init__.py +0 -12
  173. mojo/apps/notify/models/account.py +0 -128
  174. mojo/apps/notify/models/attachment.py +0 -24
  175. mojo/apps/notify/models/bounce.py +0 -68
  176. mojo/apps/notify/models/complaint.py +0 -40
  177. mojo/apps/notify/models/inbox.py +0 -113
  178. mojo/apps/notify/models/inbox_message.py +0 -173
  179. mojo/apps/notify/models/outbox.py +0 -129
  180. mojo/apps/notify/models/outbox_message.py +0 -288
  181. mojo/apps/notify/models/template.py +0 -30
  182. mojo/apps/notify/providers/aws.py +0 -73
  183. mojo/apps/notify/rest/ses.py +0 -0
  184. mojo/apps/notify/utils/__init__.py +0 -2
  185. mojo/apps/notify/utils/notifications.py +0 -404
  186. mojo/apps/notify/utils/parsing.py +0 -202
  187. mojo/apps/notify/utils/render.py +0 -144
  188. mojo/apps/tasks/README.md +0 -118
  189. mojo/apps/tasks/__init__.py +0 -44
  190. mojo/apps/tasks/manager.py +0 -644
  191. mojo/apps/tasks/rest/__init__.py +0 -2
  192. mojo/apps/tasks/rest/hooks.py +0 -0
  193. mojo/apps/tasks/rest/tasks.py +0 -76
  194. mojo/apps/tasks/runner.py +0 -439
  195. mojo/apps/tasks/task.py +0 -99
  196. mojo/apps/tasks/tq_handlers.py +0 -132
  197. mojo/helpers/crypto/__pycache__/hash.cpython-310.pyc +0 -0
  198. mojo/helpers/crypto/__pycache__/sign.cpython-310.pyc +0 -0
  199. mojo/helpers/crypto/__pycache__/utils.cpython-310.pyc +0 -0
  200. mojo/helpers/redis.py +0 -10
  201. mojo/models/meta.py +0 -262
  202. mojo/serializers/advanced/README.md +0 -363
  203. mojo/serializers/advanced/__init__.py +0 -247
  204. mojo/serializers/advanced/formats/__init__.py +0 -28
  205. mojo/serializers/advanced/formats/excel.py +0 -516
  206. mojo/serializers/advanced/formats/json.py +0 -239
  207. mojo/serializers/advanced/formats/response.py +0 -485
  208. mojo/serializers/advanced/serializer.py +0 -568
  209. mojo/serializers/optimized.py +0 -618
  210. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/LICENSE +0 -0
  211. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/NOTICE +0 -0
  212. {django_nativemojo-0.1.15.dist-info → django_nativemojo-0.1.17.dist-info}/WHEEL +0 -0
  213. /mojo/apps/{notify → aws/migrations}/__init__.py +0 -0
  214. /mojo/apps/{notify/handlers → docit/markdown_plugins}/__init__.py +0 -0
  215. /mojo/apps/{notify/management → docit/migrations}/__init__.py +0 -0
  216. /mojo/apps/{notify/providers → jobs/examples}/__init__.py +0 -0
  217. /mojo/apps/{notify/rest → jobs/migrations}/__init__.py +0 -0
  218. /mojo/{serializers → rest}/openapi.py +0 -0
  219. /mojo/serializers/{settings_example.py → examples/settings.py} +0 -0
  220. /mojo/{apps/notify/handlers/ses/bounce.py → serializers/formats/__init__.py} +0 -0
  221. /mojo/serializers/{advanced/formats → formats}/localizers.py +0 -0
@@ -0,0 +1,181 @@
1
+ """
2
+ Disabled Cache Backend for Django-MOJO Serializers
3
+
4
+ No-op cache backend that disables all caching functionality while maintaining
5
+ the same interface as other cache backends. Useful for development, testing,
6
+ or production environments where caching should be completely disabled.
7
+
8
+ Key Features:
9
+ - All operations succeed but no data is actually cached
10
+ - Zero memory footprint for caching
11
+ - Minimal performance overhead
12
+ - Same interface as other backends for easy switching
13
+ - Safe for all environments including production
14
+
15
+ Usage:
16
+ cache = DisabledCacheBackend()
17
+
18
+ # All operations succeed but nothing is cached
19
+ cache.set("key", "value", ttl=300) # Returns True, but doesn't cache
20
+ result = cache.get("key") # Always returns None
21
+ cache.clear() # Returns True
22
+
23
+ # Statistics show zero activity
24
+ stats = cache.stats() # All zeros
25
+ """
26
+
27
+ from typing import Any, Optional, Dict
28
+
29
+ # Use logit with graceful fallback
30
+ try:
31
+ from mojo.helpers import logit
32
+ logger = logit.get_logger("disabled_cache", "disabled_cache.log")
33
+ except Exception:
34
+ import logging
35
+ logger = logging.getLogger("disabled_cache")
36
+
37
+ from .base import CacheBackend
38
+
39
+
40
+ class DisabledCacheBackend(CacheBackend):
41
+ """
42
+ No-op cache backend that disables all caching functionality.
43
+
44
+ This backend implements the full CacheBackend interface but performs
45
+ no actual caching operations. All methods succeed but no data is stored
46
+ or retrieved. This is useful when you want to disable caching without
47
+ changing application code.
48
+
49
+ Benefits:
50
+ - Zero memory usage for caching
51
+ - Minimal CPU overhead
52
+ - Same interface as other backends
53
+ - Safe fallback option
54
+ - Useful for debugging caching issues
55
+ """
56
+
57
+ def __init__(self):
58
+ """
59
+ Initialize disabled cache backend.
60
+
61
+ No configuration options needed since no caching is performed.
62
+ Logs initialization for debugging purposes.
63
+ """
64
+ logger.info("Disabled cache backend initialized - no caching will be performed")
65
+
66
+ def get(self, key: str) -> Optional[Any]:
67
+ """
68
+ Always returns None (cache miss).
69
+
70
+ Since no caching is performed, all get operations result in cache misses.
71
+ This ensures the application always fetches fresh data.
72
+
73
+ :param key: Cache key (ignored)
74
+ :return: Always None (cache miss)
75
+ """
76
+ return None
77
+
78
+ def set(self, key: str, value: Any, ttl: int = 0) -> bool:
79
+ """
80
+ Always returns True but doesn't cache anything.
81
+
82
+ Accepts all parameters to maintain interface compatibility but
83
+ performs no caching operation. This allows the application to
84
+ continue functioning normally without caching.
85
+
86
+ :param key: Cache key (ignored)
87
+ :param value: Value to cache (ignored)
88
+ :param ttl: Time-to-live in seconds (ignored)
89
+ :return: Always True (operation "succeeded")
90
+ """
91
+ return True
92
+
93
+ def delete(self, key: str) -> bool:
94
+ """
95
+ Always returns True but doesn't delete anything.
96
+
97
+ Since nothing is cached, there's nothing to delete, but we return
98
+ True to indicate the "operation" succeeded.
99
+
100
+ :param key: Cache key (ignored)
101
+ :return: Always True (operation "succeeded")
102
+ """
103
+ return True
104
+
105
+ def clear(self) -> bool:
106
+ """
107
+ Always returns True but doesn't clear anything.
108
+
109
+ Since no cache exists, there's nothing to clear, but we return
110
+ True to indicate the "operation" succeeded.
111
+
112
+ :return: Always True (operation "succeeded")
113
+ """
114
+ return True
115
+
116
+ def stats(self) -> Dict[str, Any]:
117
+ """
118
+ Returns statistics showing no cache activity.
119
+
120
+ All statistics are zero since no caching operations are performed.
121
+ This maintains compatibility with monitoring systems that expect
122
+ cache statistics.
123
+
124
+ :return: Dictionary with zero statistics
125
+ """
126
+ return {
127
+ # Backend identification
128
+ 'backend': 'disabled',
129
+
130
+ # Capacity information (all zero/disabled)
131
+ 'current_size': 0,
132
+ 'max_size': 0,
133
+ 'utilization': 0.0,
134
+
135
+ # Performance metrics (all zero)
136
+ 'hit_rate': 0.0,
137
+ 'total_requests': 0,
138
+
139
+ # Operation counts (all zero)
140
+ 'hits': 0,
141
+ 'misses': 0,
142
+ 'sets': 0,
143
+ 'deletes': 0,
144
+ 'evictions': 0,
145
+ 'expired_items': 0,
146
+ 'errors': 0,
147
+
148
+ # Status information
149
+ 'status': 'disabled',
150
+ 'note': 'Caching is completely disabled'
151
+ }
152
+
153
+ def is_enabled(self) -> bool:
154
+ """
155
+ Always returns False since caching is disabled.
156
+
157
+ Utility method to check if caching is actually enabled.
158
+ Other backends would return True.
159
+
160
+ :return: Always False
161
+ """
162
+ return False
163
+
164
+ def get_config_info(self) -> Dict[str, Any]:
165
+ """
166
+ Return configuration information for this backend.
167
+
168
+ Useful for debugging and monitoring to understand
169
+ what cache backend is currently active.
170
+
171
+ :return: Configuration information
172
+ """
173
+ return {
174
+ 'backend_type': 'disabled',
175
+ 'description': 'No-op cache backend with caching completely disabled',
176
+ 'memory_usage': 0,
177
+ 'thread_safe': True,
178
+ 'supports_ttl': False, # TTL is ignored
179
+ 'supports_persistence': False,
180
+ 'recommended_for': ['development', 'testing', 'debugging']
181
+ }
@@ -0,0 +1,287 @@
1
+ """
2
+ Memory Cache Backend for Django-MOJO Serializers
3
+
4
+ In-memory LRU cache implementation with TTL support and thread-safe operations.
5
+ Provides high-performance caching for serialized model instances with automatic
6
+ eviction and expiration handling.
7
+
8
+ Key Features:
9
+ - LRU (Least Recently Used) eviction when capacity is reached
10
+ - TTL (Time To Live) expiration checking on access
11
+ - Thread-safe operations using RLock
12
+ - JSON serialization for Redis compatibility
13
+ - Performance statistics tracking
14
+ - Configurable size limits with safe defaults
15
+ - TTL of 0 means no caching (safe default behavior)
16
+
17
+ Usage:
18
+ cache = MemoryCacheBackend(max_size=5000, enable_stats=True)
19
+
20
+ # Set with TTL (0 = no caching)
21
+ cache.set("Event_123_default", serialized_data, ttl=300)
22
+
23
+ # Get (returns None if not found/expired)
24
+ data = cache.get("Event_123_default")
25
+
26
+ # Statistics
27
+ stats = cache.stats()
28
+ """
29
+
30
+ # Use ujson for optimal performance, fallback to standard json
31
+ try:
32
+ import ujson as json
33
+ except ImportError:
34
+ import json
35
+ import time
36
+ import threading
37
+ from collections import OrderedDict
38
+ from typing import Any, Optional, Dict
39
+
40
+ # Use logit with graceful fallback
41
+ try:
42
+ from mojo.helpers import logit
43
+ logger = logit.get_logger("memory_cache", "memory_cache.log")
44
+ except Exception:
45
+ import logging
46
+ logger = logging.getLogger("memory_cache")
47
+
48
+ from .base import CacheBackend
49
+
50
+
51
+ class MemoryCacheBackend(CacheBackend):
52
+ """
53
+ In-memory LRU cache backend with TTL support.
54
+
55
+ Implements thread-safe LRU eviction with TTL-based expiration.
56
+ Uses OrderedDict for efficient LRU operations and JSON serialization
57
+ for compatibility with distributed cache backends.
58
+
59
+ Cache Structure:
60
+ - OrderedDict maintains insertion/access order for LRU
61
+ - Each entry: {key: (json_value, expires_at_timestamp)}
62
+ - Thread-safe with RLock for concurrent access
63
+ - Automatic cleanup on access (lazy expiration)
64
+ """
65
+
66
+ def __init__(self, max_size: int = 5000, enable_stats: bool = True):
67
+ """
68
+ Initialize memory cache backend.
69
+
70
+ :param max_size: Maximum number of items to cache before LRU eviction
71
+ :param enable_stats: Enable performance statistics tracking
72
+ """
73
+ self.max_size = max_size
74
+ self.enable_stats = enable_stats
75
+
76
+ # Thread-safe LRU cache storage
77
+ # Format: {key: (json_serialized_value, expires_at_timestamp)}
78
+ self._cache = OrderedDict()
79
+ self._lock = threading.RLock()
80
+
81
+ # Performance statistics tracking
82
+ self._stats = {
83
+ 'hits': 0, # Successful cache retrievals
84
+ 'misses': 0, # Cache misses (not found/expired)
85
+ 'sets': 0, # Items added to cache
86
+ 'deletes': 0, # Items explicitly deleted
87
+ 'evictions': 0, # Items evicted due to capacity
88
+ 'expired_items': 0, # Items that expired on access
89
+ 'errors': 0 # JSON or other operation errors
90
+ }
91
+
92
+ logger.debug(f"Memory cache initialized: max_size={max_size}, stats_enabled={enable_stats}")
93
+
94
+ def get(self, key: str) -> Optional[Any]:
95
+ """
96
+ Retrieve item from cache with TTL checking and LRU updating.
97
+
98
+ Checks expiration, updates LRU order, and deserializes JSON.
99
+ Returns None for cache misses or expired items.
100
+ """
101
+ with self._lock:
102
+ if key not in self._cache:
103
+ self._increment_stat('misses')
104
+ return None
105
+
106
+ try:
107
+ # Remove from current position for LRU update
108
+ json_value, expires_at = self._cache.pop(key)
109
+
110
+ # Check TTL expiration
111
+ if expires_at != 0 and time.time() > expires_at:
112
+ # Item expired - don't re-add to cache
113
+ self._increment_stat('expired_items')
114
+ self._increment_stat('misses')
115
+ return None
116
+
117
+ # Re-add to end (most recently used position)
118
+ self._cache[key] = (json_value, expires_at)
119
+ self._increment_stat('hits')
120
+
121
+ # Deserialize from JSON
122
+ return json.loads(json_value) if json_value else None
123
+
124
+ except (json.JSONDecodeError, KeyError, ValueError, TypeError) as e:
125
+ logger.warning(f"Cache get error for key '{key}': {e}")
126
+ # Remove corrupted entry
127
+ self._cache.pop(key, None)
128
+ self._increment_stat('errors')
129
+ self._increment_stat('misses')
130
+ return None
131
+
132
+ def set(self, key: str, value: Any, ttl: int = 0) -> bool:
133
+ """
134
+ Store item in cache with LRU eviction and JSON serialization.
135
+
136
+ TTL of 0 means no caching (safe default). Automatically evicts
137
+ LRU items when capacity is reached.
138
+ """
139
+ if ttl == 0:
140
+ # TTL of 0 means no caching - safe default behavior
141
+ return True
142
+
143
+ with self._lock:
144
+ try:
145
+ # Serialize to JSON for Redis compatibility
146
+ json_value = json.dumps(value, default=str) # default=str handles dates/decimals
147
+
148
+ # Calculate expiration timestamp
149
+ expires_at = time.time() + ttl if ttl > 0 else 0
150
+
151
+ # Remove existing entry if present (updates position)
152
+ if key in self._cache:
153
+ self._cache.pop(key)
154
+
155
+ # Evict LRU items if at capacity
156
+ while len(self._cache) >= self.max_size:
157
+ self._evict_lru()
158
+
159
+ # Add new item (becomes most recently used)
160
+ self._cache[key] = (json_value, expires_at)
161
+ self._increment_stat('sets')
162
+
163
+ return True
164
+
165
+ except (json.JSONEncodeError, TypeError, ValueError) as e:
166
+ logger.warning(f"Cache set error for key '{key}': {e}")
167
+ self._increment_stat('errors')
168
+ return False
169
+
170
+ def delete(self, key: str) -> bool:
171
+ """
172
+ Remove specific item from cache.
173
+
174
+ :param key: Cache key to remove
175
+ :return: True if item was found and removed
176
+ """
177
+ with self._lock:
178
+ if key in self._cache:
179
+ self._cache.pop(key)
180
+ self._increment_stat('deletes')
181
+ return True
182
+ return False
183
+
184
+ def clear(self) -> bool:
185
+ """
186
+ Clear all cached items and reset statistics.
187
+
188
+ Thread-safe operation that removes all items from cache.
189
+ """
190
+ with self._lock:
191
+ cleared_count = len(self._cache)
192
+ self._cache.clear()
193
+
194
+ if cleared_count > 0:
195
+ logger.info(f"Cleared {cleared_count} items from memory cache")
196
+
197
+ return True
198
+
199
+ def stats(self) -> Dict[str, Any]:
200
+ """
201
+ Get comprehensive cache statistics.
202
+
203
+ Returns performance metrics, capacity info, and calculated ratios
204
+ for monitoring and optimization purposes.
205
+ """
206
+ with self._lock:
207
+ total_requests = self._stats['hits'] + self._stats['misses']
208
+ hit_rate = (self._stats['hits'] / total_requests) if total_requests > 0 else 0.0
209
+
210
+ return {
211
+ # Backend identification
212
+ 'backend': 'memory',
213
+
214
+ # Capacity information
215
+ 'current_size': len(self._cache),
216
+ 'max_size': self.max_size,
217
+ 'utilization': len(self._cache) / self.max_size if self.max_size > 0 else 0.0,
218
+
219
+ # Performance metrics
220
+ 'hit_rate': hit_rate,
221
+ 'total_requests': total_requests,
222
+
223
+ # Raw statistics
224
+ **self._stats.copy()
225
+ }
226
+
227
+ def _evict_lru(self):
228
+ """
229
+ Evict least recently used item from cache.
230
+
231
+ OrderedDict maintains insertion order - first item is LRU.
232
+ Thread-safe operation that removes oldest item.
233
+ """
234
+ if self._cache:
235
+ # OrderedDict: first item (index 0) is least recently used
236
+ evicted_key, _ = self._cache.popitem(last=False)
237
+ self._increment_stat('evictions')
238
+ logger.debug(f"Evicted LRU item: {evicted_key}")
239
+
240
+ def _increment_stat(self, stat_name: str):
241
+ """
242
+ Thread-safe statistics increment.
243
+
244
+ Only increments if statistics tracking is enabled.
245
+ """
246
+ if self.enable_stats:
247
+ self._stats[stat_name] += 1
248
+
249
+ def get_memory_info(self) -> Dict[str, Any]:
250
+ """
251
+ Get memory usage estimation and cache health information.
252
+
253
+ Provides rough estimates for memory usage and recommendations
254
+ for cache configuration optimization.
255
+ """
256
+ with self._lock:
257
+ current_size = len(self._cache)
258
+
259
+ if current_size == 0:
260
+ return {
261
+ 'estimated_memory_mb': 0,
262
+ 'estimated_memory_bytes': 0,
263
+ 'objects': 0,
264
+ 'health': 'empty'
265
+ }
266
+
267
+ # Rough estimate: 2KB per cached object (JSON + overhead)
268
+ estimated_bytes = current_size * 2048
269
+ estimated_mb = estimated_bytes / (1024 * 1024)
270
+
271
+ # Health assessment
272
+ utilization = current_size / self.max_size if self.max_size > 0 else 0
273
+ if utilization > 0.9:
274
+ health = 'high_utilization'
275
+ elif utilization > 0.7:
276
+ health = 'moderate_utilization'
277
+ else:
278
+ health = 'good'
279
+
280
+ return {
281
+ 'estimated_memory_bytes': estimated_bytes,
282
+ 'estimated_memory_mb': round(estimated_mb, 2),
283
+ 'objects': current_size,
284
+ 'utilization': utilization,
285
+ 'health': health,
286
+ 'note': 'Rough estimate assuming ~2KB per cached object'
287
+ }