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.
Files changed (276) hide show
  1. django_nativemojo-0.1.16.dist-info/METADATA +138 -0
  2. django_nativemojo-0.1.16.dist-info/RECORD +302 -0
  3. mojo/__init__.py +1 -1
  4. mojo/apps/account/management/__init__.py +5 -0
  5. mojo/apps/account/management/commands/__init__.py +6 -0
  6. mojo/apps/account/management/commands/serializer_admin.py +651 -0
  7. mojo/apps/account/migrations/0004_user_avatar.py +20 -0
  8. mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
  9. mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
  10. mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
  11. mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
  12. mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
  13. mojo/apps/account/migrations/0010_group_avatar.py +20 -0
  14. mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
  15. mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
  16. mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
  17. mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
  18. mojo/apps/account/models/__init__.py +2 -0
  19. mojo/apps/account/models/device.py +281 -0
  20. mojo/apps/account/models/group.py +319 -15
  21. mojo/apps/account/models/member.py +29 -5
  22. mojo/apps/account/models/push/__init__.py +4 -0
  23. mojo/apps/account/models/push/config.py +112 -0
  24. mojo/apps/account/models/push/delivery.py +93 -0
  25. mojo/apps/account/models/push/device.py +66 -0
  26. mojo/apps/account/models/push/template.py +99 -0
  27. mojo/apps/account/models/user.py +369 -19
  28. mojo/apps/account/rest/__init__.py +2 -0
  29. mojo/apps/account/rest/device.py +39 -0
  30. mojo/apps/account/rest/group.py +9 -0
  31. mojo/apps/account/rest/push.py +187 -0
  32. mojo/apps/account/rest/user.py +100 -6
  33. mojo/apps/account/services/__init__.py +1 -0
  34. mojo/apps/account/services/push.py +363 -0
  35. mojo/apps/aws/migrations/0001_initial.py +206 -0
  36. mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
  37. mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
  38. mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
  39. mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
  40. mojo/apps/aws/models/__init__.py +19 -0
  41. mojo/apps/aws/models/email_attachment.py +99 -0
  42. mojo/apps/aws/models/email_domain.py +218 -0
  43. mojo/apps/aws/models/email_template.py +132 -0
  44. mojo/apps/aws/models/incoming_email.py +197 -0
  45. mojo/apps/aws/models/mailbox.py +288 -0
  46. mojo/apps/aws/models/sent_message.py +175 -0
  47. mojo/apps/aws/rest/__init__.py +7 -0
  48. mojo/apps/aws/rest/email.py +33 -0
  49. mojo/apps/aws/rest/email_ops.py +183 -0
  50. mojo/apps/aws/rest/messages.py +32 -0
  51. mojo/apps/aws/rest/s3.py +64 -0
  52. mojo/apps/aws/rest/send.py +101 -0
  53. mojo/apps/aws/rest/sns.py +403 -0
  54. mojo/apps/aws/rest/templates.py +19 -0
  55. mojo/apps/aws/services/__init__.py +32 -0
  56. mojo/apps/aws/services/email.py +390 -0
  57. mojo/apps/aws/services/email_ops.py +548 -0
  58. mojo/apps/docit/__init__.py +6 -0
  59. mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
  60. mojo/apps/docit/markdown_plugins/toc.py +12 -0
  61. mojo/apps/docit/migrations/0001_initial.py +113 -0
  62. mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
  63. mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
  64. mojo/apps/docit/models/__init__.py +17 -0
  65. mojo/apps/docit/models/asset.py +231 -0
  66. mojo/apps/docit/models/book.py +227 -0
  67. mojo/apps/docit/models/page.py +319 -0
  68. mojo/apps/docit/models/page_revision.py +203 -0
  69. mojo/apps/docit/rest/__init__.py +10 -0
  70. mojo/apps/docit/rest/asset.py +17 -0
  71. mojo/apps/docit/rest/book.py +22 -0
  72. mojo/apps/docit/rest/page.py +22 -0
  73. mojo/apps/docit/rest/page_revision.py +17 -0
  74. mojo/apps/docit/services/__init__.py +11 -0
  75. mojo/apps/docit/services/docit.py +315 -0
  76. mojo/apps/docit/services/markdown.py +44 -0
  77. mojo/apps/fileman/README.md +8 -8
  78. mojo/apps/fileman/backends/base.py +76 -70
  79. mojo/apps/fileman/backends/filesystem.py +86 -86
  80. mojo/apps/fileman/backends/s3.py +409 -108
  81. mojo/apps/fileman/migrations/0001_initial.py +106 -0
  82. mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
  83. mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
  84. mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
  85. mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
  86. mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
  87. mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
  88. mojo/apps/fileman/migrations/0008_file_category.py +18 -0
  89. mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
  90. mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
  91. mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
  92. mojo/apps/fileman/models/__init__.py +1 -5
  93. mojo/apps/fileman/models/file.py +240 -58
  94. mojo/apps/fileman/models/manager.py +427 -31
  95. mojo/apps/fileman/models/rendition.py +118 -0
  96. mojo/apps/fileman/renderer/__init__.py +111 -0
  97. mojo/apps/fileman/renderer/audio.py +403 -0
  98. mojo/apps/fileman/renderer/base.py +205 -0
  99. mojo/apps/fileman/renderer/document.py +404 -0
  100. mojo/apps/fileman/renderer/image.py +222 -0
  101. mojo/apps/fileman/renderer/utils.py +297 -0
  102. mojo/apps/fileman/renderer/video.py +304 -0
  103. mojo/apps/fileman/rest/__init__.py +1 -18
  104. mojo/apps/fileman/rest/upload.py +22 -32
  105. mojo/apps/fileman/signals.py +58 -0
  106. mojo/apps/fileman/tasks.py +254 -0
  107. mojo/apps/fileman/utils/__init__.py +40 -16
  108. mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
  109. mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
  110. mojo/apps/incident/migrations/0007_event_uid.py +18 -0
  111. mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
  112. mojo/apps/incident/migrations/0009_incident_status.py +18 -0
  113. mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
  114. mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
  115. mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
  116. mojo/apps/incident/models/__init__.py +2 -0
  117. mojo/apps/incident/models/event.py +35 -0
  118. mojo/apps/incident/models/history.py +36 -0
  119. mojo/apps/incident/models/incident.py +3 -1
  120. mojo/apps/incident/models/ticket.py +62 -0
  121. mojo/apps/incident/reporter.py +21 -1
  122. mojo/apps/incident/rest/__init__.py +1 -0
  123. mojo/apps/incident/rest/event.py +7 -1
  124. mojo/apps/incident/rest/ticket.py +43 -0
  125. mojo/apps/jobs/__init__.py +489 -0
  126. mojo/apps/jobs/adapters.py +24 -0
  127. mojo/apps/jobs/cli.py +616 -0
  128. mojo/apps/jobs/daemon.py +370 -0
  129. mojo/apps/jobs/examples/sample_jobs.py +376 -0
  130. mojo/apps/jobs/examples/webhook_examples.py +203 -0
  131. mojo/apps/jobs/handlers/__init__.py +5 -0
  132. mojo/apps/jobs/handlers/webhook.py +317 -0
  133. mojo/apps/jobs/job_engine.py +734 -0
  134. mojo/apps/jobs/keys.py +203 -0
  135. mojo/apps/jobs/local_queue.py +363 -0
  136. mojo/apps/jobs/management/__init__.py +3 -0
  137. mojo/apps/jobs/management/commands/__init__.py +3 -0
  138. mojo/apps/jobs/manager.py +1327 -0
  139. mojo/apps/jobs/migrations/0001_initial.py +97 -0
  140. mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
  141. mojo/apps/jobs/models/__init__.py +6 -0
  142. mojo/apps/jobs/models/job.py +441 -0
  143. mojo/apps/jobs/rest/__init__.py +2 -0
  144. mojo/apps/jobs/rest/control.py +466 -0
  145. mojo/apps/jobs/rest/jobs.py +421 -0
  146. mojo/apps/jobs/scheduler.py +571 -0
  147. mojo/apps/jobs/services/__init__.py +6 -0
  148. mojo/apps/jobs/services/job_actions.py +465 -0
  149. mojo/apps/jobs/settings.py +209 -0
  150. mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
  151. mojo/apps/logit/models/log.py +7 -1
  152. mojo/apps/metrics/__init__.py +8 -1
  153. mojo/apps/metrics/redis_metrics.py +198 -0
  154. mojo/apps/metrics/rest/__init__.py +3 -0
  155. mojo/apps/metrics/rest/categories.py +266 -0
  156. mojo/apps/metrics/rest/helpers.py +48 -0
  157. mojo/apps/metrics/rest/permissions.py +99 -0
  158. mojo/apps/metrics/rest/values.py +277 -0
  159. mojo/apps/metrics/utils.py +19 -2
  160. mojo/decorators/auth.py +6 -1
  161. mojo/decorators/http.py +47 -3
  162. mojo/helpers/aws/__init__.py +45 -0
  163. mojo/helpers/aws/ec2.py +804 -0
  164. mojo/helpers/aws/iam.py +748 -0
  165. mojo/helpers/aws/inbound_email.py +309 -0
  166. mojo/helpers/aws/kms.py +413 -0
  167. mojo/helpers/aws/s3.py +451 -11
  168. mojo/helpers/aws/ses.py +483 -0
  169. mojo/helpers/aws/ses_domain.py +959 -0
  170. mojo/helpers/aws/sns.py +461 -0
  171. mojo/helpers/crypto/__init__.py +1 -1
  172. mojo/helpers/crypto/utils.py +15 -0
  173. mojo/helpers/dates.py +18 -0
  174. mojo/helpers/location/__init__.py +2 -0
  175. mojo/helpers/location/countries.py +262 -0
  176. mojo/helpers/location/geolocation.py +196 -0
  177. mojo/helpers/logit.py +37 -0
  178. mojo/helpers/redis/__init__.py +2 -0
  179. mojo/helpers/redis/adapter.py +606 -0
  180. mojo/helpers/redis/client.py +48 -0
  181. mojo/helpers/redis/pool.py +225 -0
  182. mojo/helpers/request.py +8 -0
  183. mojo/helpers/response.py +14 -2
  184. mojo/helpers/settings/__init__.py +2 -0
  185. mojo/helpers/{settings.py → settings/helper.py} +1 -37
  186. mojo/helpers/settings/parser.py +132 -0
  187. mojo/middleware/auth.py +1 -1
  188. mojo/middleware/cors.py +40 -0
  189. mojo/middleware/logging.py +131 -12
  190. mojo/middleware/mojo.py +10 -0
  191. mojo/models/rest.py +494 -65
  192. mojo/models/secrets.py +98 -3
  193. mojo/serializers/__init__.py +106 -0
  194. mojo/serializers/core/__init__.py +90 -0
  195. mojo/serializers/core/cache/__init__.py +121 -0
  196. mojo/serializers/core/cache/backends.py +518 -0
  197. mojo/serializers/core/cache/base.py +102 -0
  198. mojo/serializers/core/cache/disabled.py +181 -0
  199. mojo/serializers/core/cache/memory.py +287 -0
  200. mojo/serializers/core/cache/redis.py +533 -0
  201. mojo/serializers/core/cache/utils.py +454 -0
  202. mojo/serializers/core/manager.py +550 -0
  203. mojo/serializers/core/serializer.py +475 -0
  204. mojo/serializers/examples/settings.py +322 -0
  205. mojo/serializers/formats/csv.py +393 -0
  206. mojo/serializers/formats/localizers.py +509 -0
  207. mojo/serializers/{models.py → simple.py} +38 -15
  208. mojo/serializers/suggested_improvements.md +388 -0
  209. testit/client.py +1 -1
  210. testit/helpers.py +35 -4
  211. testit/runner.py +23 -6
  212. django_nativemojo-0.1.10.dist-info/METADATA +0 -96
  213. django_nativemojo-0.1.10.dist-info/RECORD +0 -194
  214. mojo/apps/metrics/rest/db.py +0 -0
  215. mojo/apps/notify/README.md +0 -91
  216. mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
  217. mojo/apps/notify/admin.py +0 -52
  218. mojo/apps/notify/handlers/example_handlers.py +0 -516
  219. mojo/apps/notify/handlers/ses/__init__.py +0 -25
  220. mojo/apps/notify/handlers/ses/bounce.py +0 -0
  221. mojo/apps/notify/handlers/ses/complaint.py +0 -25
  222. mojo/apps/notify/handlers/ses/message.py +0 -86
  223. mojo/apps/notify/management/commands/__init__.py +0 -1
  224. mojo/apps/notify/management/commands/process_notifications.py +0 -370
  225. mojo/apps/notify/mod +0 -0
  226. mojo/apps/notify/models/__init__.py +0 -12
  227. mojo/apps/notify/models/account.py +0 -128
  228. mojo/apps/notify/models/attachment.py +0 -24
  229. mojo/apps/notify/models/bounce.py +0 -68
  230. mojo/apps/notify/models/complaint.py +0 -40
  231. mojo/apps/notify/models/inbox.py +0 -113
  232. mojo/apps/notify/models/inbox_message.py +0 -173
  233. mojo/apps/notify/models/outbox.py +0 -129
  234. mojo/apps/notify/models/outbox_message.py +0 -288
  235. mojo/apps/notify/models/template.py +0 -30
  236. mojo/apps/notify/providers/aws.py +0 -73
  237. mojo/apps/notify/rest/ses.py +0 -0
  238. mojo/apps/notify/utils/__init__.py +0 -2
  239. mojo/apps/notify/utils/notifications.py +0 -404
  240. mojo/apps/notify/utils/parsing.py +0 -202
  241. mojo/apps/notify/utils/render.py +0 -144
  242. mojo/apps/tasks/README.md +0 -118
  243. mojo/apps/tasks/__init__.py +0 -11
  244. mojo/apps/tasks/manager.py +0 -489
  245. mojo/apps/tasks/rest/__init__.py +0 -2
  246. mojo/apps/tasks/rest/hooks.py +0 -0
  247. mojo/apps/tasks/rest/tasks.py +0 -62
  248. mojo/apps/tasks/runner.py +0 -174
  249. mojo/apps/tasks/tq_handlers.py +0 -14
  250. mojo/helpers/aws/setup_email.py +0 -0
  251. mojo/helpers/redis.py +0 -10
  252. mojo/models/meta.py +0 -262
  253. mojo/ws4redis/README.md +0 -174
  254. mojo/ws4redis/__init__.py +0 -2
  255. mojo/ws4redis/client.py +0 -283
  256. mojo/ws4redis/connection.py +0 -327
  257. mojo/ws4redis/exceptions.py +0 -32
  258. mojo/ws4redis/redis.py +0 -183
  259. mojo/ws4redis/servers/base.py +0 -86
  260. mojo/ws4redis/servers/django.py +0 -171
  261. mojo/ws4redis/servers/uwsgi.py +0 -63
  262. mojo/ws4redis/settings.py +0 -45
  263. mojo/ws4redis/utf8validator.py +0 -128
  264. mojo/ws4redis/websocket.py +0 -403
  265. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
  266. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
  267. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
  268. /mojo/apps/{notify → aws}/__init__.py +0 -0
  269. /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
  270. /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
  271. /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
  272. /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
  273. /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
  274. /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
  275. /mojo/{serializers → rest}/openapi.py +0 -0
  276. /mojo/{apps/fileman/rest/__init__ → serializers/formats/__init__.py} +0 -0
