django-nativemojo 0.1.10__py3-none-any.whl → 0.1.16__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.
- django_nativemojo-0.1.16.dist-info/METADATA +138 -0
- django_nativemojo-0.1.16.dist-info/RECORD +302 -0
- mojo/__init__.py +1 -1
- mojo/apps/account/management/__init__.py +5 -0
- mojo/apps/account/management/commands/__init__.py +6 -0
- mojo/apps/account/management/commands/serializer_admin.py +651 -0
- mojo/apps/account/migrations/0004_user_avatar.py +20 -0
- mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
- mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
- mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
- mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
- mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
- mojo/apps/account/migrations/0010_group_avatar.py +20 -0
- mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
- mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
- mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
- mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
- mojo/apps/account/models/__init__.py +2 -0
- mojo/apps/account/models/device.py +281 -0
- mojo/apps/account/models/group.py +319 -15
- mojo/apps/account/models/member.py +29 -5
- mojo/apps/account/models/push/__init__.py +4 -0
- mojo/apps/account/models/push/config.py +112 -0
- mojo/apps/account/models/push/delivery.py +93 -0
- mojo/apps/account/models/push/device.py +66 -0
- mojo/apps/account/models/push/template.py +99 -0
- mojo/apps/account/models/user.py +369 -19
- mojo/apps/account/rest/__init__.py +2 -0
- mojo/apps/account/rest/device.py +39 -0
- mojo/apps/account/rest/group.py +9 -0
- mojo/apps/account/rest/push.py +187 -0
- mojo/apps/account/rest/user.py +100 -6
- mojo/apps/account/services/__init__.py +1 -0
- mojo/apps/account/services/push.py +363 -0
- mojo/apps/aws/migrations/0001_initial.py +206 -0
- mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
- mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
- mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
- mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
- mojo/apps/aws/models/__init__.py +19 -0
- mojo/apps/aws/models/email_attachment.py +99 -0
- mojo/apps/aws/models/email_domain.py +218 -0
- mojo/apps/aws/models/email_template.py +132 -0
- mojo/apps/aws/models/incoming_email.py +197 -0
- mojo/apps/aws/models/mailbox.py +288 -0
- mojo/apps/aws/models/sent_message.py +175 -0
- mojo/apps/aws/rest/__init__.py +7 -0
- mojo/apps/aws/rest/email.py +33 -0
- mojo/apps/aws/rest/email_ops.py +183 -0
- mojo/apps/aws/rest/messages.py +32 -0
- mojo/apps/aws/rest/s3.py +64 -0
- mojo/apps/aws/rest/send.py +101 -0
- mojo/apps/aws/rest/sns.py +403 -0
- mojo/apps/aws/rest/templates.py +19 -0
- mojo/apps/aws/services/__init__.py +32 -0
- mojo/apps/aws/services/email.py +390 -0
- mojo/apps/aws/services/email_ops.py +548 -0
- mojo/apps/docit/__init__.py +6 -0
- mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
- mojo/apps/docit/markdown_plugins/toc.py +12 -0
- mojo/apps/docit/migrations/0001_initial.py +113 -0
- mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
- mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
- mojo/apps/docit/models/__init__.py +17 -0
- mojo/apps/docit/models/asset.py +231 -0
- mojo/apps/docit/models/book.py +227 -0
- mojo/apps/docit/models/page.py +319 -0
- mojo/apps/docit/models/page_revision.py +203 -0
- mojo/apps/docit/rest/__init__.py +10 -0
- mojo/apps/docit/rest/asset.py +17 -0
- mojo/apps/docit/rest/book.py +22 -0
- mojo/apps/docit/rest/page.py +22 -0
- mojo/apps/docit/rest/page_revision.py +17 -0
- mojo/apps/docit/services/__init__.py +11 -0
- mojo/apps/docit/services/docit.py +315 -0
- mojo/apps/docit/services/markdown.py +44 -0
- mojo/apps/fileman/README.md +8 -8
- mojo/apps/fileman/backends/base.py +76 -70
- mojo/apps/fileman/backends/filesystem.py +86 -86
- mojo/apps/fileman/backends/s3.py +409 -108
- mojo/apps/fileman/migrations/0001_initial.py +106 -0
- mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
- mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
- mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
- mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
- mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
- mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
- mojo/apps/fileman/migrations/0008_file_category.py +18 -0
- mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
- mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
- mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
- mojo/apps/fileman/models/__init__.py +1 -5
- mojo/apps/fileman/models/file.py +240 -58
- mojo/apps/fileman/models/manager.py +427 -31
- mojo/apps/fileman/models/rendition.py +118 -0
- mojo/apps/fileman/renderer/__init__.py +111 -0
- mojo/apps/fileman/renderer/audio.py +403 -0
- mojo/apps/fileman/renderer/base.py +205 -0
- mojo/apps/fileman/renderer/document.py +404 -0
- mojo/apps/fileman/renderer/image.py +222 -0
- mojo/apps/fileman/renderer/utils.py +297 -0
- mojo/apps/fileman/renderer/video.py +304 -0
- mojo/apps/fileman/rest/__init__.py +1 -18
- mojo/apps/fileman/rest/upload.py +22 -32
- mojo/apps/fileman/signals.py +58 -0
- mojo/apps/fileman/tasks.py +254 -0
- mojo/apps/fileman/utils/__init__.py +40 -16
- mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
- mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
- mojo/apps/incident/migrations/0007_event_uid.py +18 -0
- mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
- mojo/apps/incident/migrations/0009_incident_status.py +18 -0
- mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
- mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
- mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
- mojo/apps/incident/models/__init__.py +2 -0
- mojo/apps/incident/models/event.py +35 -0
- mojo/apps/incident/models/history.py +36 -0
- mojo/apps/incident/models/incident.py +3 -1
- mojo/apps/incident/models/ticket.py +62 -0
- mojo/apps/incident/reporter.py +21 -1
- mojo/apps/incident/rest/__init__.py +1 -0
- mojo/apps/incident/rest/event.py +7 -1
- mojo/apps/incident/rest/ticket.py +43 -0
- mojo/apps/jobs/__init__.py +489 -0
- mojo/apps/jobs/adapters.py +24 -0
- mojo/apps/jobs/cli.py +616 -0
- mojo/apps/jobs/daemon.py +370 -0
- mojo/apps/jobs/examples/sample_jobs.py +376 -0
- mojo/apps/jobs/examples/webhook_examples.py +203 -0
- mojo/apps/jobs/handlers/__init__.py +5 -0
- mojo/apps/jobs/handlers/webhook.py +317 -0
- mojo/apps/jobs/job_engine.py +734 -0
- mojo/apps/jobs/keys.py +203 -0
- mojo/apps/jobs/local_queue.py +363 -0
- mojo/apps/jobs/management/__init__.py +3 -0
- mojo/apps/jobs/management/commands/__init__.py +3 -0
- mojo/apps/jobs/manager.py +1327 -0
- mojo/apps/jobs/migrations/0001_initial.py +97 -0
- mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
- mojo/apps/jobs/models/__init__.py +6 -0
- mojo/apps/jobs/models/job.py +441 -0
- mojo/apps/jobs/rest/__init__.py +2 -0
- mojo/apps/jobs/rest/control.py +466 -0
- mojo/apps/jobs/rest/jobs.py +421 -0
- mojo/apps/jobs/scheduler.py +571 -0
- mojo/apps/jobs/services/__init__.py +6 -0
- mojo/apps/jobs/services/job_actions.py +465 -0
- mojo/apps/jobs/settings.py +209 -0
- mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
- mojo/apps/logit/models/log.py +7 -1
- mojo/apps/metrics/__init__.py +8 -1
- mojo/apps/metrics/redis_metrics.py +198 -0
- mojo/apps/metrics/rest/__init__.py +3 -0
- mojo/apps/metrics/rest/categories.py +266 -0
- mojo/apps/metrics/rest/helpers.py +48 -0
- mojo/apps/metrics/rest/permissions.py +99 -0
- mojo/apps/metrics/rest/values.py +277 -0
- mojo/apps/metrics/utils.py +19 -2
- mojo/decorators/auth.py +6 -1
- mojo/decorators/http.py +47 -3
- mojo/helpers/aws/__init__.py +45 -0
- mojo/helpers/aws/ec2.py +804 -0
- mojo/helpers/aws/iam.py +748 -0
- mojo/helpers/aws/inbound_email.py +309 -0
- mojo/helpers/aws/kms.py +413 -0
- mojo/helpers/aws/s3.py +451 -11
- mojo/helpers/aws/ses.py +483 -0
- mojo/helpers/aws/ses_domain.py +959 -0
- mojo/helpers/aws/sns.py +461 -0
- mojo/helpers/crypto/__init__.py +1 -1
- mojo/helpers/crypto/utils.py +15 -0
- mojo/helpers/dates.py +18 -0
- mojo/helpers/location/__init__.py +2 -0
- mojo/helpers/location/countries.py +262 -0
- mojo/helpers/location/geolocation.py +196 -0
- mojo/helpers/logit.py +37 -0
- mojo/helpers/redis/__init__.py +2 -0
- mojo/helpers/redis/adapter.py +606 -0
- mojo/helpers/redis/client.py +48 -0
- mojo/helpers/redis/pool.py +225 -0
- mojo/helpers/request.py +8 -0
- mojo/helpers/response.py +14 -2
- mojo/helpers/settings/__init__.py +2 -0
- mojo/helpers/{settings.py → settings/helper.py} +1 -37
- mojo/helpers/settings/parser.py +132 -0
- mojo/middleware/auth.py +1 -1
- mojo/middleware/cors.py +40 -0
- mojo/middleware/logging.py +131 -12
- mojo/middleware/mojo.py +10 -0
- mojo/models/rest.py +494 -65
- mojo/models/secrets.py +98 -3
- mojo/serializers/__init__.py +106 -0
- mojo/serializers/core/__init__.py +90 -0
- mojo/serializers/core/cache/__init__.py +121 -0
- mojo/serializers/core/cache/backends.py +518 -0
- mojo/serializers/core/cache/base.py +102 -0
- mojo/serializers/core/cache/disabled.py +181 -0
- mojo/serializers/core/cache/memory.py +287 -0
- mojo/serializers/core/cache/redis.py +533 -0
- mojo/serializers/core/cache/utils.py +454 -0
- mojo/serializers/core/manager.py +550 -0
- mojo/serializers/core/serializer.py +475 -0
- mojo/serializers/examples/settings.py +322 -0
- mojo/serializers/formats/csv.py +393 -0
- mojo/serializers/formats/localizers.py +509 -0
- mojo/serializers/{models.py → simple.py} +38 -15
- mojo/serializers/suggested_improvements.md +388 -0
- testit/client.py +1 -1
- testit/helpers.py +35 -4
- testit/runner.py +23 -6
- django_nativemojo-0.1.10.dist-info/METADATA +0 -96
- django_nativemojo-0.1.10.dist-info/RECORD +0 -194
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/notify/README.md +0 -91
- mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
- mojo/apps/notify/admin.py +0 -52
- mojo/apps/notify/handlers/example_handlers.py +0 -516
- mojo/apps/notify/handlers/ses/__init__.py +0 -25
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +0 -25
- mojo/apps/notify/handlers/ses/message.py +0 -86
- mojo/apps/notify/management/commands/__init__.py +0 -1
- mojo/apps/notify/management/commands/process_notifications.py +0 -370
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +0 -12
- mojo/apps/notify/models/account.py +0 -128
- mojo/apps/notify/models/attachment.py +0 -24
- mojo/apps/notify/models/bounce.py +0 -68
- mojo/apps/notify/models/complaint.py +0 -40
- mojo/apps/notify/models/inbox.py +0 -113
- mojo/apps/notify/models/inbox_message.py +0 -173
- mojo/apps/notify/models/outbox.py +0 -129
- mojo/apps/notify/models/outbox_message.py +0 -288
- mojo/apps/notify/models/template.py +0 -30
- mojo/apps/notify/providers/aws.py +0 -73
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +0 -2
- mojo/apps/notify/utils/notifications.py +0 -404
- mojo/apps/notify/utils/parsing.py +0 -202
- mojo/apps/notify/utils/render.py +0 -144
- mojo/apps/tasks/README.md +0 -118
- mojo/apps/tasks/__init__.py +0 -11
- mojo/apps/tasks/manager.py +0 -489
- mojo/apps/tasks/rest/__init__.py +0 -2
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +0 -62
- mojo/apps/tasks/runner.py +0 -174
- mojo/apps/tasks/tq_handlers.py +0 -14
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/redis.py +0 -10
- mojo/models/meta.py +0 -262
- mojo/ws4redis/README.md +0 -174
- mojo/ws4redis/__init__.py +0 -2
- mojo/ws4redis/client.py +0 -283
- mojo/ws4redis/connection.py +0 -327
- mojo/ws4redis/exceptions.py +0 -32
- mojo/ws4redis/redis.py +0 -183
- mojo/ws4redis/servers/base.py +0 -86
- mojo/ws4redis/servers/django.py +0 -171
- mojo/ws4redis/servers/uwsgi.py +0 -63
- mojo/ws4redis/settings.py +0 -45
- mojo/ws4redis/utf8validator.py +0 -128
- mojo/ws4redis/websocket.py +0 -403
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
- /mojo/apps/{notify → aws}/__init__.py +0 -0
- /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
- /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
- /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
- /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
- /mojo/{serializers → rest}/openapi.py +0 -0
- /mojo/{apps/fileman/rest/__init__ → serializers/formats/__init__.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
|
+
}
|