django-nativemojo 0.1.10__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 (194) hide show
  1. django_nativemojo-0.1.10.dist-info/LICENSE +19 -0
  2. django_nativemojo-0.1.10.dist-info/METADATA +96 -0
  3. django_nativemojo-0.1.10.dist-info/NOTICE +8 -0
  4. django_nativemojo-0.1.10.dist-info/RECORD +194 -0
  5. django_nativemojo-0.1.10.dist-info/WHEEL +4 -0
  6. mojo/__init__.py +3 -0
  7. mojo/apps/account/__init__.py +1 -0
  8. mojo/apps/account/admin.py +91 -0
  9. mojo/apps/account/apps.py +16 -0
  10. mojo/apps/account/migrations/0001_initial.py +77 -0
  11. mojo/apps/account/migrations/0002_user_is_email_verified_user_is_phone_verified.py +23 -0
  12. mojo/apps/account/migrations/0003_group_mojo_secrets_user_mojo_secrets.py +23 -0
  13. mojo/apps/account/migrations/__init__.py +0 -0
  14. mojo/apps/account/models/__init__.py +3 -0
  15. mojo/apps/account/models/group.py +98 -0
  16. mojo/apps/account/models/member.py +95 -0
  17. mojo/apps/account/models/pkey.py +18 -0
  18. mojo/apps/account/models/user.py +211 -0
  19. mojo/apps/account/rest/__init__.py +3 -0
  20. mojo/apps/account/rest/group.py +25 -0
  21. mojo/apps/account/rest/user.py +47 -0
  22. mojo/apps/account/utils/__init__.py +0 -0
  23. mojo/apps/account/utils/jwtoken.py +72 -0
  24. mojo/apps/account/utils/passkeys.py +54 -0
  25. mojo/apps/fileman/README.md +549 -0
  26. mojo/apps/fileman/__init__.py +0 -0
  27. mojo/apps/fileman/apps.py +15 -0
  28. mojo/apps/fileman/backends/__init__.py +117 -0
  29. mojo/apps/fileman/backends/base.py +319 -0
  30. mojo/apps/fileman/backends/filesystem.py +397 -0
  31. mojo/apps/fileman/backends/s3.py +398 -0
  32. mojo/apps/fileman/examples/configurations.py +378 -0
  33. mojo/apps/fileman/examples/usage_example.py +665 -0
  34. mojo/apps/fileman/management/__init__.py +1 -0
  35. mojo/apps/fileman/management/commands/__init__.py +1 -0
  36. mojo/apps/fileman/management/commands/cleanup_expired_uploads.py +222 -0
  37. mojo/apps/fileman/models/__init__.py +7 -0
  38. mojo/apps/fileman/models/file.py +292 -0
  39. mojo/apps/fileman/models/manager.py +227 -0
  40. mojo/apps/fileman/models/render.py +0 -0
  41. mojo/apps/fileman/rest/__init__ +0 -0
  42. mojo/apps/fileman/rest/__init__.py +23 -0
  43. mojo/apps/fileman/rest/fileman.py +13 -0
  44. mojo/apps/fileman/rest/upload.py +92 -0
  45. mojo/apps/fileman/utils/__init__.py +19 -0
  46. mojo/apps/fileman/utils/upload.py +616 -0
  47. mojo/apps/incident/__init__.py +1 -0
  48. mojo/apps/incident/handlers/__init__.py +3 -0
  49. mojo/apps/incident/handlers/event_handlers.py +142 -0
  50. mojo/apps/incident/migrations/0001_initial.py +83 -0
  51. mojo/apps/incident/migrations/0002_rename_bundle_ruleset_bundle_minutes_event_hostname_and_more.py +44 -0
  52. mojo/apps/incident/migrations/0003_alter_event_model_id.py +18 -0
  53. mojo/apps/incident/migrations/0004_alter_incident_model_id.py +18 -0
  54. mojo/apps/incident/migrations/__init__.py +0 -0
  55. mojo/apps/incident/models/__init__.py +3 -0
  56. mojo/apps/incident/models/event.py +135 -0
  57. mojo/apps/incident/models/incident.py +33 -0
  58. mojo/apps/incident/models/rule.py +247 -0
  59. mojo/apps/incident/parsers/__init__.py +0 -0
  60. mojo/apps/incident/parsers/ossec/__init__.py +1 -0
  61. mojo/apps/incident/parsers/ossec/core.py +82 -0
  62. mojo/apps/incident/parsers/ossec/parsed.py +23 -0
  63. mojo/apps/incident/parsers/ossec/rules.py +124 -0
  64. mojo/apps/incident/parsers/ossec/utils.py +169 -0
  65. mojo/apps/incident/reporter.py +42 -0
  66. mojo/apps/incident/rest/__init__.py +2 -0
  67. mojo/apps/incident/rest/event.py +23 -0
  68. mojo/apps/incident/rest/ossec.py +22 -0
  69. mojo/apps/logit/__init__.py +0 -0
  70. mojo/apps/logit/admin.py +37 -0
  71. mojo/apps/logit/migrations/0001_initial.py +32 -0
  72. mojo/apps/logit/migrations/0002_log_duid_log_payload_log_username.py +28 -0
  73. mojo/apps/logit/migrations/0003_log_level.py +18 -0
  74. mojo/apps/logit/migrations/__init__.py +0 -0
  75. mojo/apps/logit/models/__init__.py +1 -0
  76. mojo/apps/logit/models/log.py +57 -0
  77. mojo/apps/logit/rest.py +9 -0
  78. mojo/apps/metrics/README.md +79 -0
  79. mojo/apps/metrics/__init__.py +12 -0
  80. mojo/apps/metrics/redis_metrics.py +331 -0
  81. mojo/apps/metrics/rest/__init__.py +1 -0
  82. mojo/apps/metrics/rest/base.py +152 -0
  83. mojo/apps/metrics/rest/db.py +0 -0
  84. mojo/apps/metrics/utils.py +227 -0
  85. mojo/apps/notify/README.md +91 -0
  86. mojo/apps/notify/README_NOTIFICATIONS.md +566 -0
  87. mojo/apps/notify/__init__.py +0 -0
  88. mojo/apps/notify/admin.py +52 -0
  89. mojo/apps/notify/handlers/__init__.py +0 -0
  90. mojo/apps/notify/handlers/example_handlers.py +516 -0
  91. mojo/apps/notify/handlers/ses/__init__.py +25 -0
  92. mojo/apps/notify/handlers/ses/bounce.py +0 -0
  93. mojo/apps/notify/handlers/ses/complaint.py +25 -0
  94. mojo/apps/notify/handlers/ses/message.py +86 -0
  95. mojo/apps/notify/management/__init__.py +0 -0
  96. mojo/apps/notify/management/commands/__init__.py +1 -0
  97. mojo/apps/notify/management/commands/process_notifications.py +370 -0
  98. mojo/apps/notify/mod +0 -0
  99. mojo/apps/notify/models/__init__.py +12 -0
  100. mojo/apps/notify/models/account.py +128 -0
  101. mojo/apps/notify/models/attachment.py +24 -0
  102. mojo/apps/notify/models/bounce.py +68 -0
  103. mojo/apps/notify/models/complaint.py +40 -0
  104. mojo/apps/notify/models/inbox.py +113 -0
  105. mojo/apps/notify/models/inbox_message.py +173 -0
  106. mojo/apps/notify/models/outbox.py +129 -0
  107. mojo/apps/notify/models/outbox_message.py +288 -0
  108. mojo/apps/notify/models/template.py +30 -0
  109. mojo/apps/notify/providers/__init__.py +0 -0
  110. mojo/apps/notify/providers/aws.py +73 -0
  111. mojo/apps/notify/rest/__init__.py +0 -0
  112. mojo/apps/notify/rest/ses.py +0 -0
  113. mojo/apps/notify/utils/__init__.py +2 -0
  114. mojo/apps/notify/utils/notifications.py +404 -0
  115. mojo/apps/notify/utils/parsing.py +202 -0
  116. mojo/apps/notify/utils/render.py +144 -0
  117. mojo/apps/tasks/README.md +118 -0
  118. mojo/apps/tasks/__init__.py +11 -0
  119. mojo/apps/tasks/manager.py +489 -0
  120. mojo/apps/tasks/rest/__init__.py +2 -0
  121. mojo/apps/tasks/rest/hooks.py +0 -0
  122. mojo/apps/tasks/rest/tasks.py +62 -0
  123. mojo/apps/tasks/runner.py +174 -0
  124. mojo/apps/tasks/tq_handlers.py +14 -0
  125. mojo/decorators/__init__.py +3 -0
  126. mojo/decorators/auth.py +25 -0
  127. mojo/decorators/cron.py +31 -0
  128. mojo/decorators/http.py +132 -0
  129. mojo/decorators/validate.py +14 -0
  130. mojo/errors.py +88 -0
  131. mojo/helpers/__init__.py +0 -0
  132. mojo/helpers/aws/__init__.py +0 -0
  133. mojo/helpers/aws/client.py +8 -0
  134. mojo/helpers/aws/s3.py +268 -0
  135. mojo/helpers/aws/setup_email.py +0 -0
  136. mojo/helpers/cron.py +79 -0
  137. mojo/helpers/crypto/__init__.py +4 -0
  138. mojo/helpers/crypto/aes.py +60 -0
  139. mojo/helpers/crypto/hash.py +59 -0
  140. mojo/helpers/crypto/privpub/__init__.py +1 -0
  141. mojo/helpers/crypto/privpub/hybrid.py +97 -0
  142. mojo/helpers/crypto/privpub/rsa.py +104 -0
  143. mojo/helpers/crypto/sign.py +36 -0
  144. mojo/helpers/crypto/too.l.py +25 -0
  145. mojo/helpers/crypto/utils.py +26 -0
  146. mojo/helpers/daemon.py +94 -0
  147. mojo/helpers/dates.py +69 -0
  148. mojo/helpers/dns/__init__.py +0 -0
  149. mojo/helpers/dns/godaddy.py +62 -0
  150. mojo/helpers/filetypes.py +128 -0
  151. mojo/helpers/logit.py +310 -0
  152. mojo/helpers/modules.py +95 -0
  153. mojo/helpers/paths.py +63 -0
  154. mojo/helpers/redis.py +10 -0
  155. mojo/helpers/request.py +89 -0
  156. mojo/helpers/request_parser.py +269 -0
  157. mojo/helpers/response.py +14 -0
  158. mojo/helpers/settings.py +146 -0
  159. mojo/helpers/sysinfo.py +140 -0
  160. mojo/helpers/ua.py +0 -0
  161. mojo/middleware/__init__.py +0 -0
  162. mojo/middleware/auth.py +26 -0
  163. mojo/middleware/logging.py +55 -0
  164. mojo/middleware/mojo.py +21 -0
  165. mojo/migrations/0001_initial.py +32 -0
  166. mojo/migrations/__init__.py +0 -0
  167. mojo/models/__init__.py +2 -0
  168. mojo/models/meta.py +262 -0
  169. mojo/models/rest.py +538 -0
  170. mojo/models/secrets.py +59 -0
  171. mojo/rest/__init__.py +1 -0
  172. mojo/rest/info.py +26 -0
  173. mojo/serializers/__init__.py +0 -0
  174. mojo/serializers/models.py +165 -0
  175. mojo/serializers/openapi.py +188 -0
  176. mojo/urls.py +38 -0
  177. mojo/ws4redis/README.md +174 -0
  178. mojo/ws4redis/__init__.py +2 -0
  179. mojo/ws4redis/client.py +283 -0
  180. mojo/ws4redis/connection.py +327 -0
  181. mojo/ws4redis/exceptions.py +32 -0
  182. mojo/ws4redis/redis.py +183 -0
  183. mojo/ws4redis/servers/__init__.py +0 -0
  184. mojo/ws4redis/servers/base.py +86 -0
  185. mojo/ws4redis/servers/django.py +171 -0
  186. mojo/ws4redis/servers/uwsgi.py +63 -0
  187. mojo/ws4redis/settings.py +45 -0
  188. mojo/ws4redis/utf8validator.py +128 -0
  189. mojo/ws4redis/websocket.py +403 -0
  190. testit/__init__.py +0 -0
  191. testit/client.py +147 -0
  192. testit/faker.py +20 -0
  193. testit/helpers.py +198 -0
  194. testit/runner.py +262 -0
