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,2 @@
1
+ from .event import *
2
+ from .ossec import *
@@ -0,0 +1,23 @@
1
+ from mojo import decorators as md
2
+ from mojo.apps.incident.models import Incident, Event, RuleSet, Rule
3
+
4
+
5
+ @md.URL('incident')
6
+ @md.URL('incident/<int:pk>')
7
+ def on_incident(request, pk=None):
8
+ return Incident.on_rest_request(request, pk)
9
+
10
+ @md.URL('event')
11
+ @md.URL('event/<int:pk>')
12
+ def on_event(request, pk=None):
13
+ return Event.on_rest_request(request, pk)
14
+
15
+ @md.URL('event/ruleset')
16
+ @md.URL('event/ruleset/<int:pk>')
17
+ def on_event_ruleset(request, pk=None):
18
+ return RuleSet.on_rest_request(request, pk)
19
+
20
+ @md.URL('event/ruleset/rule')
21
+ @md.URL('event/ruleset/rule/<int:pk>')
22
+ def on_event_ruleset_rule(request, pk=None):
23
+ return Rule.on_rest_request(request, pk)
@@ -0,0 +1,22 @@
1
+ from mojo import decorators as md
2
+ from mojo.apps.incident.parsers import ossec
3
+ from mojo import JsonResponse
4
+ from mojo.apps.incident import reporter
5
+
6
+
7
+ @md.POST('ossec/alert')
8
+ def on_ossec_alert(request):
9
+ ossec_alert = ossec.parse(request.DATA)
10
+ # add the request ip
11
+ ossec_alert["request_ip"] = request.ip
12
+ reporter.report_event(ossec_alert.text, category="ossec", **ossec_alert)
13
+ return JsonResponse({"status": True})
14
+
15
+
16
+ @md.POST('ossec/alert/batch')
17
+ def on_ossec_alert_batch(request):
18
+ ossec_alerts = ossec.parse(request.DATA)
19
+ for alert in ossec_alerts:
20
+ alert["request_ip"] = request.ip
21
+ reporter.report_event(alert.text, category="ossec", **alert)
22
+ return JsonResponse({"status": True})
File without changes
@@ -0,0 +1,37 @@
1
+ from mojo.apps.logit.models import Log
2
+ import json
3
+ from django.utils.safestring import mark_safe
4
+
5
+ from django.contrib import admin
6
+
7
+ # from django.contrib.sites.models import Site
8
+
9
+ # Unregister Group and Site from admin
10
+ # admin.site.unregister(Group)
11
+ # admin.site.unregister(Site)
12
+
13
+ @admin.register(Log)
14
+ class LogAdmin(admin.ModelAdmin):
15
+ exclude = ("log",)
16
+ list_display = ("created", "kind", "method", "path", "ip", "uid", "model_name", "model_id", "log_summary")
17
+ list_filter = ("kind", "ip", "uid", "model_name", "model_id", "created")
18
+ search_fields = ("log", "path", "ip", "model_name")
19
+ readonly_fields = ("created", "kind", "path", "ip", "uid", "model_name", "model_id", "user_agent", "method")
20
+ date_hierarchy = "created"
21
+ ordering = ("-created",)
22
+
23
+ def formatted_log(self, obj):
24
+ """Attempt to format the log as JSON if possible, otherwise return raw log."""
25
+ if obj.log:
26
+ try:
27
+ json_data = json.loads(obj.log) # Try parsing as JSON
28
+ formatted_json = json.dumps(json_data, indent=4, sort_keys=True)
29
+ return mark_safe(f"<pre>{formatted_json}</pre>") # Wrap in <pre> for better formatting
30
+ except json.JSONDecodeError:
31
+ return obj.log # Return raw log if not JSON
32
+ return None
33
+
34
+ def log_summary(self, obj):
35
+ """Show a shortened version of the log for better readability."""
36
+ return obj.log[:75] + "..." if obj.log and len(obj.log) > 75 else obj.log
37
+ log_summary.short_description = "Log Preview"
@@ -0,0 +1,32 @@
1
+ # Generated by Django 4.2.21 on 2025-05-29 23:34
2
+
3
+ from django.db import migrations, models
4
+ import mojo.models.rest
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ initial = True
10
+
11
+ dependencies = [
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name='Log',
17
+ fields=[
18
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19
+ ('created', models.DateTimeField(auto_now_add=True, db_index=True)),
20
+ ('kind', models.CharField(db_index=True, default=None, max_length=200, null=True)),
21
+ ('method', models.CharField(default=None, max_length=200, null=True)),
22
+ ('path', models.TextField(db_index=True, default=None, null=True)),
23
+ ('ip', models.CharField(db_index=True, default=None, max_length=32, null=True)),
24
+ ('uid', models.IntegerField(db_index=True, default=0)),
25
+ ('user_agent', models.TextField(default=None, null=True)),
26
+ ('log', models.TextField(default=None, null=True)),
27
+ ('model_name', models.TextField(db_index=True, default=None, null=True)),
28
+ ('model_id', models.IntegerField(db_index=True, default=0)),
29
+ ],
30
+ bases=(models.Model, mojo.models.rest.MojoModel),
31
+ ),
32
+ ]
@@ -0,0 +1,28 @@
1
+ # Generated by Django 4.2.21 on 2025-05-31 23:05
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('logit', '0001_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='log',
15
+ name='duid',
16
+ field=models.TextField(default=None, null=True),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='log',
20
+ name='payload',
21
+ field=models.TextField(default=None, null=True),
22
+ ),
23
+ migrations.AddField(
24
+ model_name='log',
25
+ name='username',
26
+ field=models.TextField(default=None, null=True),
27
+ ),
28
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 4.2.21 on 2025-05-31 23:48
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('logit', '0002_log_duid_log_payload_log_username'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='log',
15
+ name='level',
16
+ field=models.CharField(db_index=True, default='low', max_length=12),
17
+ ),
18
+ ]
File without changes
@@ -0,0 +1 @@
1
+ from .log import *
@@ -0,0 +1,57 @@
1
+ from mojo.models import MojoModel
2
+ from django.db import models as dm
3
+ from mojo.helpers import logit
4
+ # logger = logit.get_logger("requests", "requests.log")
5
+
6
+
7
+ class Log(dm.Model, MojoModel):
8
+ created = dm.DateTimeField(auto_now_add=True, db_index=True)
9
+ level = dm.CharField(max_length=12, default="info", db_index=True)
10
+ kind = dm.CharField(max_length=200, default=None, null=True, db_index=True)
11
+ method = dm.CharField(max_length=200, default=None, null=True)
12
+ path = dm.TextField(default=None, null=True, db_index=True)
13
+ payload = dm.TextField(default=None, null=True)
14
+ ip = dm.CharField(max_length=32, default=None, null=True, db_index=True)
15
+ duid = dm.TextField(default=None, null=True)
16
+ uid = dm.IntegerField(default=0, db_index=True)
17
+ username = dm.TextField(default=None, null=True)
18
+ user_agent = dm.TextField(default=None, null=True)
19
+ log = dm.TextField(default=None, null=True)
20
+ model_name = dm.TextField(default=None, null=True, db_index=True)
21
+ model_id = dm.IntegerField(default=0, db_index=True)
22
+ # expires = dm.DateTimeField(db_index=True)
23
+
24
+ @classmethod
25
+ def logit(cls, request, log, kind="log", model_name=None, model_id=0, level="info", **kwargs):
26
+ if not isinstance(log, (bytes, str)):
27
+ log = f"INVALID LOG TYPE: attempting to log type: {type(log)}"
28
+ log = log.decode("utf-8") if isinstance(log, bytes) else log
29
+ log = logit.mask_sensitive_data(log)
30
+
31
+ uid, username, ip_address, path, method, duid = 0, None, None, None, None, None
32
+ if request:
33
+ username = request.user.username if request.user.is_authenticated else None
34
+ uid = request.user.pk if request.user.is_authenticated else 0
35
+ path = request.path
36
+ duid = request.duid
37
+ ip_address = request.ip
38
+ method = request.method
39
+
40
+ path = kwargs.get("path", path)
41
+ method = kwargs.get("method", method)
42
+ duid = kwargs.get("duid", duid)
43
+
44
+ return cls.objects.create(
45
+ level=level,
46
+ kind=kind,
47
+ method=method,
48
+ path=path,
49
+ ip=ip_address,
50
+ uid=uid,
51
+ duid=duid,
52
+ username=username,
53
+ log=log,
54
+ user_agent=request.user_agent,
55
+ model_name=model_name,
56
+ model_id=model_id
57
+ )
@@ -0,0 +1,9 @@
1
+ from mojo import decorators as md
2
+ from mojo.apps.logit.models import Log
3
+
4
+ APP_NAME = ""
5
+
6
+ @md.URL('logs')
7
+ @md.URL('logs/<int:pk>')
8
+ def on_logs(request, pk=None):
9
+ return Log.on_rest_request(request, pk)
@@ -0,0 +1,79 @@
1
+ # Redis Metrics
2
+
3
+ The `redis_metrics` module is designed to record and retrieve metrics using Redis as the data store. This package helps in maintaining metrics at various time granularities (e.g., hours, days, months, etc.) and organizing them into categories for better management.
4
+
5
+ Ensure that Redis is running, and your Django project is configured to connect to it.
6
+
7
+ ## Usage
8
+
9
+ ### Recording Metrics
10
+
11
+ The `record_metrics` function is used to record events or occurrences by incrementing counters in Redis.
12
+
13
+ #### Parameters
14
+
15
+ - **slug (str)**: The base identifier for the metric.
16
+ - **when (datetime)**: The timestamp when the event occurred.
17
+ - **count (int, optional)**: The number by which to increment the metric. Default is 0.
18
+ - **group (optional)**: Reserved for future use.
19
+ - **category (optional)**: Used to group slugs into categories.
20
+ - **min_granulariy (str, optional)**: The smallest time unit for metric tracking (e.g., "hours"). Default is "hours".
21
+ - **max_granularity (str, optional)**: The largest time unit for metric tracking (e.g., "years"). Default is "years".
22
+ - **args (*args)**: Additional arguments used in slug generation.
23
+
24
+ #### Example
25
+
26
+ ```python
27
+ from datetime import datetime
28
+ from metrics.redis_metrics import record_metrics
29
+
30
+ # Record a metric 'page_views' for current time, incremented by 1.
31
+ record_metrics(slug="page_views", when=datetime.now(), count=1)
32
+
33
+ # Record a metric 'user_signups' with category 'activity', incremented by 5 at specific time.
34
+ record_metrics(slug="user_signups", when=datetime(2023,10,10), count=5, category="activity")
35
+
36
+ # Record metrics with different granularities
37
+ record_metrics(slug="app_usage", when=datetime.now(), count=10, min_granulariy="minutes", max_granularity="days")
38
+ ```
39
+
40
+ ### Retrieving Metrics
41
+
42
+ Retrieve the recorded metrics using a variety of retrieval methods provided by the module.
43
+
44
+ #### Get Metrics from Slug
45
+
46
+ Fetch the metrics for a specific slug between two datetime ranges at a given granularity.
47
+
48
+ #### Example
49
+
50
+ ```python
51
+ from datetime import datetime
52
+ from metrics.redis_metrics import get_metrics
53
+
54
+ # Fetch 'page_views' metrics from October 1, 2023, to October 10, 2023, with hourly granularity.
55
+ metrics = get_metrics(slug="page_views", dt_start=datetime(2023, 10, 1), dt_end=datetime(2023, 10, 10), granularity="hours")
56
+ print(metrics)
57
+ ```
58
+
59
+ ### Categories
60
+
61
+ You can organize your metrics into categories for more structured management.
62
+
63
+ #### Example
64
+
65
+ ```python
66
+ from metrics.redis_metrics import get_category_slugs, get_categories
67
+
68
+ # Get all the slugs in the 'activity' category.
69
+ activity_slugs = get_category_slugs(category="activity")
70
+ print(activity_slugs)
71
+
72
+ # Get all categories.
73
+ categories = get_categories()
74
+ print(categories)
75
+ ```
76
+
77
+ ## Conclusion
78
+
79
+ Redis Metrics is a robust utility for tracking application-specific metrics over various time periods with ease. The flexibility provided through time granularity and categorization makes it ideal for applications requiring detailed metric tracking and analysis.
@@ -0,0 +1,12 @@
1
+ from .redis_metrics import (
2
+ record,
3
+ fetch,
4
+ get_categories,
5
+ fetch_by_category,
6
+ get_category_slugs,
7
+ delete_category,
8
+ get_view_perms,
9
+ get_write_perms,
10
+ set_view_perms,
11
+ set_write_perms
12
+ )
@@ -0,0 +1,331 @@
1
+ from . import utils
2
+ from mojo.helpers import redis, dates
3
+ from mojo.helpers.settings import settings
4
+ import datetime
5
+ from objict import objict, nobjict
6
+
7
+
8
+ def record(slug, when=None, count=1, category=None, account="global",
9
+ min_granularity="hours", max_granularity="years", timezone=None):
10
+ """
11
+ Records metrics in Redis by incrementing counters for various time granularities.
12
+
13
+ Args:
14
+ slug (str): The base identifier for the metric.
15
+ when (datetime, optional): The time at which the event occurred. Defaults to current time if not provided.
16
+ count (int, optional): The count to increment the metric by. Defaults to 1.
17
+ category (str, optional): The category to which the metric belongs. Useful for grouping similar metrics.
18
+ account (str, optional): The account under which the metric is recorded. Defaults to "global".
19
+ min_granularity (str, optional): The minimum time granularity (e.g., "hours"). Defaults to "hours".
20
+ max_granularity (str, optional): The maximum time granularity (e.g., "years"). Defaults to "years".
21
+ timezone (str, optional): The timezone to use as the base for the granularity calculations.
22
+ *kwargs: Additional arguments to be used in slug generation.
23
+
24
+ Returns:
25
+ None: This function doesn't return a value. It performs its operations on Redis directly.
26
+ """
27
+ when = utils.normalize_datetime(when, timezone)
28
+ # Get Redis connection
29
+ redis_conn = redis.get_connection()
30
+ pipeline = redis_conn.pipeline()
31
+ if category is not None:
32
+ add_category_slug(category, slug, pipeline, account)
33
+ add_metrics_slug(slug, pipeline, account)
34
+ # Generate granularities
35
+ granularities = utils.generate_granularities(min_granularity, max_granularity)
36
+ # Process each granularity
37
+ for granularity in granularities:
38
+ # Generate slug for the current granularity
39
+ generated_slug = utils.generate_slug(slug, when, granularity, account)
40
+ # Add count to the slug in Redis
41
+ pipeline.incr(generated_slug, count)
42
+ exp_at = utils.get_expires_at(granularity, slug, category)
43
+ if exp_at:
44
+ pipeline.expireat(generated_slug, exp_at)
45
+ pipeline.execute()
46
+
47
+
48
+ def fetch(slug, dt_start=None, dt_end=None, granularity="hours",
49
+ redis_con=None, account="global", with_labels=False, dr_slugs=None):
50
+ """
51
+ Fetches metrics from Redis based on slugs within a specified date range and granularity.
52
+
53
+ Args:
54
+ slug (str or list): The slug(s) identifying metrics to fetch.
55
+ dt_start (datetime, optional): The start of the date range. Defaults to None.
56
+ dt_end (datetime, optional): The end of the date range. Defaults to None.
57
+ granularity (str, optional): The time granularity to use. Defaults to "hours".
58
+ redis_con: The Redis connection instance (optional).
59
+ account (str, optional): The account under which the metric is recorded. Defaults to "global".
60
+ with_labels (bool, optional): If True, includes timestamp labels in response data. Defaults to False.
61
+ dr_slugs: Pre-generated slugs for the date range. Defaults to None.
62
+
63
+ Returns:
64
+ list or nobjict: Returns a list of values or a structured nobjict with periods and data if `with_labels` is True.
65
+ """
66
+ if redis_con is None:
67
+ redis_con = redis.get_connection()
68
+ if isinstance(slug, (list, set)):
69
+ resp = nobjict()
70
+ if with_labels:
71
+ resp.data = {}
72
+ resp.labels = utils.periods_from_dr_slugs(utils.generate_slugs_for_range(
73
+ slug[0], dt_start, dt_end, granularity, account))
74
+ for s in slug:
75
+ values = fetch(s, dt_start, dt_end, granularity, redis_con, account)
76
+ if with_labels:
77
+ resp.data[s] = values
78
+ else:
79
+ resp[s] = values
80
+ return resp
81
+ dr_slugs = utils.generate_slugs_for_range(slug, dt_start, dt_end, granularity, account)
82
+ values = [int(met) if met is not None else 0 for met in redis_con.mget(dr_slugs)]
83
+ if not with_labels:
84
+ return values
85
+ return nobjict(labels=utils.periods_from_dr_slugs(dr_slugs), data={slug: values})
86
+
87
+ def add_metrics_slug(slug, redis_con=None, account="global"):
88
+ """
89
+ Adds a metric slug to a Redis set for the specified account.
90
+
91
+ Args:
92
+ slug (str): The slug to add.
93
+ redis_con: The Redis connection instance (optional).
94
+ account (str): The account to which the slug should be added. Defaults to "global".
95
+
96
+ Returns:
97
+ None
98
+ """
99
+ if redis_con is None:
100
+ redis_con = redis.get_connection()
101
+ redis_con.sadd(utils.generate_slugs_key(account), slug)
102
+
103
+
104
+ def delete_metrics_slug(slug, account="global", redis_con=None):
105
+ """
106
+ Deletes a specific slug from the Redis set for a given account.
107
+
108
+ Args:
109
+ slug (str): The slug to delete.
110
+ redis_con: The Redis connection instance (optional).
111
+ account (str): The account from which the slug should be removed. Defaults to "global".
112
+
113
+ Returns:
114
+ None
115
+ """
116
+ if redis_con is None:
117
+ redis_con = redis.get_connection()
118
+ redis_con.srem(utils.generate_slugs_key(account), slug)
119
+
120
+ # now lets delete all keys with our slug prefix
121
+ prefix = utils.generate_slug_prefix(slug, account)
122
+ return __delete_keys_with_prefix(prefix, redis_con)
123
+
124
+
125
+ def __delete_keys_with_prefix(prefix, redis_conn):
126
+ cursor = b'0'
127
+ total_deleted = 0
128
+ while cursor != b'0':
129
+ cursor, keys = redis_conn.scan(cursor=cursor, match=f"{prefix}*")
130
+ if keys:
131
+ total_deleted += len(keys)
132
+ redis_conn.delete(*keys)
133
+ return total_deleted
134
+
135
+ def get_account_slugs(account, redis_con=None):
136
+ """
137
+ Retrieves all slugs associated with a specific account from Redis.
138
+
139
+ Args:
140
+ account (str): The account for which to retrieve slugs.
141
+ redis_con: The Redis connection instance (optional).
142
+
143
+ Returns:
144
+ set: A set of decoded slugs belonging to the specified account.
145
+ """
146
+ if redis_con is None:
147
+ redis_con = redis.get_connection()
148
+ return {s.decode() for s in redis_con.smembers(utils.generate_slugs_key(account))}
149
+
150
+
151
+ def add_category_slug(category, slug, redis_con=None, account="global"):
152
+ """
153
+ Adds a slug to a category set in Redis and indexes the category.
154
+
155
+ Args:
156
+ category (str): The category to which the slug should be added.
157
+ slug (str): The slug to add.
158
+ redis_con: The Redis connection instance (optional).
159
+ account (str): The account under which the category resides. Defaults to "global".
160
+
161
+ Returns:
162
+ None
163
+ """
164
+ if redis_con is None:
165
+ redis_con = redis.get_connection()
166
+ redis_con.sadd(utils.generate_category_slug(account, category), slug)
167
+ redis_con.sadd(utils.generate_category_key(account), category)
168
+
169
+
170
+ def get_category_slugs(category, redis_con=None, account="global"):
171
+ """
172
+ Retrieves all slugs associated with a specific category from Redis.
173
+
174
+ Args:
175
+ category (str): The category for which to retrieve slugs.
176
+ redis_con: The Redis connection instance (optional).
177
+ account (str): The account under which the category resides. Defaults to "global".
178
+
179
+ Returns:
180
+ set: A set of decoded slugs belonging to the specified category.
181
+ """
182
+ if redis_con is None:
183
+ redis_con = redis.get_connection()
184
+ return {s.decode() for s in redis_con.smembers(utils.generate_category_slug(account, category))}
185
+
186
+
187
+ def delete_category(category, redis_con=None, account="global"):
188
+ """
189
+ Deletes a specific category from Redis, including all associated slugs.
190
+
191
+ Args:
192
+ category (str): The category to delete.
193
+ redis_con: The Redis connection instance (optional).
194
+ account (str): The account under which the category resides. Defaults to "global".
195
+
196
+ Returns:
197
+ None
198
+ """
199
+ if redis_con is None:
200
+ redis_con = redis.get_connection()
201
+ category_slug = utils.generate_category_slug(account, category)
202
+ pipeline = redis_con.pipeline()
203
+ pipeline.delete(category_slug) # Deletes the entire set
204
+ pipeline.srem(utils.generate_category_key(account), category) # Remove the category name from index
205
+ pipeline.execute()
206
+
207
+
208
+ def get_categories(redis_con=None, account="global"):
209
+ """
210
+ Retrieves all categories for a specific account from Redis.
211
+
212
+ Args:
213
+ redis_con: The Redis connection instance (optional).
214
+ account (str): The account for which to retrieve categories. Defaults to "global".
215
+
216
+ Returns:
217
+ set: A set of decoded category names belonging to the specified account.
218
+ """
219
+ if redis_con is None:
220
+ redis_con = redis.get_connection()
221
+ return {s.decode() for s in redis_con.smembers(utils.generate_category_key(account))}
222
+
223
+
224
+ def fetch_by_category(category, dt_start=None, dt_end=None, granularity="hours",
225
+ redis_con=None, account="global", with_labels=False):
226
+ """
227
+ Fetches metrics for all slugs within a specified category, date range, and granularity.
228
+
229
+ Args:
230
+ category (str): The category for which to fetch metrics.
231
+ dt_start (datetime, optional): The start date for fetching metrics.
232
+ dt_end (datetime, optional): The end date for fetching metrics.
233
+ granularity (str, optional): The granularity of the metrics. Defaults to "hours".
234
+ redis_con: The Redis connection instance (optional).
235
+ account (str, optional): The account under which the category resides. Defaults to "global".
236
+ with_labels (bool, optional): If True, includes timestamp labels in response data. Defaults to False.
237
+
238
+ Returns:
239
+ list or nobjict: Fetches and returns metric data using the `fetch` function.
240
+ """
241
+ return fetch(get_category_slugs(category, redis_con, account), with_labels=with_labels, account=account)
242
+
243
+
244
+ def set_view_perms(account, perms, redis_con=None):
245
+ """
246
+ Sets view permissions for a specific account.
247
+
248
+ Args:
249
+ account (str): The account for which to set permissions.
250
+ perms (str or list, optional): Permissions to set. If list, it will be converted to a comma-separated string.
251
+ redis_con: The Redis connection instance (optional).
252
+
253
+ Returns:
254
+ None
255
+ """
256
+ if redis_con is None:
257
+ redis_con = redis.get_connection()
258
+ view_perm_key = utils.generate_perm_view_key(account)
259
+ if perms is None:
260
+ redis_con.delete(view_perm_key)
261
+ else:
262
+ if isinstance(perms, list):
263
+ perms = ','.join(perms)
264
+ redis_con.set(view_perm_key, perms)
265
+
266
+
267
+ def set_write_perms(account, perms, redis_con=None):
268
+ """
269
+ Sets write permissions for a specific account.
270
+
271
+ Args:
272
+ account (str): The account for which to set permissions.
273
+ perms (str or list, optional): Permissions to set. If list, it will be converted to a comma-separated string.
274
+ redis_con: The Redis connection instance (optional).
275
+
276
+ Returns:
277
+ None
278
+ """
279
+ if redis_con is None:
280
+ redis_con = redis.get_connection()
281
+ write_perm_key = utils.generate_perm_write_key(account)
282
+ if perms is None:
283
+ redis_con.delete(write_perm_key)
284
+ else:
285
+ if isinstance(perms, list):
286
+ perms = ','.join(perms)
287
+ redis_con.set(write_perm_key, perms)
288
+
289
+
290
+ def get_view_perms(account, redis_con=None):
291
+ """
292
+ Retrieves view permissions for a specific account.
293
+
294
+ Args:
295
+ account (str): The account for which to retrieve permissions.
296
+ redis_con: The Redis connection instance (optional).
297
+
298
+ Returns:
299
+ str or list or None: The permissions for the specified account. Returns None if no permissions are set.
300
+ """
301
+ if redis_con is None:
302
+ redis_con = redis.get_connection()
303
+ view_perm_key = utils.generate_perm_view_key(account)
304
+ perms = redis_con.get(view_perm_key)
305
+ if perms:
306
+ perms = perms.decode('utf-8')
307
+ if ',' in perms:
308
+ perms = perms.split(',')
309
+ return perms
310
+
311
+
312
+ def get_write_perms(account, redis_con=None):
313
+ """
314
+ Retrieves write permissions for a specific account.
315
+
316
+ Args:
317
+ account (str): The account for which to retrieve permissions.
318
+ redis_con: The Redis connection instance (optional).
319
+
320
+ Returns:
321
+ str or list or None: The permissions for the specified account. Returns None if no permissions are set.
322
+ """
323
+ if redis_con is None:
324
+ redis_con = redis.get_connection()
325
+ write_perm_key = utils.generate_perm_write_key(account)
326
+ perms = redis_con.get(write_perm_key)
327
+ if perms:
328
+ perms = perms.decode('utf-8')
329
+ if ',' in perms:
330
+ perms = perms.split(',')
331
+ return perms
@@ -0,0 +1 @@
1
+ from .base import *