@@ -0,0 +1,550 @@
1
+ """
2
+ Serializer Manager for Django-MOJO
3
+
4
+ Provides a unified interface for switching between different serializers and managing
5
+ serialization strategies across the application. Supports:
6
+
7
+ - Multiple serializer backends (simple, advanced, optimized, custom)
8
+ - Runtime serializer switching
9
+ - Performance monitoring and comparison
10
+ - Configuration via Django settings
11
+ - Fallback mechanisms for robustness
12
+ - Easy integration with MojoModel REST operations
13
+
14
+ Usage:
15
+ # Use default configured serializer
16
+ manager = SerializerManager()
17
+ serializer = manager.get_serializer(instance, graph="list")
18
+
19
+ # Force specific serializer
20
+ serializer = manager.get_serializer(instance, serializer_type="optimized")
21
+
22
+ # Performance comparison
23
+ results = manager.benchmark_serializers(queryset)
24
+ """
25
+
26
+ import time
27
+ import importlib
28
+ from typing import Dict, Any, Optional, Type, Union, List
29
+ from threading import RLock
30
+
31
+ from django.conf import settings
32
+ from django.db.models import QuerySet, Model
33
+ from django.http import HttpResponse
34
+
35
+ from mojo.helpers import logit
36
+
37
+ logger = logit.get_logger("serializer_manager", "serializer_manager.log")
38
+
39
+ # Thread-safe lock for serializer registry operations
40
+ _registry_lock = RLock()
41
+
42
+ # Default serializer configurations
43
+ # DEFAULT_SERIALIZERS = {
44
+ # 'simple': 'mojo.serializers.simple.GraphSerializer',
45
+ # 'optimized': 'mojo.serializers.core.serializer.OptimizedGraphSerializer',
46
+ # 'advanced': 'mojo.serializers.advanced.AdvancedGraphSerializer',
47
+ # }
48
+
49
+ DEFAULT_SERIALIZERS = {
50
+ 'optimized': 'mojo.serializers.core.serializer.OptimizedGraphSerializer'
51
+ }
52
+
53
+ FORMAT_SERIALIZERS = {
54
+ 'csv': 'mojo.serializers.formats.csv.CsvFormatter'
55
+ }
56
+
57
+ # Global serializer registry
58
+ _SERIALIZER_REGISTRY = {}
59
+
60
+ # Performance tracking
61
+ _PERFORMANCE_DATA = {
62
+ 'serializer_usage': {},
63
+ 'performance_history': [],
64
+ 'benchmark_results': {}
65
+ }
66
+
67
+
68
+ class SerializerRegistry:
69
+ """
70
+ Registry for managing available serializers.
71
+
72
+ This registry supports lazy loading of serializers. When a serializer is
73
+ registered using an import path string, it is only imported when the
74
+ `register` method is called. The default serializers are registered
75
+ on the first call to `get_serializer_manager()`, avoiding imports at
76
+ application startup.
77
+ """
78
+
79
+ def __init__(self):
80
+ self.serializers = {}
81
+ self.default_serializer = None
82
+ self.lock = RLock()
83
+
84
+ def register(self, name: str, serializer_class_or_path: Union[str, Type],
85
+ description: str = None, is_default: bool = False):
86
+ """
87
+ Register a serializer.
88
+
89
+ :param name: Unique name for the serializer
90
+ :param serializer_class_or_path: Serializer class or import path string
91
+ :param description: Optional description
92
+ :param is_default: Set as default serializer
93
+ """
94
+ with self.lock:
95
+ # Handle string import path
96
+ if isinstance(serializer_class_or_path, str):
97
+ try:
98
+ module_path, class_name = serializer_class_or_path.rsplit('.', 1)
99
+ module = importlib.import_module(module_path)
100
+ serializer_class = getattr(module, class_name)
101
+ except (ImportError, AttributeError) as e:
102
+ logger.error(f"Failed to import serializer '{serializer_class_or_path}': {e}")
103
+ return False
104
+ else:
105
+ serializer_class = serializer_class_or_path
106
+
107
+ self.serializers[name] = {
108
+ 'class': serializer_class,
109
+ 'description': description or f"{name} serializer",
110
+ 'registered_at': time.time()
111
+ }
112
+
113
+ if is_default or self.default_serializer is None:
114
+ self.default_serializer = name
115
+
116
+ logger.info(f"Registered serializer: {name}")
117
+ return True
118
+
119
+ def get(self, name: str):
120
+ """Get serializer class by name."""
121
+ with self.lock:
122
+ serializer_info = self.serializers.get(name)
123
+ return serializer_info['class'] if serializer_info else None
124
+
125
+ def list_serializers(self):
126
+ """List all registered serializers."""
127
+ with self.lock:
128
+ return {
129
+ name: {
130
+ 'description': info['description'],
131
+ 'class_name': info['class'].__name__,
132
+ 'is_default': name == self.default_serializer
133
+ }
134
+ for name, info in self.serializers.items()
135
+ }
136
+
137
+ def get_default(self):
138
+ """Get the default serializer name."""
139
+ with self.lock:
140
+ return self.default_serializer
141
+
142
+ def set_default(self, name: str):
143
+ """Set default serializer."""
144
+ with self.lock:
145
+ if name in self.serializers:
146
+ self.default_serializer = name
147
+ logger.info(f"Default serializer set to: {name}")
148
+ return True
149
+ return False
150
+
151
+
152
+ # Global serializer registry
153
+ _registry = SerializerRegistry()
154
+
155
+
156
+ class SerializerManager:
157
+ """
158
+ Main serializer manager providing unified interface for all serialization operations.
159
+ """
160
+
161
+ def __init__(self, default_serializer: str = None, enable_performance_tracking: bool = True):
162
+ """
163
+ Initialize serializer manager.
164
+
165
+ :param default_serializer: Override default serializer
166
+ :param enable_performance_tracking: Enable performance monitoring
167
+ """
168
+ self.default_serializer = default_serializer
169
+ self.performance_tracking = enable_performance_tracking
170
+ self.registry = _registry
171
+ self.serializer_class = None
172
+
173
+ # Initialize default serializers if not already done
174
+ self._ensure_default_serializers()
175
+
176
+ # Load configuration from Django settings
177
+ self._load_configuration()
178
+
179
+ def _ensure_default_serializers(self):
180
+ """Ensure default serializers are registered."""
181
+ if not self.registry.serializers:
182
+ for name, import_path in DEFAULT_SERIALIZERS.items():
183
+ self.registry.register(
184
+ name=name,
185
+ serializer_class_or_path=import_path,
186
+ is_default=(name == 'optimized') # Set optimized as default
187
+ )
188
+ for format, import_path in FORMAT_SERIALIZERS.items():
189
+ _registry.register(
190
+ name=format,
191
+ serializer_class_or_path=import_path
192
+ )
193
+
194
+
195
+ def _load_configuration(self):
196
+ """Load configuration from Django settings."""
197
+ # Get default serializer from settings
198
+ default_from_settings = getattr(settings, 'MOJO_DEFAULT_SERIALIZER', None)
199
+ if default_from_settings and not self.default_serializer:
200
+ self.default_serializer = default_from_settings
201
+
202
+ # Register custom serializers from settings
203
+ custom_serializers = getattr(settings, 'MOJO_CUSTOM_SERIALIZERS', {})
204
+ for name, config in custom_serializers.items():
205
+ if isinstance(config, str):
206
+ # Simple string path
207
+ self.registry.register(name, config)
208
+ elif isinstance(config, dict):
209
+ # Detailed configuration
210
+ self.registry.register(
211
+ name=name,
212
+ serializer_class_or_path=config.get('class'),
213
+ description=config.get('description'),
214
+ is_default=config.get('is_default', False)
215
+ )
216
+
217
+ def get_serializer(self, instance, graph: str = "default", many: bool = None,
218
+ serializer_type: str = None, **kwargs):
219
+ if not self.serializer_class:
220
+ self.serializer_class = self.registry.get("optimized")
221
+ return self.serializer_class(instance, graph=graph, many=many, **kwargs)
222
+
223
+
224
+ def get_serializer_old(self, instance, graph: str = "default", many: bool = None,
225
+ serializer_type: str = None, **kwargs):
226
+ """
227
+ Get appropriate serializer for the given instance and parameters.
228
+
229
+ :param instance: Model instance, QuerySet, or list of objects
230
+ :param graph: Graph configuration name
231
+ :param many: Force many=True for list serialization
232
+ :param serializer_type: Override serializer type
233
+ :param kwargs: Additional serializer arguments
234
+ :return: Configured serializer instance
235
+ """
236
+ # Determine serializer type
237
+ if serializer_type is None:
238
+ serializer_type = self.default_serializer or self.registry.get_default()
239
+
240
+ # Get serializer class
241
+ serializer_class = self.registry.get(serializer_type)
242
+ if serializer_class is None:
243
+ logger.warning(f"Serializer '{serializer_type}' not found, using default")
244
+ serializer_type = self.registry.get_default()
245
+ serializer_class = self.registry.get(serializer_type)
246
+
247
+ if serializer_class is None:
248
+ raise ValueError("No serializer available")
249
+
250
+ # Auto-detect many parameter for QuerySets
251
+ if many is None and isinstance(instance, QuerySet):
252
+ many = True
253
+
254
+ # Track usage for performance monitoring
255
+ if self.performance_tracking:
256
+ self._track_usage(serializer_type, instance)
257
+
258
+ # Create and return serializer instance
259
+ try:
260
+ return serializer_class(instance, graph=graph, many=many, **kwargs)
261
+ except Exception as e:
262
+ logger.error(f"Failed to create serializer '{serializer_type}': {e}")
263
+ # Fallback to simple serializer
264
+ fallback_class = self.registry.get('simple')
265
+ if fallback_class and fallback_class != serializer_class:
266
+ logger.info("Falling back to simple serializer")
267
+ return fallback_class(instance, graph=graph, many=many)
268
+ raise
269
+
270
+ def get_format_serializer(self, format: str):
271
+ SerializerClass = self.registry.get(format)
272
+ if SerializerClass:
273
+ return SerializerClass()
274
+ raise ValueError(f"Serializer for format '{format}' not found")
275
+
276
+ def serialize(self, instance, graph: str = "default", many: bool = None,
277
+ serializer_type: str = None, **kwargs):
278
+ """
279
+ Direct serialization method.
280
+
281
+ :param instance: Object(s) to serialize
282
+ :param graph: Graph configuration
283
+ :param many: Force many=True
284
+ :param serializer_type: Override serializer type
285
+ :param kwargs: Additional arguments
286
+ :return: Serialized data
287
+ """
288
+ serializer = self.get_serializer(
289
+ instance=instance,
290
+ graph=graph,
291
+ many=many,
292
+ serializer_type=serializer_type,
293
+ **kwargs
294
+ )
295
+ return serializer.serialize()
296
+
297
+ def to_json(self, instance, graph: str = "default", many: bool = None,
298
+ serializer_type: str = None, **kwargs):
299
+ """
300
+ Serialize to JSON string.
301
+
302
+ :param instance: Object(s) to serialize
303
+ :param graph: Graph configuration
304
+ :param many: Force many=True
305
+ :param serializer_type: Override serializer type
306
+ :param kwargs: Additional JSON arguments
307
+ :return: JSON string
308
+ """
309
+ serializer = self.get_serializer(
310
+ instance=instance,
311
+ graph=graph,
312
+ many=many,
313
+ serializer_type=serializer_type
314
+ )
315
+ return serializer.to_json(**kwargs)
316
+
317
+ def to_response(self, instance, request, graph: str = "default", many: bool = None,
318
+ serializer_type: str = None, **kwargs):
319
+ """
320
+ Serialize to HTTP response.
321
+
322
+ :param instance: Object(s) to serialize
323
+ :param request: Django request object
324
+ :param graph: Graph configuration
325
+ :param many: Force many=True
326
+ :param serializer_type: Override serializer type
327
+ :param kwargs: Additional response arguments
328
+ :return: HttpResponse
329
+ """
330
+ serializer = self.get_serializer(
331
+ instance=instance,
332
+ graph=graph,
333
+ many=many,
334
+ serializer_type=serializer_type
335
+ )
336
+ return serializer.to_response(request, **kwargs)
337
+
338
+ def benchmark_serializers(self, instance, graph: str = "default",
339
+ serializer_types: List[str] = None, iterations: int = 10):
340
+ """
341
+ Benchmark multiple serializers for performance comparison.
342
+
343
+ :param instance: Test instance or queryset
344
+ :param graph: Graph configuration to test
345
+ :param serializer_types: List of serializers to test (default: all)
346
+ :param iterations: Number of iterations per serializer
347
+ :return: Benchmark results
348
+ """
349
+ if serializer_types is None:
350
+ serializer_types = list(self.registry.list_serializers().keys())
351
+
352
+ results = {}
353
+
354
+ for serializer_type in serializer_types:
355
+ logger.info(f"Benchmarking {serializer_type} serializer...")
356
+
357
+ times = []
358
+ errors = 0
359
+
360
+ for i in range(iterations):
361
+ try:
362
+ start_time = time.perf_counter()
363
+
364
+ serializer = self.get_serializer(
365
+ instance=instance,
366
+ graph=graph,
367
+ serializer_type=serializer_type
368
+ )
369
+ data = serializer.serialize()
370
+ json_output = serializer.to_json()
371
+
372
+ end_time = time.perf_counter()
373
+ times.append(end_time - start_time)
374
+
375
+ except Exception as e:
376
+ logger.error(f"Benchmark error for {serializer_type}: {e}")
377
+ errors += 1
378
+
379
+ if times:
380
+ results[serializer_type] = {
381
+ 'min_time': min(times),
382
+ 'max_time': max(times),
383
+ 'avg_time': sum(times) / len(times),
384
+ 'total_time': sum(times),
385
+ 'iterations': len(times),
386
+ 'errors': errors,
387
+ 'objects_per_second': len(times) / sum(times) if sum(times) > 0 else 0
388
+ }
389
+ else:
390
+ results[serializer_type] = {
391
+ 'error': 'All iterations failed',
392
+ 'errors': errors
393
+ }
394
+
395
+ # Store benchmark results
396
+ if self.performance_tracking:
397
+ _PERFORMANCE_DATA['benchmark_results'][time.time()] = results
398
+
399
+ return results
400
+
401
+ def get_performance_stats(self):
402
+ """Get performance statistics for all serializers."""
403
+ stats = {
404
+ 'usage_stats': _PERFORMANCE_DATA['serializer_usage'].copy(),
405
+ 'registered_serializers': self.registry.list_serializers(),
406
+ 'default_serializer': self.registry.get_default()
407
+ }
408
+
409
+ # Add serializer-specific stats
410
+ for name in self.registry.serializers.keys():
411
+ serializer_class = self.registry.get(name)
412
+ if hasattr(serializer_class, 'get_performance_stats'):
413
+ try:
414
+ stats[f'{name}_stats'] = serializer_class.get_performance_stats()
415
+ except Exception as e:
416
+ logger.warning(f"Failed to get stats for {name}: {e}")
417
+
418
+ return stats
419
+
420
+ def clear_caches(self, serializer_type: str = None):
421
+ """
422
+ Clear caches for specified serializer or all serializers.
423
+
424
+ :param serializer_type: Specific serializer to clear, or None for all
425
+ """
426
+ if serializer_type:
427
+ serializer_class = self.registry.get(serializer_type)
428
+ if serializer_class and hasattr(serializer_class, 'clear_caches'):
429
+ serializer_class.clear_caches()
430
+ logger.info(f"Cleared caches for {serializer_type}")
431
+ else:
432
+ # Clear all serializer caches
433
+ for name in self.registry.serializers.keys():
434
+ serializer_class = self.registry.get(name)
435
+ if serializer_class and hasattr(serializer_class, 'clear_caches'):
436
+ try:
437
+ serializer_class.clear_caches()
438
+ except Exception as e:
439
+ logger.warning(f"Failed to clear cache for {name}: {e}")
440
+ logger.info("Cleared all serializer caches")
441
+
442
+ def register_serializer(self, name: str, serializer_class_or_path: Union[str, Type],
443
+ description: str = None, is_default: bool = False):
444
+ """
445
+ Register a new serializer.
446
+
447
+ :param name: Unique serializer name
448
+ :param serializer_class_or_path: Serializer class or import path
449
+ :param description: Optional description
450
+ :param is_default: Set as default serializer
451
+ :return: True if successful
452
+ """
453
+ return self.registry.register(name, serializer_class_or_path, description, is_default)
454
+
455
+ def set_default_serializer(self, name: str):
456
+ """
457
+ Set the default serializer.
458
+
459
+ :param name: Serializer name
460
+ :return: True if successful
461
+ """
462
+ success = self.registry.set_default(name)
463
+ if success:
464
+ self.default_serializer = name
465
+ return success
466
+
467
+ def _track_usage(self, serializer_type: str, instance):
468
+ """Track serializer usage for performance monitoring."""
469
+ if not self.performance_tracking:
470
+ return
471
+
472
+ usage_key = f"{serializer_type}"
473
+ if usage_key not in _PERFORMANCE_DATA['serializer_usage']:
474
+ _PERFORMANCE_DATA['serializer_usage'][usage_key] = {
475
+ 'count': 0,
476
+ 'last_used': None,
477
+ 'total_objects': 0
478
+ }
479
+
480
+ stats = _PERFORMANCE_DATA['serializer_usage'][usage_key]
481
+ stats['count'] += 1
482
+ stats['last_used'] = time.time()
483
+
484
+ # Count objects being serialized
485
+ if isinstance(instance, QuerySet):
486
+ try:
487
+ stats['total_objects'] += instance.count()
488
+ except Exception:
489
+ stats['total_objects'] += 1
490
+ elif isinstance(instance, (list, tuple)):
491
+ stats['total_objects'] += len(instance)
492
+ else:
493
+ stats['total_objects'] += 1
494
+
495
+
496
+ # Global manager instance
497
+ _default_manager = None
498
+
499
+ def get_serializer_manager():
500
+ """Get the global serializer manager instance."""
501
+ global _default_manager
502
+ if _default_manager is None:
503
+ _default_manager = SerializerManager()
504
+ return _default_manager
505
+
506
+ def register_serializer(name: str, serializer_class_or_path: Union[str, Type],
507
+ description: str = None, is_default: bool = False):
508
+ """Register a serializer globally."""
509
+ return get_serializer_manager().register_serializer(name, serializer_class_or_path, description, is_default)
510
+
511
+ def set_default_serializer(name: str):
512
+ """Set the global default serializer."""
513
+ return get_serializer_manager().set_default_serializer(name)
514
+
515
+ def serialize(instance, graph: str = "default", many: bool = None, serializer_type: str = None, **kwargs):
516
+ """Global serialize function."""
517
+ return get_serializer_manager().serialize(instance, graph, many, serializer_type, **kwargs)
518
+
519
+ def to_json(instance, graph: str = "default", many: bool = None, serializer_type: str = None, **kwargs):
520
+ """Global to_json function."""
521
+ return get_serializer_manager().to_json(instance, graph, many, serializer_type, **kwargs)
522
+
523
+ def to_response(instance, request, graph: str = "default", many: bool = None, serializer_type: str = None, **kwargs):
524
+ """Global to_response function."""
525
+ return get_serializer_manager().to_response(instance, request, graph, many, serializer_type, **kwargs)
526
+
527
+ def get_performance_stats():
528
+ """Get global performance statistics."""
529
+ return get_serializer_manager().get_performance_stats()
530
+
531
+ def clear_serializer_caches(serializer_type: str = None):
532
+ """Clear serializer caches globally."""
533
+ return get_serializer_manager().clear_caches(serializer_type)
534
+
535
+ def benchmark_serializers(instance, graph: str = "default", serializer_types: List[str] = None, iterations: int = 10):
536
+ """Benchmark serializers globally."""
537
+ return get_serializer_manager().benchmark_serializers(instance, graph, serializer_types, iterations)
538
+
539
+ def list_serializers():
540
+ """List all registered serializers globally."""
541
+ return get_serializer_manager().registry.list_serializers()
542
+
543
+ # Import ujson availability info
544
+ try:
545
+ import ujson
546
+ HAS_UJSON = True
547
+ UJSON_VERSION = getattr(ujson, '__version__', 'unknown')
548
+ except ImportError:
549
+ HAS_UJSON = False
550
+ UJSON_VERSION = None