@@ -0,0 +1,174 @@
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()
@@ -0,0 +1,14 @@
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")
@@ -0,0 +1,3 @@
1
+ from .http import *
2
+ from .validate import *
3
+ from .auth import *
@@ -0,0 +1,25 @@
1
+ from functools import wraps
2
+ import mojo.errors
3
+
4
+ def requires_perms(*required_perms):
5
+ def decorator(func):
6
+ @wraps(func)
7
+ def wrapper(request, *args, **kwargs):
8
+ if not request.user.is_authenticated:
9
+ raise mojo.errors.PermissionDeniedException()
10
+ if not request.user.has_permission(required_perms):
11
+ raise mojo.errors.PermissionDeniedException()
12
+ return func(request, *args, **kwargs)
13
+ return wrapper
14
+ return decorator
15
+
16
+
17
+ def requires_auth():
18
+ def decorator(func):
19
+ @wraps(func)
20
+ def wrapper(request, *args, **kwargs):
21
+ if not request.user.is_authenticated:
22
+ raise mojo.errors.PermissionDeniedException()
23
+ return func(request, *args, **kwargs)
24
+ return wrapper
25
+ return decorator
@@ -0,0 +1,31 @@
1
+ from typing import Callable
2
+
3
+ def schedule(minutes: str = '*', hours: str = '*', days: str = '*',
4
+ months: str = '*', weekdays: str = '*') -> Callable:
5
+ """
6
+ A decorator to schedule functions based on cron syntax.
7
+
8
+ Args:
9
+ minutes (str): The minutes argument for the cron schedule (default is '*').
10
+ hours (str): The hours argument for the cron schedule (default is '*').
11
+ days (str): The days of the month argument for the cron schedule (default is '*').
12
+ months (str): The months argument for the cron schedule (default is '*').
13
+ weekdays (str): The days of the week argument for the cron schedule (default is '*').
14
+
15
+ Returns:
16
+ Callable: The decorated function.
17
+ """
18
+ def decorator(func: Callable) -> Callable:
19
+ if not hasattr(decorator, 'scheduled_functions'):
20
+ decorator.scheduled_functions = []
21
+ cron_spec = {
22
+ 'func': func,
23
+ 'minutes': minutes,
24
+ 'hours': hours,
25
+ 'days': days,
26
+ 'months': months,
27
+ 'weekdays': weekdays
28
+ }
29
+ decorator.scheduled_functions.append(cron_spec)
30
+ return func
31
+ return decorator
@@ -0,0 +1,132 @@
1
+ import sys
2
+ import traceback
3
+ from mojo.helpers.settings import settings
4
+ from mojo.helpers import modules as jm
5
+ from mojo.helpers import logit
6
+ import mojo.errors
7
+ from django.urls import path, re_path
8
+ # from django.http import JsonResponse
9
+ from mojo.helpers.response import JsonResponse
10
+ from functools import wraps
11
+ from mojo.helpers import modules
12
+ from mojo.models import rest
13
+ from mojo.apps import metrics
14
+
15
+ logger = logit.get_logger("error", "error.log")
16
+ # logger.info("created")
17
+
18
+ # Global registry for REST routes
19
+ REGISTERED_URLS = {}
20
+ URLPATTERN_METHODS = {}
21
+ MOJO_API_MODULE = settings.get("MOJO_API_MODULE", "api")
22
+ MOJO_APPEND_SLASH = settings.get("MOJO_APPEND_SLASH", False)
23
+
24
+ API_METRICS = settings.get("API_METRICS", False)
25
+ API_METRICS_GRANULARITY = settings.get("API_METRICS_GRANULARITY", "days")
26
+
27
+
28
+ def dispatcher(request, *args, **kwargs):
29
+ """
30
+ Dispatches incoming requests to the appropriate registered URL method.
31
+ """
32
+ rest.ACTIVE_REQUEST = request
33
+ key = kwargs.pop('__mojo_rest_root_key__', None)
34
+ if "group" in request.DATA:
35
+ request.group = modules.get_model_instance("account", "Group", int(request.DATA.group))
36
+ method_key = f"{key}__{request.method}"
37
+ if method_key not in URLPATTERN_METHODS:
38
+ method_key = f"{key}__ALL"
39
+ if method_key in URLPATTERN_METHODS:
40
+ return dispatch_error_handler(URLPATTERN_METHODS[method_key])(request, *args, **kwargs)
41
+ return JsonResponse({"error": "Endpoint not found", "code": 404}, status=404)
42
+
43
+
44
+ def dispatch_error_handler(func):
45
+ """
46
+ Decorator to catch and handle errors.
47
+ It logs exceptions and returns appropriate HTTP responses.
48
+ """
49
+ @wraps(func)
50
+ def wrapper(request, *args, **kwargs):
51
+ try:
52
+ if API_METRICS:
53
+ metrics.record("api_calls", category="mojo_api", min_granularity=API_METRICS_GRANULARITY)
54
+ return func(request, *args, **kwargs)
55
+ except mojo.errors.MojoException as err:
56
+ if API_METRICS:
57
+ metrics.record("api_errors", category="mojo_api", min_granularity=API_METRICS_GRANULARITY)
58
+ return JsonResponse({"error": err.reason, "code": err.code}, status=err.status)
59
+ except ValueError as err:
60
+ if API_METRICS:
61
+ metrics.record("api_errors", category="mojo_api", min_granularity=API_METRICS_GRANULARITY)
62
+ logger.exception(f"ValueErrror: {str(err)}, Path: {request.path}, IP: {request.META.get('REMOTE_ADDR')}")
63
+ return JsonResponse({"error": str(err), "code": 555 }, status=500)
64
+ except Exception as err:
65
+ if API_METRICS:
66
+ metrics.record("api_errors", category="mojo_api", min_granularity=API_METRICS_GRANULARITY)
67
+ # logger.exception(f"Unhandled REST Exception: {request.path}")
68
+ logger.exception(f"Error: {str(err)}, Path: {request.path}, IP: {request.META.get('REMOTE_ADDR')}")
69
+ return JsonResponse({"error": str(err) }, status=500)
70
+
71
+ return wrapper
72
+
73
+
74
+ def _register_route(method="ALL"):
75
+ """
76
+ Decorator to automatically register a Django view for a specific HTTP method.
77
+ Supports defining a custom pattern inside the decorator.
78
+
79
+ :param method: The HTTP method (GET, POST, etc.).
80
+ """
81
+ def decorator(pattern=None, docs=None):
82
+ def wrapper(view_func):
83
+ module = jm.get_root_module(view_func)
84
+ if not module:
85
+ print("!!!!!!!")
86
+ print(sys._getframe(2).f_code.co_filename)
87
+ raise RuntimeError(f"Could not determine module for {view_func.__name__}")
88
+
89
+ # Ensure `urlpatterns` exists in the calling module
90
+ if not hasattr(module, 'urlpatterns'):
91
+ module.urlpatterns = []
92
+
93
+ # If no pattern is provided, use the function name as the pattern
94
+ if pattern is None:
95
+ pattern_used = f"{view_func.__name__}"
96
+ else:
97
+ pattern_used = pattern
98
+
99
+ if MOJO_APPEND_SLASH:
100
+ pattern_used = pattern if pattern_used.endswith("/") else f"{pattern_used}/"
101
+ # Register view in URL mapping
102
+ app_name = module.__name__.split(".")[-1]
103
+ # print(f"{module.__name__}.urlpatterns")
104
+ root_key = f"{app_name}__{pattern_used}"
105
+ key = f"{root_key}__{method}"
106
+ # print(f"{app_name} -> {pattern_used} -> {key}")
107
+ URLPATTERN_METHODS[key] = view_func
108
+
109
+ # Determine whether to use path() or re_path()
110
+ url_func = path if not (pattern_used.startswith("^") or pattern_used.endswith("$")) else re_path
111
+
112
+ # Add to `urlpatterns`
113
+ module.urlpatterns.append(url_func(
114
+ pattern_used, dispatcher,
115
+ kwargs={
116
+ "__mojo_rest_root_key__": root_key
117
+ }))
118
+ # Attach metadata
119
+ view_func.__app_module_name__ = module.__name__
120
+ view_func.__app_name__ = app_name
121
+ view_func.__url__ = (method, pattern_used)
122
+ view_func.__docs__ = docs or {}
123
+ return view_func
124
+ return wrapper
125
+ return decorator
126
+
127
+ # Public-facing URL decorators
128
+ URL = _register_route()
129
+ GET = _register_route("GET")
130
+ POST = _register_route("POST")
131
+ PUT = _register_route("PUT")
132
+ DELETE = _register_route("DELETE")
@@ -0,0 +1,14 @@
1
+ from functools import wraps
2
+ import mojo.errors
3
+
4
+ def requires_params(*required_params):
5
+ def decorator(func):
6
+ @wraps(func)
7
+ def wrapper(request, *args, **kwargs):
8
+ missing_params = [param for param in required_params if param not in request.DATA]
9
+ if missing_params:
10
+ str_params = ', '.join(missing_params)
11
+ raise mojo.errors.ValueException(f"missing required parameters: {str_params}")
12
+ return func(request, *args, **kwargs)
13
+ return wrapper
14
+ return decorator
mojo/errors.py ADDED
@@ -0,0 +1,88 @@
1
+ class MojoException(Exception):
2
+ """
3
+ Base exception class for Mojo-related errors.
4
+
5
+ Attributes:
6
+ reason (str): The reason for the exception.
7
+ code (int): The error code associated with the exception.
8
+ status (int, optional): The HTTP status code. Defaults to None.
9
+ """
10
+
11
+ def __init__(self, reason, code, status=500):
12
+ """
13
+ Initialize a MojoException instance.
14
+
15
+ Args:
16
+ reason (str): The reason for the exception.
17
+ code (int): The error code associated with the exception.
18
+ status (int, optional): The HTTP status code. Defaults to None.
19
+ """
20
+ super().__init__(reason)
21
+ self.reason = reason
22
+ self.code = code
23
+ self.status = status
24
+
25
+
26
+ class ValueException(MojoException):
27
+ """
28
+ Exception raised for REST API value errors.
29
+
30
+ Attributes:
31
+ reason (str): The reason for the exception. Defaults to 'REST API Error'.
32
+ code (int): The error code associated with the exception. Defaults to 500.
33
+ status (int, optional): The HTTP status code. Defaults to 500.
34
+ """
35
+
36
+ def __init__(self, reason='REST API Error', code=400, status=400):
37
+ """
38
+ Initialize a RestErrorException instance.
39
+
40
+ Args:
41
+ reason (str, optional): The reason for the exception. Defaults to 'REST API Error'.
42
+ code (int, optional): The error code associated with the exception. Defaults to 500.
43
+ status (int, optional): The HTTP status code. Defaults to 500.
44
+ """
45
+ super().__init__(reason, code, status)
46
+
47
+
48
+ class PermissionDeniedException(MojoException):
49
+ """
50
+ Exception raised for permission denied errors.
51
+
52
+ Attributes:
53
+ reason (str): The reason for the exception. Defaults to 'Permission Denied'.
54
+ code (int): The error code associated with the exception. Defaults to 403.
55
+ status (int, optional): The HTTP status code. Defaults to 403.
56
+ """
57
+
58
+ def __init__(self, reason='Permission Denied', code=403, status=403):
59
+ """
60
+ Initialize a PermissionDeniedException instance.
61
+
62
+ Args:
63
+ reason (str, optional): The reason for the exception. Defaults to 'Permission Denied'.
64
+ code (int, optional): The error code associated with the exception. Defaults to 403.
65
+ status (int, optional): The HTTP status code. Defaults to 403.
66
+ """
67
+ super().__init__(reason, code, status)
68
+
69
+ class RestErrorException(MojoException):
70
+ """
71
+ Exception raised for REST API errors.
72
+
73
+ Attributes:
74
+ reason (str): The reason for the exception. Defaults to 'REST API Error'.
75
+ code (int): The error code associated with the exception. Defaults to 500.
76
+ status (int, optional): The HTTP status code. Defaults to 500.
77
+ """
78
+
79
+ def __init__(self, reason='REST API Error', code=500, status=500):
80
+ """
81
+ Initialize a RestErrorException instance.
82
+
83
+ Args:
84
+ reason (str, optional): The reason for the exception. Defaults to 'REST API Error'.
85
+ code (int, optional): The error code associated with the exception. Defaults to 500.
86
+ status (int, optional): The HTTP status code. Defaults to 500.
87
+ """
88
+ super().__init__(reason, code, status)
File without changes
File without changes
@@ -0,0 +1,8 @@
1
+ import boto3
2
+
3
+ def get_session(access_key, secret_key, region):
4
+ return boto3.Session(
5
+ aws_access_key_id=access_key,
6
+ aws_secret_access_key=secret_key,
7
+ region_name=region,
8
+ )