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
mojo/apps/tasks/runner.py DELETED
@@ -1,174 +0,0 @@
1
- from importlib import import_module
2
- from concurrent.futures import ThreadPoolExecutor
3
- from .manager import TaskManager
4
- from mojo.tasks import manager
5
- import os
6
- from mojo.helpers import logit
7
- from mojo.helpers import daemon
8
- from mojo.helpers import paths
9
- import time
10
-
11
-
12
- class TaskEngine(daemon.Daemon):
13
- """
14
- The TaskEngine is responsible for managing and executing tasks across different channels.
15
- It leverages a thread pool to execute tasks concurrently and uses a task manager to maintain task states.
16
- """
17
- def __init__(self, channels=["broadcast"], max_workers=5):
18
- """
19
- Initialize the TaskEngine.
20
-
21
- Args:
22
- channels (list): A list of channel names where tasks are queued.
23
- max_workers (int, optional): The maximum number of threads available for task execution. Defaults to 5.
24
- """
25
- super().__init__("taskit", os.path.join(paths.VAR_ROOT, "taskit"))
26
- self.manager = manager.TaskManager(channels)
27
- self.channels = channels
28
- if "broadcast" not in self.channels:
29
- self.channels.append("broadcast")
30
- self.max_workers = max_workers
31
- self.executor = None
32
- self.logger = logit.get_logger("taskit", "taskit.log")
33
-
34
- def reset_running_tasks(self):
35
- """
36
- Reset tasks that are stuck in a running state by moving them back to the pending state.
37
- """
38
- for channel in self.channels:
39
- for task_id in self.manager.get_running_ids(channel):
40
- self.logger.info(f"moving task {task_id} from running to pending")
41
- self.manager.remove_from_running(channel, task_id)
42
- self.manager.add_to_pending(channel, task_id)
43
-
44
- def queue_pending_tasks(self):
45
- """
46
- Queue all the pending tasks for execution.
47
- """
48
- for channel in self.channels:
49
- for task_id in self.manager.get_pending_ids(channel):
50
- self.queue_task(task_id)
51
-
52
- def handle_message(self, message):
53
- """
54
- Handle incoming messages from the channels, decoding task identifiers and queuing them for execution.
55
-
56
- Args:
57
- message (dict): A dictionary with message data containing task information.
58
- """
59
- self.queue_task(message['data'].decode())
60
-
61
- def on_run_task(self, task_id):
62
- """
63
- Execute a task based on its identifier by locating the relevant function and executing it.
64
-
65
- Args:
66
- task_id (str): The identifier of the task to be executed.
67
- """
68
- # this is a keep it thread safe with the redis connection
69
- tman = TaskManager([])
70
- task_data = tman.get_task(task_id)
71
- if not task_data:
72
- # this task has expired or no longer exists
73
- self.logger.info(f"Task {task_id} has expired or no longer exists")
74
- tman.remove_from_pending(task_id)
75
- return
76
- self.logger.info(f"Executing task {task_id}")
77
- function_path = task_data.get('function')
78
- module_name, func_name = function_path.rsplit('.', 1)
79
- module = import_module(module_name)
80
- func = getattr(module, func_name)
81
- self.manager.remove_from_pending(task_id, task_data.channel)
82
- self.manager.add_to_running(task_id, task_data.channel)
83
-
84
- try:
85
- task_data.started_at = time.time()
86
- func(task_data)
87
- task_data.completed_at = time.time()
88
- task_data.elapsed_time = task_data.completed_at - task_data.started_at
89
- tman.save_task(task_data)
90
- tman.add_to_completed(task_data)
91
- self.logger.info(f"Task {task_id} completed after {task_data.elapsed_time} seconds")
92
- except Exception as e:
93
- self.logger.error(f"Error executing task {task_id}: {str(e)}")
94
- tman.add_to_errors(task_data, str(e))
95
- finally:
96
- tman.remove_from_running(task_id, task_data.channel)
97
-
98
- def queue_task(self, task_id):
99
- """
100
- Submit a task for execution in the thread pool.
101
-
102
- Args:
103
- task_id (str): The identifier of the task to be queued.
104
- """
105
- self.logger.info(f"adding task {task_id}")
106
- self.executor.submit(self.on_run_task, task_id)
107
-
108
- def wait_for_all_tasks_to_complete(self, timeout=30):
109
- """
110
- Wait for all tasks submitted to the executor to complete.
111
- """
112
- self.executor.shutdown(wait=True, timeout=timeout)
113
- # Check if there are still active threads
114
- active_threads = [thread for thread in self.executor._threads if thread.is_alive()]
115
- if active_threads:
116
- self.logger.warning(f"shutdown issue, {len(active_threads)} tasks exceeded timeout")
117
- self.executor.shutdown(wait=False) # Stop accepting new tasks
118
-
119
- def start_listening(self):
120
- """
121
- Listen for messages on the subscribed channels and handle them as they arrive.
122
- """
123
- self.logger.info("starting with channels...", self.channels)
124
- self.reset_running_tasks()
125
- self.queue_pending_tasks()
126
- pubsub = self.manager.redis.pubsub()
127
- channel_keys = {self.manager.get_channel_key(channel): self.handle_message for channel in self.channels}
128
- pubsub.subscribe(**channel_keys)
129
- for message in pubsub.listen():
130
- if not self.running:
131
- self.logger.info("shutting down, waiting for tasks to complete")
132
- self.wait_for_all_tasks_to_complete()
133
- self.logger.info("shutdown complete")
134
- return
135
- if message['type'] != 'message':
136
- continue
137
- self.handle_message(message)
138
-
139
- def run(self):
140
- self.executor = ThreadPoolExecutor(max_workers=self.max_workers)
141
- self.start_listening()
142
-
143
-
144
- # HELPERS FOR RUNNING VIA CLI
145
- def get_args():
146
- """
147
- Setup the argument parser for command-line interface.
148
-
149
- Returns:
150
- Namespace: Parsed command-line arguments.
151
- """
152
- import argparse
153
- parser = argparse.ArgumentParser(description="TaskEngine Background Service")
154
- parser.add_argument("--start", action="store_true", help="Start the daemon")
155
- parser.add_argument("--stop", action="store_true", help="Stop the daemon")
156
- parser.add_argument("--foreground", "-f", action="store_true", help="Run in foreground mode")
157
- parser.add_argument("-v", "--verbose", action="store_true",
158
- help="Enable verbose logging")
159
- return parser, parser.parse_args()
160
-
161
-
162
- def main():
163
- from mojo.helpers.settings import settings
164
- parser, args = get_args()
165
- daemon = TaskEngine(settings.TASKIT_CHANNELS)
166
- if args.start:
167
- daemon.start()
168
- elif args.stop:
169
- daemon.stop()
170
- elif args.foreground:
171
- print("Running in foreground mode...")
172
- daemon.run()
173
- else:
174
- parser.print_help()
@@ -1,14 +0,0 @@
1
- from mojo.helpers import logit
2
- import time
3
-
4
- logger = logit.get_logger("ti_example", "ti_example.log")
5
-
6
- def run_example_task(task):
7
- logger.info("Running example task with data", task)
8
- time.sleep(task.data.get("duration", 5))
9
-
10
-
11
- def run_error_task(task):
12
- logger.info("Running error task with data", task)
13
- time.sleep(2)
14
- raise Exception("Example error")
File without changes
mojo/helpers/redis.py DELETED
@@ -1,10 +0,0 @@
1
- from redis import ConnectionPool, StrictRedis
2
- from mojo.helpers.settings import settings
3
- REDIS_POOL = None
4
-
5
-
6
- def get_connection():
7
- global REDIS_POOL
8
- if REDIS_POOL is None:
9
- REDIS_POOL = ConnectionPool(**settings.REDIS_DB)
10
- return StrictRedis(connection_pool=REDIS_POOL)
mojo/models/meta.py DELETED
@@ -1,262 +0,0 @@
1
- from objict import objict
2
- from django.db import models as dm
3
- import string
4
- from rest.encryption import ENCRYPTER, DECRYPTER
5
- from datetime import datetime, date
6
-
7
- class MetaDataBase(dm.Model):
8
- class Meta:
9
- abstract = True
10
-
11
- category = dm.CharField(db_index=True, max_length=32, default=None, null=True, blank=True)
12
- key = dm.CharField(db_index=True, max_length=80)
13
- value_format = dm.CharField(max_length=16)
14
- value = dm.TextField()
15
- int_value = dm.IntegerField(default=None, null=True, blank=True)
16
- float_value = dm.FloatField(default=None, null=True, blank=True)
17
-
18
- def set_value(self, value):
19
- self.value = str(value)
20
- value_type = type(value)
21
-
22
- if value_type is int or self.value in ["0", "1"]:
23
- if value_type is int and value > 2147483647:
24
- self.value_format = "S"
25
- return
26
- self.value_format = "I"
27
- self.int_value = value
28
- elif value_type is float:
29
- self.value_format = "F"
30
- self.float_value = value
31
- elif isinstance(value, list):
32
- self.value_format = "L"
33
- elif isinstance(value, dict):
34
- self.value_format = "O"
35
- elif isinstance(value, str) and len(value) < 9 and value.isdigit():
36
- self.value_format = "I"
37
- self.int_value = int(value)
38
- elif value in ["True", "true", "False", "false"]:
39
- self.value_format = "I"
40
- self.int_value = 1 if value.lower() == "true" else 0
41
- elif isinstance(value, bool):
42
- self.value_format = "I"
43
- self.int_value = 1 if value else 0
44
- else:
45
- self.value_format = "S"
46
-
47
- def get_strict_type(self, field_type):
48
- try:
49
- return field_type(self.value)
50
- except (ValueError, TypeError):
51
- if field_type is bool:
52
- return self.int_value != 0 if self.value_format == 'I' else self.value.lower() in ['true', '1', 'y', 'yes']
53
- elif field_type in [date, datetime]:
54
- return rh.parseDate(self.value)
55
- return self.value
56
-
57
- def get_value(self, field_type=None):
58
- if field_type:
59
- return self.get_strict_type(field_type)
60
- if self.value_format == 'I':
61
- return self.int_value
62
- elif self.value_format == 'F':
63
- return self.float_value
64
- elif self.value_format in ["L", "O"] and self.value:
65
- try:
66
- return eval(self.value)
67
- except Exception:
68
- pass
69
- return self.value
70
-
71
- def __str__(self):
72
- return f"{self.category}.{self.key}={self.value}" if self.category else f"{self.key}={self.value}"
73
-
74
- class MetaDataModel:
75
- def set_metadata(self, request, values=None):
76
- if not self.id:
77
- self.save()
78
-
79
- values = values or request
80
- if isinstance(values, list):
81
- values = objict({k: v for item in values if isinstance(item, dict) for k, v in item.items()})
82
-
83
- if not isinstance(values, dict):
84
- raise ValueError(f"invalid metadata: {values}")
85
-
86
- for key, value in values.items():
87
- cat, key = key.split('.', 1) if '.' in key else (None, key)
88
- self.set_property(key, value, cat, request=request)
89
-
90
- def metadata(self):
91
- return self.get_properties()
92
-
93
- def remove_properties(self, category=None):
94
- self.properties.filter(category=category).delete()
95
-
96
- def get_properties(self, category=None):
97
- result = {}
98
- for prop in self.properties.all():
99
- category_ = prop.category
100
- key = prop.key
101
-
102
- if not category_:
103
- self._add_property_to_result(result, prop)
104
- continue
105
-
106
- props = self.get_field_props(category_)
107
- if props.hidden:
108
- continue
109
-
110
- if category_ not in result:
111
- result[category_] = {}
112
-
113
- if category_ == "secrets":
114
- masked_value = "*" * prop.int_value if prop.int_value else "******"
115
- result[category_][key] = masked_value
116
- else:
117
- self._add_property_to_result(result[category_], prop)
118
-
119
- return result.get(category, {}) if category else result
120
-
121
- def _add_property_to_result(self, result_dict, prop):
122
- props = self.get_field_props(prop.key)
123
- if not props.hidden:
124
- result_dict[prop.key] = prop.get_value()
125
-
126
- def get_field_props(self, key):
127
- self._init_field_props()
128
- category, key = key.split('.', 1) if '.' in key else (None, key)
129
- props = objict()
130
-
131
- if self.__field_props:
132
- cat_props = self.__field_props.get(category, {})
133
- self._update_props_with_category(props, cat_props)
134
-
135
- field_props = self.__field_props.get(key, {})
136
- self._update_props_with_field(props, field_props)
137
-
138
- return props
139
-
140
- def _update_props_with_category(self, props, cat_props):
141
- if cat_props:
142
- props.notify = cat_props.get("notify")
143
- props.requires = cat_props.get("requires")
144
- props.hidden = cat_props.get("hidden", False)
145
- on_change_name = cat_props.get("on_change")
146
- if on_change_name:
147
- props.on_change = getattr(self, on_change_name, None)
148
-
149
- def _update_props_with_field(self, props, field_props):
150
- props.notify = field_props.get("notify", props.notify)
151
- props.requires = field_props.get("requires", props.requires)
152
- props.hidden = field_props.get("hidden", props.hidden)
153
- on_change_name = field_props.get("on_change")
154
- if on_change_name:
155
- props.on_change = getattr(self, on_change_name, None)
156
-
157
- def check_field_perms(self, full_key, props, request=None):
158
- if not props.requires:
159
- return True
160
- if not request or not request.member:
161
- return False
162
- if request.member.hasPermission(props.requires) or request.user.is_superuser:
163
- return True
164
-
165
- if props.notify and request.member:
166
- subject = f"permission denied changing protected '{full_key}' field"
167
- msg = f"permission denied changing protected field '{full_key}'\nby user: {request.user.username}\nfor: {self}"
168
- request.member.notifyWithPermission(props.notify, subject, msg, email_only=True)
169
- raise re.PermissionDeniedException(subject, 481)
170
-
171
- def set_properties(self, data, category=None, request=None, using=None):
172
- for k, v in data.items():
173
- self.set_property(k, v, category, request=request, using=using)
174
-
175
- def set_property(self, key, value, category=None, request=None, using=None, ascii_only=False, encrypted=False):
176
- if ascii_only and isinstance(value, str):
177
- value = ''.join(filter(lambda x: x in string.printable, value))
178
-
179
- if using is None:
180
- using = getattr(self.RestMeta, "DATABASE", None)
181
-
182
- if request is None:
183
- request = rh.getActiveRequest()
184
-
185
- self._init_field_props()
186
-
187
- if '.' in key:
188
- category, key = key.split('.', 1)
189
-
190
- full_key = f"{category}.{key}" if category else key
191
- field_props = self.get_field_props(full_key)
192
-
193
- if not self.check_field_perms(full_key, field_props, request):
194
- return False
195
-
196
- prop = self.properties.filter(category=category, key=key).last()
197
- if not prop and (value is None or value == ""):
198
- return False
199
-
200
- has_changed, old_value = self._update_or_create_property(prop, category, key, value, encrypted, using)
201
-
202
- if has_changed and field_props.on_change:
203
- field_props.on_change(key, value, old_value, category)
204
-
205
- self._notify_change_if_required(field_props, full_key, value, request)
206
-
207
- if hasattr(self, "_recordRestChange"):
208
- self._recordRestChange(f"metadata.{full_key}", old_value)
209
-
210
- return has_changed
211
-
212
- def _update_or_create_property(self, prop, category, key, value, encrypted, using):
213
- has_changed = False
214
- old_value = None
215
-
216
- value_len = len(value) if encrypted else 0
217
- if encrypted:
218
- value = ENCRYPTER.encrypt(value)
219
-
220
- if prop:
221
- old_value = prop.get_value()
222
- if value is None or value == "":
223
- self.properties.filter(category=category, key=key).delete()
224
- has_changed = True
225
- else:
226
- has_changed = str(value) != prop.value
227
- if has_changed:
228
- prop.set_value(value)
229
- if encrypted:
230
- prop.int_value = value_len
231
- prop.save(using=using)
232
- else:
233
- has_changed = True
234
- PropClass = self.get_fk_model("properties")
235
- prop = PropClass(parent=self, key=key, category=category)
236
- prop.set_value(value)
237
- prop.save(using=using)
238
-
239
- return has_changed, old_value
240
-
241
- def _notify_change_if_required(self, field_props, full_key, value, request):
242
- if field_props.notify and request and request.member:
243
- username = request.member.username if request and request.member else "root"
244
- truncated_value = "***" if value and len(str(value)) > 5 else value
245
- msg = (f"protected field '{full_key}' changed to '{truncated_value}'\n"
246
- f"by user: {username}\nfor: {self}")
247
- request.member.notifyWithPermission(field_props.notify, f"protected '{full_key}' field changed", msg, email_only=True)
248
-
249
- def get_property(self, key, default=None, category=None, field_type=None, decrypted=False):
250
- category, key = key.split('.', 1) if '.' in key else (category, key)
251
-
252
- try:
253
- prop_value = self.properties.get(category=category, key=key).get_value(field_type)
254
- return DECRYPTER.decrypt(prop_value) if decrypted and prop_value else prop_value
255
- except Exception:
256
- return default
257
-
258
- def set_secret_property(self, key, value):
259
- return self.set_property(key, value, category="secrets", encrypted=True)
260
-
261
- def get_secret_property(self, key, default=None):
262
- return self.get_property(key, default, "secrets", decrypted=True)
mojo/ws4redis/README.md DELETED
@@ -1,174 +0,0 @@
1
- # Websocket HOWTO
2
-
3
- ## Authentication
4
-
5
- ### JWT
6
-
7
- Requires an existing JWT token that has gone through authentication process via rest
8
-
9
- ```json
10
- {
11
- "action": "auth",
12
- "kind": "jwt",
13
- "token": "..."
14
- }
15
- ```
16
-
17
-
18
-
19
- ### Model Authentication
20
-
21
- You can implement custom authentication flows via a model by using the WS4REDIS_AUTHENTICATORS in your django settings.py.
22
-
23
- ##### WS4REDIS_AUTHENTICATORS
24
-
25
- ```python
26
- WS4REDIS_AUTHENTICATORS = {
27
- "mymodel": "myapp.MyModel"
28
- }
29
- ```
30
-
31
- In your Model you will need to add the following class methods.
32
-
33
- This method is used by the async/websocket service to authenticate.
34
- If the model can authenticate the connection it should return dict with kind and pk of the model that is authenticaed.
35
-
36
-
37
-
38
- ##### authWS4RedisConnection
39
-
40
- This method will authenticate the model, or return None if authentication failed.
41
-
42
- ```python
43
- @classmethod
44
- def authWS4RedisConnection(cls, auth_data):
45
- if auth_data and auth_data.token:
46
- terminal = cls.objects.filter(token=auth_data.token).last()
47
- if terminal is not None:
48
- # we now return the terminal credentials to the framework
49
- return UberDict(
50
- kind="terminal",
51
- pk=terminal.id,
52
- uuid=terminal.tid,
53
- token=auth_data.token,
54
- only_one=True, # only allows one connection at a time
55
- instance=terminal)
56
- return None
57
- ```
58
-
59
-
60
-
61
- ##### canPublishTo
62
-
63
- Add this to your Model to validate messages from this connection to be sent to this channel.
64
-
65
- ```python
66
- @classmethod
67
- def canPublishTo(cls, credentials, msg):
68
- if credentials:
69
- return True
70
- return False
71
- ```
72
-
73
-
74
-
75
- ##### WS4REDIS_CHANNELS
76
-
77
- Map channels to models
78
-
79
- ```python
80
- WS4REDIS_CHANNELS = {
81
- "group": "account.Group",
82
- "chat": "chat.Room",
83
- }
84
- ```
85
-
86
-
87
-
88
- ##### onWS4RedisMessage
89
-
90
- Add this to your Model to allow for handling of messages sent to this channel.
91
-
92
- ```python
93
- @classmethod
94
- def onWS4RedisMessage(cls, credentials, msg):
95
- if msg.action == "status":
96
- cls.createStatusRecord(msg)
97
-
98
- ```
99
-
100
-
101
-
102
- ### URL Params
103
-
104
- You can also use params in the url of the websocket.
105
-
106
- **THIS IS NOT RECOMMENDED as the url params are not encrypted and can be easily snooped.**
107
-
108
- Include something like the follow in your django settings.py:
109
-
110
- ```python
111
- def URL_AUTHENTICATOR(ws_con):
112
- from objict import objict
113
- token = ws_con.request.GET.get("token", None)
114
- session_key = ws_con.request.GET.get("session_key", None)
115
- if token is not None:
116
- # this example assume the token is used for terminal auth
117
- # you will still need to implement the Custom Auth flows to handle this
118
- ws_con.on_auth(objict(kind="terminal", token=token))
119
- elif session_key is not None:
120
- # or alternative is a session
121
- ws_con.on_auth(objict(kind="session", token=session_key))
122
-
123
- ```
124
-
125
-
126
-
127
- ## Subscribe
128
-
129
- ```json
130
- {
131
- "action": "subscribe",
132
- "channel": "group",
133
- "pk": 3,
134
- }
135
- ```
136
-
137
- ### Security
138
-
139
- In settins WS4REDIS_CHANNELS, map your channel to a model.
140
- The model should have a classmethod for canSubscribeTo that returns a list of pk they can subscribe to.
141
-
142
-
143
- ## UnSubscribe
144
-
145
- ```json
146
- {
147
- "action": "unsubscribe",
148
- "channel": "group",
149
- "pk": 3,
150
- }
151
- ```
152
-
153
-
154
- ## Publish / Send To
155
-
156
- ```json
157
- {
158
- "action": "publish",
159
- "channel": "group",
160
- "pk": 3,
161
- "message": "..."
162
- }
163
- ```
164
-
165
- ### Security
166
-
167
- In settins WS4REDIS_CHANNELS, map your channel to a model.
168
- The model should have a classmethod for canPublishTo that returns a list of pk they can publish to.
169
-
170
-
171
- ## Custom Messages
172
-
173
- If an unknown action is sent with a channel then the framework will call onWS4RedisMessage on the channel model.
174
-
mojo/ws4redis/__init__.py DELETED
@@ -1,2 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- __version__ = '1.1.8'