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.
- django_nativemojo-0.1.10.dist-info/LICENSE +19 -0
- django_nativemojo-0.1.10.dist-info/METADATA +96 -0
- django_nativemojo-0.1.10.dist-info/NOTICE +8 -0
- django_nativemojo-0.1.10.dist-info/RECORD +194 -0
- django_nativemojo-0.1.10.dist-info/WHEEL +4 -0
- mojo/__init__.py +3 -0
- mojo/apps/account/__init__.py +1 -0
- mojo/apps/account/admin.py +91 -0
- mojo/apps/account/apps.py +16 -0
- mojo/apps/account/migrations/0001_initial.py +77 -0
- mojo/apps/account/migrations/0002_user_is_email_verified_user_is_phone_verified.py +23 -0
- mojo/apps/account/migrations/0003_group_mojo_secrets_user_mojo_secrets.py +23 -0
- mojo/apps/account/migrations/__init__.py +0 -0
- mojo/apps/account/models/__init__.py +3 -0
- mojo/apps/account/models/group.py +98 -0
- mojo/apps/account/models/member.py +95 -0
- mojo/apps/account/models/pkey.py +18 -0
- mojo/apps/account/models/user.py +211 -0
- mojo/apps/account/rest/__init__.py +3 -0
- mojo/apps/account/rest/group.py +25 -0
- mojo/apps/account/rest/user.py +47 -0
- mojo/apps/account/utils/__init__.py +0 -0
- mojo/apps/account/utils/jwtoken.py +72 -0
- mojo/apps/account/utils/passkeys.py +54 -0
- mojo/apps/fileman/README.md +549 -0
- mojo/apps/fileman/__init__.py +0 -0
- mojo/apps/fileman/apps.py +15 -0
- mojo/apps/fileman/backends/__init__.py +117 -0
- mojo/apps/fileman/backends/base.py +319 -0
- mojo/apps/fileman/backends/filesystem.py +397 -0
- mojo/apps/fileman/backends/s3.py +398 -0
- mojo/apps/fileman/examples/configurations.py +378 -0
- mojo/apps/fileman/examples/usage_example.py +665 -0
- mojo/apps/fileman/management/__init__.py +1 -0
- mojo/apps/fileman/management/commands/__init__.py +1 -0
- mojo/apps/fileman/management/commands/cleanup_expired_uploads.py +222 -0
- mojo/apps/fileman/models/__init__.py +7 -0
- mojo/apps/fileman/models/file.py +292 -0
- mojo/apps/fileman/models/manager.py +227 -0
- mojo/apps/fileman/models/render.py +0 -0
- mojo/apps/fileman/rest/__init__ +0 -0
- mojo/apps/fileman/rest/__init__.py +23 -0
- mojo/apps/fileman/rest/fileman.py +13 -0
- mojo/apps/fileman/rest/upload.py +92 -0
- mojo/apps/fileman/utils/__init__.py +19 -0
- mojo/apps/fileman/utils/upload.py +616 -0
- mojo/apps/incident/__init__.py +1 -0
- mojo/apps/incident/handlers/__init__.py +3 -0
- mojo/apps/incident/handlers/event_handlers.py +142 -0
- mojo/apps/incident/migrations/0001_initial.py +83 -0
- mojo/apps/incident/migrations/0002_rename_bundle_ruleset_bundle_minutes_event_hostname_and_more.py +44 -0
- mojo/apps/incident/migrations/0003_alter_event_model_id.py +18 -0
- mojo/apps/incident/migrations/0004_alter_incident_model_id.py +18 -0
- mojo/apps/incident/migrations/__init__.py +0 -0
- mojo/apps/incident/models/__init__.py +3 -0
- mojo/apps/incident/models/event.py +135 -0
- mojo/apps/incident/models/incident.py +33 -0
- mojo/apps/incident/models/rule.py +247 -0
- mojo/apps/incident/parsers/__init__.py +0 -0
- mojo/apps/incident/parsers/ossec/__init__.py +1 -0
- mojo/apps/incident/parsers/ossec/core.py +82 -0
- mojo/apps/incident/parsers/ossec/parsed.py +23 -0
- mojo/apps/incident/parsers/ossec/rules.py +124 -0
- mojo/apps/incident/parsers/ossec/utils.py +169 -0
- mojo/apps/incident/reporter.py +42 -0
- mojo/apps/incident/rest/__init__.py +2 -0
- mojo/apps/incident/rest/event.py +23 -0
- mojo/apps/incident/rest/ossec.py +22 -0
- mojo/apps/logit/__init__.py +0 -0
- mojo/apps/logit/admin.py +37 -0
- mojo/apps/logit/migrations/0001_initial.py +32 -0
- mojo/apps/logit/migrations/0002_log_duid_log_payload_log_username.py +28 -0
- mojo/apps/logit/migrations/0003_log_level.py +18 -0
- mojo/apps/logit/migrations/__init__.py +0 -0
- mojo/apps/logit/models/__init__.py +1 -0
- mojo/apps/logit/models/log.py +57 -0
- mojo/apps/logit/rest.py +9 -0
- mojo/apps/metrics/README.md +79 -0
- mojo/apps/metrics/__init__.py +12 -0
- mojo/apps/metrics/redis_metrics.py +331 -0
- mojo/apps/metrics/rest/__init__.py +1 -0
- mojo/apps/metrics/rest/base.py +152 -0
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/metrics/utils.py +227 -0
- mojo/apps/notify/README.md +91 -0
- mojo/apps/notify/README_NOTIFICATIONS.md +566 -0
- mojo/apps/notify/__init__.py +0 -0
- mojo/apps/notify/admin.py +52 -0
- mojo/apps/notify/handlers/__init__.py +0 -0
- mojo/apps/notify/handlers/example_handlers.py +516 -0
- mojo/apps/notify/handlers/ses/__init__.py +25 -0
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +25 -0
- mojo/apps/notify/handlers/ses/message.py +86 -0
- mojo/apps/notify/management/__init__.py +0 -0
- mojo/apps/notify/management/commands/__init__.py +1 -0
- mojo/apps/notify/management/commands/process_notifications.py +370 -0
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +12 -0
- mojo/apps/notify/models/account.py +128 -0
- mojo/apps/notify/models/attachment.py +24 -0
- mojo/apps/notify/models/bounce.py +68 -0
- mojo/apps/notify/models/complaint.py +40 -0
- mojo/apps/notify/models/inbox.py +113 -0
- mojo/apps/notify/models/inbox_message.py +173 -0
- mojo/apps/notify/models/outbox.py +129 -0
- mojo/apps/notify/models/outbox_message.py +288 -0
- mojo/apps/notify/models/template.py +30 -0
- mojo/apps/notify/providers/__init__.py +0 -0
- mojo/apps/notify/providers/aws.py +73 -0
- mojo/apps/notify/rest/__init__.py +0 -0
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +2 -0
- mojo/apps/notify/utils/notifications.py +404 -0
- mojo/apps/notify/utils/parsing.py +202 -0
- mojo/apps/notify/utils/render.py +144 -0
- mojo/apps/tasks/README.md +118 -0
- mojo/apps/tasks/__init__.py +11 -0
- mojo/apps/tasks/manager.py +489 -0
- mojo/apps/tasks/rest/__init__.py +2 -0
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +62 -0
- mojo/apps/tasks/runner.py +174 -0
- mojo/apps/tasks/tq_handlers.py +14 -0
- mojo/decorators/__init__.py +3 -0
- mojo/decorators/auth.py +25 -0
- mojo/decorators/cron.py +31 -0
- mojo/decorators/http.py +132 -0
- mojo/decorators/validate.py +14 -0
- mojo/errors.py +88 -0
- mojo/helpers/__init__.py +0 -0
- mojo/helpers/aws/__init__.py +0 -0
- mojo/helpers/aws/client.py +8 -0
- mojo/helpers/aws/s3.py +268 -0
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/cron.py +79 -0
- mojo/helpers/crypto/__init__.py +4 -0
- mojo/helpers/crypto/aes.py +60 -0
- mojo/helpers/crypto/hash.py +59 -0
- mojo/helpers/crypto/privpub/__init__.py +1 -0
- mojo/helpers/crypto/privpub/hybrid.py +97 -0
- mojo/helpers/crypto/privpub/rsa.py +104 -0
- mojo/helpers/crypto/sign.py +36 -0
- mojo/helpers/crypto/too.l.py +25 -0
- mojo/helpers/crypto/utils.py +26 -0
- mojo/helpers/daemon.py +94 -0
- mojo/helpers/dates.py +69 -0
- mojo/helpers/dns/__init__.py +0 -0
- mojo/helpers/dns/godaddy.py +62 -0
- mojo/helpers/filetypes.py +128 -0
- mojo/helpers/logit.py +310 -0
- mojo/helpers/modules.py +95 -0
- mojo/helpers/paths.py +63 -0
- mojo/helpers/redis.py +10 -0
- mojo/helpers/request.py +89 -0
- mojo/helpers/request_parser.py +269 -0
- mojo/helpers/response.py +14 -0
- mojo/helpers/settings.py +146 -0
- mojo/helpers/sysinfo.py +140 -0
- mojo/helpers/ua.py +0 -0
- mojo/middleware/__init__.py +0 -0
- mojo/middleware/auth.py +26 -0
- mojo/middleware/logging.py +55 -0
- mojo/middleware/mojo.py +21 -0
- mojo/migrations/0001_initial.py +32 -0
- mojo/migrations/__init__.py +0 -0
- mojo/models/__init__.py +2 -0
- mojo/models/meta.py +262 -0
- mojo/models/rest.py +538 -0
- mojo/models/secrets.py +59 -0
- mojo/rest/__init__.py +1 -0
- mojo/rest/info.py +26 -0
- mojo/serializers/__init__.py +0 -0
- mojo/serializers/models.py +165 -0
- mojo/serializers/openapi.py +188 -0
- mojo/urls.py +38 -0
- mojo/ws4redis/README.md +174 -0
- mojo/ws4redis/__init__.py +2 -0
- mojo/ws4redis/client.py +283 -0
- mojo/ws4redis/connection.py +327 -0
- mojo/ws4redis/exceptions.py +32 -0
- mojo/ws4redis/redis.py +183 -0
- mojo/ws4redis/servers/__init__.py +0 -0
- mojo/ws4redis/servers/base.py +86 -0
- mojo/ws4redis/servers/django.py +171 -0
- mojo/ws4redis/servers/uwsgi.py +63 -0
- mojo/ws4redis/settings.py +45 -0
- mojo/ws4redis/utf8validator.py +128 -0
- mojo/ws4redis/websocket.py +403 -0
- testit/__init__.py +0 -0
- testit/client.py +147 -0
- testit/faker.py +20 -0
- testit/helpers.py +198 -0
- testit/runner.py +262 -0
mojo/helpers/logit.py
ADDED
@@ -0,0 +1,310 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import logging
|
4
|
+
import threading
|
5
|
+
from decimal import Decimal
|
6
|
+
from collections import OrderedDict
|
7
|
+
from io import StringIO
|
8
|
+
from typing import Optional
|
9
|
+
import traceback
|
10
|
+
import re
|
11
|
+
# import traceback
|
12
|
+
# import time
|
13
|
+
# from datetime import datetime
|
14
|
+
# from binascii import hexlify
|
15
|
+
from . import paths
|
16
|
+
|
17
|
+
# Resolve paths
|
18
|
+
LOG_DIR = paths.LOG_ROOT
|
19
|
+
|
20
|
+
# Ensure log directory exists
|
21
|
+
os.makedirs(LOG_DIR, exist_ok=True)
|
22
|
+
|
23
|
+
# Constants
|
24
|
+
MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB
|
25
|
+
LOG_BACKUP_COUNT = 3
|
26
|
+
COLOR_LOGS = True
|
27
|
+
LOG_MANAGER = None
|
28
|
+
|
29
|
+
def get_logger(name, filename=None, debug=False):
|
30
|
+
global LOG_MANAGER
|
31
|
+
if LOG_MANAGER is None:
|
32
|
+
LOG_MANAGER = LogManager()
|
33
|
+
return LOG_MANAGER.get_logger(name, filename, debug)
|
34
|
+
|
35
|
+
|
36
|
+
def pretty_print(msg):
|
37
|
+
out = PrettyLogger.pretty_format(msg)
|
38
|
+
print(out)
|
39
|
+
|
40
|
+
pp = pretty_print
|
41
|
+
|
42
|
+
|
43
|
+
def pretty_format(msg):
|
44
|
+
return PrettyLogger.pretty_format(msg)
|
45
|
+
|
46
|
+
|
47
|
+
def color_print(msg, color, end="\n"):
|
48
|
+
ConsoleLogger.print_message(msg, color, end)
|
49
|
+
|
50
|
+
|
51
|
+
# Mask sensitive data in the log
|
52
|
+
def mask_sensitive_data(text):
|
53
|
+
sensitive_patterns = [
|
54
|
+
r'("?(password|pwd|secret|token|access_token|api_key|authorization)"?\s*[:=]\s*"?)[^",\s]+',
|
55
|
+
r'("?(ssn|credit_card|card_number|pin|cvv)"?\s*[:=]\s*"?)[^",\s]+',
|
56
|
+
]
|
57
|
+
for pattern in sensitive_patterns:
|
58
|
+
text = re.sub(pattern, r'\1*****', text, flags=re.IGNORECASE)
|
59
|
+
return text
|
60
|
+
|
61
|
+
# Utility: Thread-safe lock handler
|
62
|
+
class ThreadSafeLock:
|
63
|
+
def __init__(self):
|
64
|
+
self.lock = threading.RLock()
|
65
|
+
|
66
|
+
def __enter__(self):
|
67
|
+
self.lock.acquire()
|
68
|
+
|
69
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
70
|
+
self.lock.release()
|
71
|
+
|
72
|
+
|
73
|
+
# Log Manager to Handle Multiple Loggers
|
74
|
+
class LogManager:
|
75
|
+
_instance = None
|
76
|
+
|
77
|
+
def __new__(cls):
|
78
|
+
if cls._instance is None:
|
79
|
+
cls._instance = super(LogManager, cls).__new__(cls)
|
80
|
+
cls._instance.loggers = {}
|
81
|
+
cls._instance.streams = {}
|
82
|
+
cls._instance.master_logger = None
|
83
|
+
cls._instance.lock = ThreadSafeLock()
|
84
|
+
return cls._instance
|
85
|
+
|
86
|
+
def get_logger(self, name, filename=None, debug=False):
|
87
|
+
"""Retrieve or create a logger."""
|
88
|
+
with self.lock:
|
89
|
+
if name in self.loggers:
|
90
|
+
return self.loggers[name]
|
91
|
+
|
92
|
+
level = logging.DEBUG if debug else logging.INFO
|
93
|
+
logger = logging.getLogger(name)
|
94
|
+
logger.setLevel(level)
|
95
|
+
|
96
|
+
# Create file handler
|
97
|
+
if filename:
|
98
|
+
log_path = os.path.join(LOG_DIR, filename)
|
99
|
+
file_handler = logging.FileHandler(log_path)
|
100
|
+
file_handler.setFormatter(self._get_formatter())
|
101
|
+
logger.addHandler(file_handler)
|
102
|
+
|
103
|
+
# Capture to master logger if exists
|
104
|
+
if self.master_logger:
|
105
|
+
logger.addHandler(logging.StreamHandler(sys.stdout))
|
106
|
+
|
107
|
+
self.loggers[name] = Logger(name, filename, logger)
|
108
|
+
return self.loggers[name]
|
109
|
+
|
110
|
+
def set_master_logger(self, logger: logging.Logger):
|
111
|
+
"""Assign master logger for global logging."""
|
112
|
+
with self.lock:
|
113
|
+
self.master_logger = logger
|
114
|
+
|
115
|
+
def _get_formatter(self) -> logging.Formatter:
|
116
|
+
return logging.Formatter("%(asctime)s - %(levelname)s - %(name)s: %(message)s")
|
117
|
+
|
118
|
+
|
119
|
+
# Logger Wrapper
|
120
|
+
class Logger:
|
121
|
+
def __init__(self, name, filename, logger):
|
122
|
+
self.name = name
|
123
|
+
self.filename = filename
|
124
|
+
self.logger = logger
|
125
|
+
|
126
|
+
def _build_log(self, *args):
|
127
|
+
output = []
|
128
|
+
for arg in args:
|
129
|
+
if isinstance(arg, dict):
|
130
|
+
output.append("")
|
131
|
+
output.append(PrettyLogger.pretty_format(arg))
|
132
|
+
else:
|
133
|
+
output.append(str(arg))
|
134
|
+
return "\n".join(output)
|
135
|
+
|
136
|
+
def info(self, *args):
|
137
|
+
self.logger.info(self._build_log(*args))
|
138
|
+
|
139
|
+
def debug(self, *args):
|
140
|
+
self.logger.debug(self._build_log(*args))
|
141
|
+
|
142
|
+
def warning(self, *args):
|
143
|
+
self.logger.warning(self._build_log(*args))
|
144
|
+
|
145
|
+
def error(self, *args):
|
146
|
+
self.logger.error(self._build_log(*args))
|
147
|
+
|
148
|
+
def critical(self, *args):
|
149
|
+
self.logger.critical(self._build_log(*args))
|
150
|
+
|
151
|
+
def exception(self, *args):
|
152
|
+
exc_info = sys.exc_info()
|
153
|
+
err = None
|
154
|
+
if exc_info:
|
155
|
+
err = {
|
156
|
+
"type": str(exc_info[0]),
|
157
|
+
"message": str(exc_info[1]),
|
158
|
+
"stack_trace": traceback.format_exception(*exc_info)
|
159
|
+
}
|
160
|
+
pretty_trace = PrettyLogger.pretty_format(err)
|
161
|
+
args = (pretty_trace, *args)
|
162
|
+
self.logger.exception(self._build_log(*args))
|
163
|
+
return err
|
164
|
+
|
165
|
+
# Log Formatting with Colors
|
166
|
+
class ColorFormatter(logging.Formatter):
|
167
|
+
COLORS = {
|
168
|
+
"DEBUG": "\033[34m", # Blue
|
169
|
+
"INFO": "\033[32m", # Green
|
170
|
+
"WARNING": "\033[33m", # Yellow
|
171
|
+
"ERROR": "\033[31m", # Red
|
172
|
+
"CRITICAL": "\033[35m", # Pink
|
173
|
+
"BLUE": "\033[34m", # Blue
|
174
|
+
"GREEN": "\033[32m", # Green
|
175
|
+
"YELLOW": "\033[33m", # Yellow
|
176
|
+
"RED": "\033[31m", # Red
|
177
|
+
"PINK": "\033[35m", # Pink
|
178
|
+
}
|
179
|
+
RESET = "\033[0m"
|
180
|
+
|
181
|
+
def format(self, record):
|
182
|
+
log_color = self.COLORS.get(record.levelname, self.RESET)
|
183
|
+
return f"{log_color}{super().format(record)}{self.RESET}"
|
184
|
+
|
185
|
+
|
186
|
+
# Utility for Pretty Logging
|
187
|
+
class PrettyLogger:
|
188
|
+
@staticmethod
|
189
|
+
def pretty_format(data, max_length=500000) -> str:
|
190
|
+
"""Formats complex data structures for logging."""
|
191
|
+
output = StringIO()
|
192
|
+
PrettyLogger._recursive_format(data, output, 0, max_length)
|
193
|
+
return output.getvalue()
|
194
|
+
|
195
|
+
@staticmethod
|
196
|
+
def _recursive_format(data, output=sys.stdout, indent=0, max_length=80):
|
197
|
+
"""Recursive function to pretty-print dictionaries and lists with proper indentation and colors."""
|
198
|
+
|
199
|
+
base_indent = " " * indent # Current level indentation
|
200
|
+
next_indent = " " * (indent + 2) # Indentation for nested structures
|
201
|
+
|
202
|
+
if isinstance(data, dict):
|
203
|
+
data = OrderedDict(sorted(data.items())) # Ensure ordered keys
|
204
|
+
output.write("{\n") # Open dict at current indent
|
205
|
+
last_index = len(data) - 1
|
206
|
+
for i, (key, value) in enumerate(data.items()):
|
207
|
+
output.write(next_indent + f"\033[34m\"{key}\"\033[0m: ")
|
208
|
+
PrettyLogger._recursive_format(value, output, indent + 2, max_length)
|
209
|
+
if i != last_index:
|
210
|
+
output.write(",") # Add comma for all but last key-value pair
|
211
|
+
output.write("\n")
|
212
|
+
if indent == 0:
|
213
|
+
base_indent = ""
|
214
|
+
output.write(base_indent + "}") # Close dict at the correct indent
|
215
|
+
elif isinstance(data, list):
|
216
|
+
output.write("[\n") # Open list at correct indent
|
217
|
+
last_index = len(data) - 1
|
218
|
+
for i, item in enumerate(data):
|
219
|
+
output.write(next_indent)
|
220
|
+
PrettyLogger._recursive_format(item, output, indent + 2, max_length)
|
221
|
+
if i != last_index:
|
222
|
+
output.write(",") # Add comma for all but last item
|
223
|
+
output.write("\n")
|
224
|
+
output.write(base_indent + "]") # Close list at correct indent
|
225
|
+
elif isinstance(data, Decimal):
|
226
|
+
output.write(f"\033[32m{str(data)}\033[0m") # Green for Decimal
|
227
|
+
elif isinstance(data, str):
|
228
|
+
output.write(f"\033[31m\"{data}\"\033[0m") # Red for strings
|
229
|
+
else:
|
230
|
+
output.write(f"\033[33m{str(data)}\033[0m") # Yellow for other types
|
231
|
+
|
232
|
+
@staticmethod
|
233
|
+
def log_json(data, logger=None):
|
234
|
+
"""Logs data in JSON format."""
|
235
|
+
if logger is None:
|
236
|
+
logger = Logger("root")
|
237
|
+
formatted_data = PrettyLogger.pretty_format(data)
|
238
|
+
logger.info(formatted_data)
|
239
|
+
|
240
|
+
|
241
|
+
# Console Logger Utility
|
242
|
+
class ConsoleLogger:
|
243
|
+
BLACK = "\033[30m"
|
244
|
+
RED = "\033[31m"
|
245
|
+
GREEN = "\033[32m"
|
246
|
+
YELLOW = "\033[33m"
|
247
|
+
PINK = "\033[35m"
|
248
|
+
BLUE = "\033[34m"
|
249
|
+
WHITE = "\033[37m"
|
250
|
+
|
251
|
+
HBLACK = "\033[90m"
|
252
|
+
HRED = "\033[91m"
|
253
|
+
HGREEN = "\033[92m"
|
254
|
+
HYELLOW = "\033[93m"
|
255
|
+
HBLUE = "\033[94m"
|
256
|
+
HPINK = "\033[95m"
|
257
|
+
HWHITE = "\033[97m"
|
258
|
+
|
259
|
+
HEADER = "\033[95m"
|
260
|
+
FAIL = "\033[91m"
|
261
|
+
OFF = "\033[0m"
|
262
|
+
BOLD = "\033[1m"
|
263
|
+
UNDERLINE = "\033[4m"
|
264
|
+
|
265
|
+
@staticmethod
|
266
|
+
def print_message(msg, color_code="\033[32m", end="\n"):
|
267
|
+
"""Prints a color-coded message to the console."""
|
268
|
+
sys.stdout.write(f"{color_code}{msg}\033[0m{end}")
|
269
|
+
sys.stdout.flush()
|
270
|
+
|
271
|
+
|
272
|
+
# Rotating File Handler
|
273
|
+
class RotatingLogger:
|
274
|
+
def __init__(self, log_file="app.log", max_bytes=MAX_LOG_SIZE, backup_count=LOG_BACKUP_COUNT):
|
275
|
+
self.logger = logging.getLogger("RotatingLogger")
|
276
|
+
self.logger.setLevel(logging.INFO)
|
277
|
+
|
278
|
+
handler = logging.handlers.RotatingFileHandler(
|
279
|
+
os.path.join(LOG_DIR, log_file),
|
280
|
+
maxBytes=max_bytes,
|
281
|
+
backupCount=backup_count,
|
282
|
+
)
|
283
|
+
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
284
|
+
self.logger.addHandler(handler)
|
285
|
+
|
286
|
+
def log(self, message, level=logging.INFO):
|
287
|
+
self.logger.log(level, message)
|
288
|
+
|
289
|
+
|
290
|
+
# Usage Example
|
291
|
+
if __name__ == "__main__":
|
292
|
+
print(BASE_DIR)
|
293
|
+
log = Logger("AppLogger", "app.log", debug=True)
|
294
|
+
log.info("🚀 Application started successfully!")
|
295
|
+
log.debug("🔍 Debugging mode enabled")
|
296
|
+
log.warning("⚠️ Warning: Low disk space")
|
297
|
+
log.error("❌ An error occurred while processing request")
|
298
|
+
log.critical("🔥 Critical system failure!")
|
299
|
+
|
300
|
+
# Pretty print a dictionary
|
301
|
+
sample_data = {
|
302
|
+
"user": "John Doe",
|
303
|
+
"email": "john.doe@example.com",
|
304
|
+
"permissions": ["read", "write"],
|
305
|
+
"settings": {"theme": "dark", "notifications": True},
|
306
|
+
}
|
307
|
+
PrettyLogger.log_json(sample_data, log)
|
308
|
+
|
309
|
+
# Console logger
|
310
|
+
ConsoleLogger.print_message("✔ Task completed successfully", "\033[32m")
|
mojo/helpers/modules.py
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
import importlib
|
2
|
+
import pkgutil
|
3
|
+
import sys
|
4
|
+
|
5
|
+
|
6
|
+
def module_exists(module_name):
|
7
|
+
return pkgutil.find_loader(module_name)
|
8
|
+
|
9
|
+
|
10
|
+
def load_module_to_globals(module, memory=None):
|
11
|
+
"""
|
12
|
+
Injects all uppercase variables from the given module into the specified namespace.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
module: The module whose variables should be injected.
|
16
|
+
memory (dict, optional): The namespace where variables will be stored.
|
17
|
+
Defaults to `globals()`.
|
18
|
+
"""
|
19
|
+
if memory is None:
|
20
|
+
memory = globals()
|
21
|
+
|
22
|
+
if isinstance(module, str):
|
23
|
+
module = load_module(module, module)
|
24
|
+
|
25
|
+
# Extract only variables that are ALL CAPS (Django convention for settings)
|
26
|
+
memory.update({key: value for key, value in vars(module).items() if key.isupper()})
|
27
|
+
|
28
|
+
|
29
|
+
def load_module(module_name, package=__name__, ignore_errors=True):
|
30
|
+
"""
|
31
|
+
Import a module by name and inject its variables into the global namespace.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
module_name (str): The name of the module to be imported.
|
35
|
+
package (str, optional): The package name to use for relative imports. Defaults to the current package.
|
36
|
+
|
37
|
+
Raises:
|
38
|
+
ImportError: If the module cannot be found in the specified package.
|
39
|
+
"""
|
40
|
+
if ignore_errors:
|
41
|
+
try:
|
42
|
+
module = importlib.import_module(module_name, package=package)
|
43
|
+
return module
|
44
|
+
except ModuleNotFoundError:
|
45
|
+
pass
|
46
|
+
return None
|
47
|
+
return importlib.import_module(module_name, package=package)
|
48
|
+
|
49
|
+
def get_root_module(func, app_root=True):
|
50
|
+
"""
|
51
|
+
Get the root (top-level) module of a function.
|
52
|
+
|
53
|
+
:param func: The function to inspect.
|
54
|
+
:return: The root module name (str) or None if not found.
|
55
|
+
"""
|
56
|
+
if not hasattr(func, "__module__"):
|
57
|
+
return None # Not a valid function or method
|
58
|
+
|
59
|
+
# Fully unwrap to get the original function
|
60
|
+
while hasattr(func, "__wrapped__"):
|
61
|
+
func = func.__wrapped__
|
62
|
+
|
63
|
+
module_name = func.__module__ # Get the module where the function is defined
|
64
|
+
|
65
|
+
if module_name not in sys.modules:
|
66
|
+
return None # The module is not loaded
|
67
|
+
|
68
|
+
parts = module_name.split('.')
|
69
|
+
|
70
|
+
if app_root:
|
71
|
+
# Try to find a module that contains 'models' submodule
|
72
|
+
for i in range(len(parts)-1, -1, -1):
|
73
|
+
potential_root = '.'.join(parts[:i+1])
|
74
|
+
try:
|
75
|
+
if module_exists(f"{potential_root}.models") or module_exists(f"{potential_root}.rest"):
|
76
|
+
return sys.modules.get(potential_root)
|
77
|
+
except ImportError:
|
78
|
+
continue
|
79
|
+
|
80
|
+
# If no models module found or app_root is False, return top-level module
|
81
|
+
root_module = parts[0]
|
82
|
+
return sys.modules.get(root_module, None)
|
83
|
+
|
84
|
+
|
85
|
+
def get_model(app_name, model_name):
|
86
|
+
from django.apps import apps
|
87
|
+
return apps.get_model(app_name, model_name)
|
88
|
+
# # Import the module containing the models
|
89
|
+
# models_module = importlib.import_module(f"{app_name}.models")
|
90
|
+
# # Get the model class from the module
|
91
|
+
# model = getattr(models_module, model_name)
|
92
|
+
# return model
|
93
|
+
|
94
|
+
def get_model_instance(app_name, model_name, pk):
|
95
|
+
return get_model(app_name, model_name).objects.filter(id=pk).last()
|
mojo/helpers/paths.py
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
import sys
|
2
|
+
import socket
|
3
|
+
from pathlib import Path
|
4
|
+
|
5
|
+
|
6
|
+
def configure_paths(base_dir, depth=2):
|
7
|
+
"""
|
8
|
+
Dynamically configure paths based on the provided BASE_DIR.
|
9
|
+
Injects computed paths into the global namespace.
|
10
|
+
"""
|
11
|
+
global HOSTNAME, PROJECT_ROOT, VAR_ROOT, BIN_ROOT, LOG_ROOT, CONFIG_ROOT
|
12
|
+
global MEDIA_ROOT, MEDIA_URL, STATIC_ROOT, SITE_STATIC_ROOT, STATIC_DATA_ROOT
|
13
|
+
global STATIC_URL, STATIC_URL_SECURE, APPS_ROOT, APPS_CONFIG_FILE, STATICFILES_DIRS
|
14
|
+
|
15
|
+
# System Info
|
16
|
+
HOSTNAME = socket.gethostname()
|
17
|
+
|
18
|
+
# Define Paths
|
19
|
+
base_path = Path(base_dir).resolve().parents[depth]
|
20
|
+
PROJECT_ROOT = base_path.parent
|
21
|
+
VAR_ROOT = PROJECT_ROOT / "var"
|
22
|
+
BIN_ROOT = PROJECT_ROOT / "bin"
|
23
|
+
APPS_ROOT = PROJECT_ROOT / "apps"
|
24
|
+
CONFIG_ROOT = PROJECT_ROOT / "config"
|
25
|
+
APPS_CONFIG_FILE = APPS_ROOT / "apps.json"
|
26
|
+
LOG_ROOT = VAR_ROOT / "logs"
|
27
|
+
|
28
|
+
# Media Settings
|
29
|
+
MEDIA_ROOT = base_path / "media"
|
30
|
+
MEDIA_URL = "/media/"
|
31
|
+
|
32
|
+
# Static Files Settings
|
33
|
+
STATIC_ROOT = base_path / "static"
|
34
|
+
SITE_STATIC_ROOT = base_path / "site_static"
|
35
|
+
STATIC_DATA_ROOT = SITE_STATIC_ROOT / "json"
|
36
|
+
STATIC_URL = "/static/"
|
37
|
+
STATIC_URL_SECURE = "/static/"
|
38
|
+
STATICFILES_DIRS = [SITE_STATIC_ROOT]
|
39
|
+
|
40
|
+
MEDIA_URL = '/media/'
|
41
|
+
MEDIA_ROOT = base_path / "media"
|
42
|
+
|
43
|
+
|
44
|
+
# Load additional site-packages paths from .site_packages file
|
45
|
+
site_packages_file = PROJECT_ROOT / ".site_packages"
|
46
|
+
|
47
|
+
if site_packages_file.exists():
|
48
|
+
with site_packages_file.open("r") as f:
|
49
|
+
site_packages_paths = [path.strip() for path in f.readlines() if path.strip()]
|
50
|
+
|
51
|
+
for path in site_packages_paths:
|
52
|
+
site_path = Path(path)
|
53
|
+
if site_path.exists() and str(site_path) not in sys.path:
|
54
|
+
sys.path.insert(0, str(site_path))
|
55
|
+
|
56
|
+
# To use this, call configure_paths(BASE_DIR) from your Django settings
|
57
|
+
|
58
|
+
|
59
|
+
def configure_apps():
|
60
|
+
from objict import objict
|
61
|
+
global INSTALLED_APPS, APPS_CONFIG
|
62
|
+
APPS_CONFIG = objict.fromFile(APPS_CONFIG_FILE)
|
63
|
+
INSTALLED_APPS = APPS_CONFIG.installed
|
mojo/helpers/redis.py
ADDED
@@ -0,0 +1,10 @@
|
|
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/helpers/request.py
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
from objict import objict, nobjict
|
2
|
+
from .request_parser import RequestDataParser
|
3
|
+
|
4
|
+
REQUEST_PARSER = RequestDataParser()
|
5
|
+
|
6
|
+
def parse_request_data(request):
|
7
|
+
"""
|
8
|
+
Consolidates all GET, POST, JSON body, and FILE data into one objict dict.
|
9
|
+
Handles dotted keys and repeated fields.
|
10
|
+
"""
|
11
|
+
return REQUEST_PARSER.parse(request)
|
12
|
+
|
13
|
+
|
14
|
+
# Additional helper function for debugging
|
15
|
+
def debug_request_data(request):
|
16
|
+
"""
|
17
|
+
Debug version that shows step-by-step processing
|
18
|
+
"""
|
19
|
+
print("=== DEBUG REQUEST PARSING ===")
|
20
|
+
print(f"Method: {request.method}")
|
21
|
+
print(f"Content-Type: {getattr(request, 'content_type', 'Not set')}")
|
22
|
+
print(f"GET: {dict(request.GET)}")
|
23
|
+
print(f"POST: {dict(request.POST)}")
|
24
|
+
print(f"FILES: {list(request.FILES.keys())}")
|
25
|
+
|
26
|
+
result = parse_request_data(request)
|
27
|
+
print(f"Final result: {result}")
|
28
|
+
return result
|
29
|
+
|
30
|
+
|
31
|
+
def get_referer(request):
|
32
|
+
return request.META.get('HTTP_REFERER')
|
33
|
+
|
34
|
+
|
35
|
+
def get_remote_ip(request):
|
36
|
+
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
37
|
+
if x_forwarded_for:
|
38
|
+
ip = x_forwarded_for.split(',')[0]
|
39
|
+
else:
|
40
|
+
ip = request.META.get('REMOTE_ADDR')
|
41
|
+
return ip
|
42
|
+
|
43
|
+
|
44
|
+
def get_device_id(request):
|
45
|
+
# Look for 'buid' or 'duid' in GET parameters
|
46
|
+
for key in ['__buid__', 'duid', "buid"]:
|
47
|
+
if key in request.GET:
|
48
|
+
return request.GET[key]
|
49
|
+
|
50
|
+
# Look for 'buid' or 'duid' in POST parameters
|
51
|
+
for key in ['buid', 'duid']:
|
52
|
+
if key in request.POST:
|
53
|
+
return request.POST[key]
|
54
|
+
|
55
|
+
return None
|
56
|
+
|
57
|
+
def get_user_agent(request):
|
58
|
+
return request.META.get("HTTP_USER_AGENT", "")
|
59
|
+
|
60
|
+
|
61
|
+
def parse_user_agent(text):
|
62
|
+
"""
|
63
|
+
returns:
|
64
|
+
{
|
65
|
+
'user_agent': {
|
66
|
+
'family': 'Mobile Safari',
|
67
|
+
'major': '13',
|
68
|
+
'minor': '5',
|
69
|
+
'patch': None
|
70
|
+
},
|
71
|
+
'os': {
|
72
|
+
'family': 'iOS',
|
73
|
+
'major': '13',
|
74
|
+
'minor': '5',
|
75
|
+
'patch': None,
|
76
|
+
'patch_minor': None
|
77
|
+
},
|
78
|
+
'device': {
|
79
|
+
'family': 'iPhone',
|
80
|
+
'brand': None,
|
81
|
+
'model': None
|
82
|
+
},
|
83
|
+
'string': '...original UA string...'
|
84
|
+
}
|
85
|
+
"""
|
86
|
+
if not isinstance(text, str):
|
87
|
+
text = get_user_agent(text)
|
88
|
+
from ua_parser import user_agent_parser
|
89
|
+
return objict.from_dict(user_agent_parser.Parse(text))
|