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.
- django_nativemojo-0.1.16.dist-info/METADATA +138 -0
- django_nativemojo-0.1.16.dist-info/RECORD +302 -0
- mojo/__init__.py +1 -1
- mojo/apps/account/management/__init__.py +5 -0
- mojo/apps/account/management/commands/__init__.py +6 -0
- mojo/apps/account/management/commands/serializer_admin.py +651 -0
- mojo/apps/account/migrations/0004_user_avatar.py +20 -0
- mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
- mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
- mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
- mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
- mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
- mojo/apps/account/migrations/0010_group_avatar.py +20 -0
- mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
- mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
- mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
- mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
- mojo/apps/account/models/__init__.py +2 -0
- mojo/apps/account/models/device.py +281 -0
- mojo/apps/account/models/group.py +319 -15
- mojo/apps/account/models/member.py +29 -5
- mojo/apps/account/models/push/__init__.py +4 -0
- mojo/apps/account/models/push/config.py +112 -0
- mojo/apps/account/models/push/delivery.py +93 -0
- mojo/apps/account/models/push/device.py +66 -0
- mojo/apps/account/models/push/template.py +99 -0
- mojo/apps/account/models/user.py +369 -19
- mojo/apps/account/rest/__init__.py +2 -0
- mojo/apps/account/rest/device.py +39 -0
- mojo/apps/account/rest/group.py +9 -0
- mojo/apps/account/rest/push.py +187 -0
- mojo/apps/account/rest/user.py +100 -6
- mojo/apps/account/services/__init__.py +1 -0
- mojo/apps/account/services/push.py +363 -0
- mojo/apps/aws/migrations/0001_initial.py +206 -0
- mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
- mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
- mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
- mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
- mojo/apps/aws/models/__init__.py +19 -0
- mojo/apps/aws/models/email_attachment.py +99 -0
- mojo/apps/aws/models/email_domain.py +218 -0
- mojo/apps/aws/models/email_template.py +132 -0
- mojo/apps/aws/models/incoming_email.py +197 -0
- mojo/apps/aws/models/mailbox.py +288 -0
- mojo/apps/aws/models/sent_message.py +175 -0
- mojo/apps/aws/rest/__init__.py +7 -0
- mojo/apps/aws/rest/email.py +33 -0
- mojo/apps/aws/rest/email_ops.py +183 -0
- mojo/apps/aws/rest/messages.py +32 -0
- mojo/apps/aws/rest/s3.py +64 -0
- mojo/apps/aws/rest/send.py +101 -0
- mojo/apps/aws/rest/sns.py +403 -0
- mojo/apps/aws/rest/templates.py +19 -0
- mojo/apps/aws/services/__init__.py +32 -0
- mojo/apps/aws/services/email.py +390 -0
- mojo/apps/aws/services/email_ops.py +548 -0
- mojo/apps/docit/__init__.py +6 -0
- mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
- mojo/apps/docit/markdown_plugins/toc.py +12 -0
- mojo/apps/docit/migrations/0001_initial.py +113 -0
- mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
- mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
- mojo/apps/docit/models/__init__.py +17 -0
- mojo/apps/docit/models/asset.py +231 -0
- mojo/apps/docit/models/book.py +227 -0
- mojo/apps/docit/models/page.py +319 -0
- mojo/apps/docit/models/page_revision.py +203 -0
- mojo/apps/docit/rest/__init__.py +10 -0
- mojo/apps/docit/rest/asset.py +17 -0
- mojo/apps/docit/rest/book.py +22 -0
- mojo/apps/docit/rest/page.py +22 -0
- mojo/apps/docit/rest/page_revision.py +17 -0
- mojo/apps/docit/services/__init__.py +11 -0
- mojo/apps/docit/services/docit.py +315 -0
- mojo/apps/docit/services/markdown.py +44 -0
- mojo/apps/fileman/README.md +8 -8
- mojo/apps/fileman/backends/base.py +76 -70
- mojo/apps/fileman/backends/filesystem.py +86 -86
- mojo/apps/fileman/backends/s3.py +409 -108
- mojo/apps/fileman/migrations/0001_initial.py +106 -0
- mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
- mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
- mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
- mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
- mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
- mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
- mojo/apps/fileman/migrations/0008_file_category.py +18 -0
- mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
- mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
- mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
- mojo/apps/fileman/models/__init__.py +1 -5
- mojo/apps/fileman/models/file.py +240 -58
- mojo/apps/fileman/models/manager.py +427 -31
- mojo/apps/fileman/models/rendition.py +118 -0
- mojo/apps/fileman/renderer/__init__.py +111 -0
- mojo/apps/fileman/renderer/audio.py +403 -0
- mojo/apps/fileman/renderer/base.py +205 -0
- mojo/apps/fileman/renderer/document.py +404 -0
- mojo/apps/fileman/renderer/image.py +222 -0
- mojo/apps/fileman/renderer/utils.py +297 -0
- mojo/apps/fileman/renderer/video.py +304 -0
- mojo/apps/fileman/rest/__init__.py +1 -18
- mojo/apps/fileman/rest/upload.py +22 -32
- mojo/apps/fileman/signals.py +58 -0
- mojo/apps/fileman/tasks.py +254 -0
- mojo/apps/fileman/utils/__init__.py +40 -16
- mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
- mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
- mojo/apps/incident/migrations/0007_event_uid.py +18 -0
- mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
- mojo/apps/incident/migrations/0009_incident_status.py +18 -0
- mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
- mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
- mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
- mojo/apps/incident/models/__init__.py +2 -0
- mojo/apps/incident/models/event.py +35 -0
- mojo/apps/incident/models/history.py +36 -0
- mojo/apps/incident/models/incident.py +3 -1
- mojo/apps/incident/models/ticket.py +62 -0
- mojo/apps/incident/reporter.py +21 -1
- mojo/apps/incident/rest/__init__.py +1 -0
- mojo/apps/incident/rest/event.py +7 -1
- mojo/apps/incident/rest/ticket.py +43 -0
- mojo/apps/jobs/__init__.py +489 -0
- mojo/apps/jobs/adapters.py +24 -0
- mojo/apps/jobs/cli.py +616 -0
- mojo/apps/jobs/daemon.py +370 -0
- mojo/apps/jobs/examples/sample_jobs.py +376 -0
- mojo/apps/jobs/examples/webhook_examples.py +203 -0
- mojo/apps/jobs/handlers/__init__.py +5 -0
- mojo/apps/jobs/handlers/webhook.py +317 -0
- mojo/apps/jobs/job_engine.py +734 -0
- mojo/apps/jobs/keys.py +203 -0
- mojo/apps/jobs/local_queue.py +363 -0
- mojo/apps/jobs/management/__init__.py +3 -0
- mojo/apps/jobs/management/commands/__init__.py +3 -0
- mojo/apps/jobs/manager.py +1327 -0
- mojo/apps/jobs/migrations/0001_initial.py +97 -0
- mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
- mojo/apps/jobs/models/__init__.py +6 -0
- mojo/apps/jobs/models/job.py +441 -0
- mojo/apps/jobs/rest/__init__.py +2 -0
- mojo/apps/jobs/rest/control.py +466 -0
- mojo/apps/jobs/rest/jobs.py +421 -0
- mojo/apps/jobs/scheduler.py +571 -0
- mojo/apps/jobs/services/__init__.py +6 -0
- mojo/apps/jobs/services/job_actions.py +465 -0
- mojo/apps/jobs/settings.py +209 -0
- mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
- mojo/apps/logit/models/log.py +7 -1
- mojo/apps/metrics/__init__.py +8 -1
- mojo/apps/metrics/redis_metrics.py +198 -0
- mojo/apps/metrics/rest/__init__.py +3 -0
- mojo/apps/metrics/rest/categories.py +266 -0
- mojo/apps/metrics/rest/helpers.py +48 -0
- mojo/apps/metrics/rest/permissions.py +99 -0
- mojo/apps/metrics/rest/values.py +277 -0
- mojo/apps/metrics/utils.py +19 -2
- mojo/decorators/auth.py +6 -1
- mojo/decorators/http.py +47 -3
- mojo/helpers/aws/__init__.py +45 -0
- mojo/helpers/aws/ec2.py +804 -0
- mojo/helpers/aws/iam.py +748 -0
- mojo/helpers/aws/inbound_email.py +309 -0
- mojo/helpers/aws/kms.py +413 -0
- mojo/helpers/aws/s3.py +451 -11
- mojo/helpers/aws/ses.py +483 -0
- mojo/helpers/aws/ses_domain.py +959 -0
- mojo/helpers/aws/sns.py +461 -0
- mojo/helpers/crypto/__init__.py +1 -1
- mojo/helpers/crypto/utils.py +15 -0
- mojo/helpers/dates.py +18 -0
- mojo/helpers/location/__init__.py +2 -0
- mojo/helpers/location/countries.py +262 -0
- mojo/helpers/location/geolocation.py +196 -0
- mojo/helpers/logit.py +37 -0
- mojo/helpers/redis/__init__.py +2 -0
- mojo/helpers/redis/adapter.py +606 -0
- mojo/helpers/redis/client.py +48 -0
- mojo/helpers/redis/pool.py +225 -0
- mojo/helpers/request.py +8 -0
- mojo/helpers/response.py +14 -2
- mojo/helpers/settings/__init__.py +2 -0
- mojo/helpers/{settings.py → settings/helper.py} +1 -37
- mojo/helpers/settings/parser.py +132 -0
- mojo/middleware/auth.py +1 -1
- mojo/middleware/cors.py +40 -0
- mojo/middleware/logging.py +131 -12
- mojo/middleware/mojo.py +10 -0
- mojo/models/rest.py +494 -65
- mojo/models/secrets.py +98 -3
- mojo/serializers/__init__.py +106 -0
- mojo/serializers/core/__init__.py +90 -0
- mojo/serializers/core/cache/__init__.py +121 -0
- mojo/serializers/core/cache/backends.py +518 -0
- mojo/serializers/core/cache/base.py +102 -0
- mojo/serializers/core/cache/disabled.py +181 -0
- mojo/serializers/core/cache/memory.py +287 -0
- mojo/serializers/core/cache/redis.py +533 -0
- mojo/serializers/core/cache/utils.py +454 -0
- mojo/serializers/core/manager.py +550 -0
- mojo/serializers/core/serializer.py +475 -0
- mojo/serializers/examples/settings.py +322 -0
- mojo/serializers/formats/csv.py +393 -0
- mojo/serializers/formats/localizers.py +509 -0
- mojo/serializers/{models.py → simple.py} +38 -15
- mojo/serializers/suggested_improvements.md +388 -0
- testit/client.py +1 -1
- testit/helpers.py +35 -4
- testit/runner.py +23 -6
- django_nativemojo-0.1.10.dist-info/METADATA +0 -96
- django_nativemojo-0.1.10.dist-info/RECORD +0 -194
- mojo/apps/metrics/rest/db.py +0 -0
- mojo/apps/notify/README.md +0 -91
- mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
- mojo/apps/notify/admin.py +0 -52
- mojo/apps/notify/handlers/example_handlers.py +0 -516
- mojo/apps/notify/handlers/ses/__init__.py +0 -25
- mojo/apps/notify/handlers/ses/bounce.py +0 -0
- mojo/apps/notify/handlers/ses/complaint.py +0 -25
- mojo/apps/notify/handlers/ses/message.py +0 -86
- mojo/apps/notify/management/commands/__init__.py +0 -1
- mojo/apps/notify/management/commands/process_notifications.py +0 -370
- mojo/apps/notify/mod +0 -0
- mojo/apps/notify/models/__init__.py +0 -12
- mojo/apps/notify/models/account.py +0 -128
- mojo/apps/notify/models/attachment.py +0 -24
- mojo/apps/notify/models/bounce.py +0 -68
- mojo/apps/notify/models/complaint.py +0 -40
- mojo/apps/notify/models/inbox.py +0 -113
- mojo/apps/notify/models/inbox_message.py +0 -173
- mojo/apps/notify/models/outbox.py +0 -129
- mojo/apps/notify/models/outbox_message.py +0 -288
- mojo/apps/notify/models/template.py +0 -30
- mojo/apps/notify/providers/aws.py +0 -73
- mojo/apps/notify/rest/ses.py +0 -0
- mojo/apps/notify/utils/__init__.py +0 -2
- mojo/apps/notify/utils/notifications.py +0 -404
- mojo/apps/notify/utils/parsing.py +0 -202
- mojo/apps/notify/utils/render.py +0 -144
- mojo/apps/tasks/README.md +0 -118
- mojo/apps/tasks/__init__.py +0 -11
- mojo/apps/tasks/manager.py +0 -489
- mojo/apps/tasks/rest/__init__.py +0 -2
- mojo/apps/tasks/rest/hooks.py +0 -0
- mojo/apps/tasks/rest/tasks.py +0 -62
- mojo/apps/tasks/runner.py +0 -174
- mojo/apps/tasks/tq_handlers.py +0 -14
- mojo/helpers/aws/setup_email.py +0 -0
- mojo/helpers/redis.py +0 -10
- mojo/models/meta.py +0 -262
- mojo/ws4redis/README.md +0 -174
- mojo/ws4redis/__init__.py +0 -2
- mojo/ws4redis/client.py +0 -283
- mojo/ws4redis/connection.py +0 -327
- mojo/ws4redis/exceptions.py +0 -32
- mojo/ws4redis/redis.py +0 -183
- mojo/ws4redis/servers/base.py +0 -86
- mojo/ws4redis/servers/django.py +0 -171
- mojo/ws4redis/servers/uwsgi.py +0 -63
- mojo/ws4redis/settings.py +0 -45
- mojo/ws4redis/utf8validator.py +0 -128
- mojo/ws4redis/websocket.py +0 -403
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
- {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
- /mojo/apps/{notify → aws}/__init__.py +0 -0
- /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
- /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
- /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
- /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
- /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
- /mojo/{serializers → rest}/openapi.py +0 -0
- /mojo/{apps/fileman/rest/__init__ → serializers/formats/__init__.py} +0 -0
mojo/ws4redis/client.py
DELETED
@@ -1,283 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
|
3
|
-
from objict import objict
|
4
|
-
|
5
|
-
from mojo.ws4redis.redis import RedisMessage, RedisStore, getRedisClient, getPoolStatus
|
6
|
-
|
7
|
-
|
8
|
-
def buildEventMessage(name=None, message=None, priority=0, model=None, model_pk=None, custom=None):
|
9
|
-
msg = objict(priority=priority)
|
10
|
-
if name:
|
11
|
-
msg["name"] = name
|
12
|
-
|
13
|
-
if message:
|
14
|
-
msg["message"] = message
|
15
|
-
|
16
|
-
if model:
|
17
|
-
msg["component"] = objict(pk=model_pk, model=model)
|
18
|
-
|
19
|
-
if custom:
|
20
|
-
msg.update(custom)
|
21
|
-
return msg.toJSON(as_string=True)
|
22
|
-
|
23
|
-
|
24
|
-
def ping():
|
25
|
-
return getRedisClient().ping()
|
26
|
-
|
27
|
-
|
28
|
-
def exists(key, default=None):
|
29
|
-
c = getRedisClient()
|
30
|
-
return c.exists(key)
|
31
|
-
|
32
|
-
|
33
|
-
def keys(keys):
|
34
|
-
c = getRedisClient()
|
35
|
-
return [v.decode() for v in c.keys(keys)]
|
36
|
-
|
37
|
-
|
38
|
-
def get(key, default=None, field_type=None):
|
39
|
-
c = getRedisClient()
|
40
|
-
v = c.get(key)
|
41
|
-
if v is None:
|
42
|
-
return default
|
43
|
-
if field_type is not None:
|
44
|
-
if field_type == "json":
|
45
|
-
return objict.fromJSON(v, ignore_errors=True)
|
46
|
-
if field_type in [str, "str"]:
|
47
|
-
v = v.decode()
|
48
|
-
return field_type(v)
|
49
|
-
return v
|
50
|
-
|
51
|
-
|
52
|
-
def set(key, value, expire=None):
|
53
|
-
c = getRedisClient()
|
54
|
-
v = c.set(key, value)
|
55
|
-
if expire:
|
56
|
-
c.expire(key, expire)
|
57
|
-
return v
|
58
|
-
|
59
|
-
|
60
|
-
def incr(key, amount=1, expire=None):
|
61
|
-
c = getRedisClient()
|
62
|
-
v = c.incr(key, amount)
|
63
|
-
if expire:
|
64
|
-
c.expire(key, expire)
|
65
|
-
return v
|
66
|
-
|
67
|
-
|
68
|
-
def expire(key, expire):
|
69
|
-
c = getRedisClient()
|
70
|
-
return c.expire(key, expire)
|
71
|
-
|
72
|
-
|
73
|
-
def decr(key, amount=1):
|
74
|
-
c = getRedisClient()
|
75
|
-
return c.decr(key, amount)
|
76
|
-
|
77
|
-
|
78
|
-
def delete(key):
|
79
|
-
c = getRedisClient()
|
80
|
-
return c.delete(key)
|
81
|
-
|
82
|
-
|
83
|
-
# SET FUNCTIONS
|
84
|
-
def sadd(name, *values):
|
85
|
-
# add value to set
|
86
|
-
c = getRedisClient()
|
87
|
-
return c.sadd(name, *values)
|
88
|
-
|
89
|
-
|
90
|
-
def srem(name, *values):
|
91
|
-
# remove value from set
|
92
|
-
c = getRedisClient()
|
93
|
-
return c.srem(name, *values)
|
94
|
-
|
95
|
-
|
96
|
-
def sismember(name, value):
|
97
|
-
# return items in set
|
98
|
-
c = getRedisClient()
|
99
|
-
return c.sismember(name, value)
|
100
|
-
|
101
|
-
|
102
|
-
def scard(name):
|
103
|
-
# count items in set
|
104
|
-
c = getRedisClient()
|
105
|
-
return c.scard(name)
|
106
|
-
|
107
|
-
|
108
|
-
def smembers(name):
|
109
|
-
# return items in set
|
110
|
-
c = getRedisClient()
|
111
|
-
return c.smembers(name)
|
112
|
-
|
113
|
-
|
114
|
-
# HASH FUNCTIONS
|
115
|
-
def hget(name, field, default=None):
|
116
|
-
c = getRedisClient()
|
117
|
-
v = c.hget(name, field)
|
118
|
-
if v is None:
|
119
|
-
return default
|
120
|
-
return v
|
121
|
-
|
122
|
-
|
123
|
-
def hgetall(name):
|
124
|
-
c = getRedisClient()
|
125
|
-
return c.hgetall(name)
|
126
|
-
|
127
|
-
|
128
|
-
def hset(name, field, value):
|
129
|
-
c = getRedisClient()
|
130
|
-
return c.hset(name, field, value)
|
131
|
-
|
132
|
-
|
133
|
-
def hdel(name, field):
|
134
|
-
c = getRedisClient()
|
135
|
-
return c.hdel(name, field)
|
136
|
-
|
137
|
-
|
138
|
-
def hincrby(name, field, inc=1):
|
139
|
-
c = getRedisClient()
|
140
|
-
return c.hincrby(name, field, inc)
|
141
|
-
|
142
|
-
|
143
|
-
def lpush(name, value, unique=False):
|
144
|
-
c = getRedisClient()
|
145
|
-
if isinstance(value, list):
|
146
|
-
for v in value:
|
147
|
-
if unique and value.encode() in c.lrange(name, 0, -1):
|
148
|
-
return 0
|
149
|
-
c.lpush(name, v)
|
150
|
-
return len(value)
|
151
|
-
if unique and value.encode() in c.lrange(name, 0, -1):
|
152
|
-
return 0
|
153
|
-
return c.lpush(name, value)
|
154
|
-
|
155
|
-
|
156
|
-
def rpush(name, value, unique=False):
|
157
|
-
c = getRedisClient()
|
158
|
-
if isinstance(value, list):
|
159
|
-
for v in value:
|
160
|
-
if unique and value.encode() in c.lrange(name, 0, -1):
|
161
|
-
return 0
|
162
|
-
c.rpush(name, v)
|
163
|
-
return len(value)
|
164
|
-
if unique and value.encode() in c.lrange(name, 0, -1):
|
165
|
-
return 0
|
166
|
-
return c.rpush(name, value)
|
167
|
-
|
168
|
-
|
169
|
-
def lpop(name, timeout=None):
|
170
|
-
c = getRedisClient()
|
171
|
-
if timeout is None:
|
172
|
-
return c.lpop(name)
|
173
|
-
r = c.blpop(name, timeout=timeout)
|
174
|
-
if isinstance(r, tuple):
|
175
|
-
return r[1].decode()
|
176
|
-
|
177
|
-
|
178
|
-
def rpop(name, timeout=None):
|
179
|
-
c = getRedisClient()
|
180
|
-
if timeout is None:
|
181
|
-
return c.rpop(name)
|
182
|
-
r = c.brpop(name, timeout=timeout)
|
183
|
-
if isinstance(r, tuple):
|
184
|
-
return r[1].decode()
|
185
|
-
|
186
|
-
|
187
|
-
def lrem(name, value, occurences=1):
|
188
|
-
c = getRedisClient()
|
189
|
-
return c.lrem(name, occurences, value.encode())
|
190
|
-
|
191
|
-
|
192
|
-
def vpop(name, value, timeout=None):
|
193
|
-
c = getRedisClient()
|
194
|
-
start = time.time()
|
195
|
-
while c.lrem(name, 1, value) == 0:
|
196
|
-
if timeout is None:
|
197
|
-
return 0
|
198
|
-
time.sleep(1.0)
|
199
|
-
elapsed = time.time() - start
|
200
|
-
if elapsed > timeout:
|
201
|
-
return 0
|
202
|
-
return 1
|
203
|
-
|
204
|
-
|
205
|
-
def lrange(name, start, end):
|
206
|
-
c = getRedisClient()
|
207
|
-
return [v.decode() for v in c.lrange(name, start, end)]
|
208
|
-
|
209
|
-
|
210
|
-
def sendToUser(user, name, message=None, priority=0, model=None, model_pk=None, custom=None):
|
211
|
-
return sendMessageToUsers([user], buildEventMessage(name, message, priority, model, model_pk, custom))
|
212
|
-
|
213
|
-
|
214
|
-
def sendToUsers(users, name, message=None, priority=0, model=None, model_pk=None, custom=None):
|
215
|
-
return sendMessageToUsers(users, buildEventMessage(name, message, priority, model, model_pk, custom))
|
216
|
-
|
217
|
-
|
218
|
-
def sendMessageToUsers(users, msg):
|
219
|
-
return RedisStore().publish(RedisMessage(msg), channel="user", pk=[u.username for u in users])
|
220
|
-
|
221
|
-
|
222
|
-
def sendToGroup(group, name, message=None, priority=0, model=None, model_pk=None, custom=None):
|
223
|
-
return sendMessageToModels("group", [group], buildEventMessage(name, message, priority, model, model_pk, custom))
|
224
|
-
|
225
|
-
|
226
|
-
def sendToGroups(groups, name, message=None, priority=0, model=None, model_pk=None, custom=None):
|
227
|
-
return sendMessageToModels("group", groups, buildEventMessage(name, message, priority, model, model_pk, custom))
|
228
|
-
|
229
|
-
|
230
|
-
def sendToModels(channel, models, name, message=None, priority=0, model=None, model_pk=None, custom=None):
|
231
|
-
return sendMessageToModels(channel, models, buildEventMessage(name, message, priority, model, model_pk, custom))
|
232
|
-
|
233
|
-
|
234
|
-
def sendMessageToModels(channel, models, msg):
|
235
|
-
return RedisStore().publish(RedisMessage(msg), channel=channel, pk=[g.pk for g in models])
|
236
|
-
|
237
|
-
|
238
|
-
def sendMessageToPK(channel, pk, msg):
|
239
|
-
return RedisStore().publish(RedisMessage(msg), channel=channel, pk=pk)
|
240
|
-
|
241
|
-
|
242
|
-
def broadcast(name, message=None, priority=0, model=None, model_pk=None, custom=None):
|
243
|
-
return broadcastMessage(buildEventMessage(name, message, priority, model, model_pk, custom))
|
244
|
-
|
245
|
-
|
246
|
-
def broadcastMessage(msg):
|
247
|
-
return RedisStore().publish(RedisMessage(msg), channel="broadcast")
|
248
|
-
|
249
|
-
|
250
|
-
def publish(key, data, c=None):
|
251
|
-
if c is None:
|
252
|
-
c = getRedisClient()
|
253
|
-
if isinstance(data, dict):
|
254
|
-
if not isinstance(data, objict):
|
255
|
-
data = objict(data)
|
256
|
-
data = data.toJSON(as_string=True)
|
257
|
-
return c.publish(key, data)
|
258
|
-
|
259
|
-
|
260
|
-
def subscribe(channel):
|
261
|
-
c = getRedisClient()
|
262
|
-
pubsub = c.pubsub()
|
263
|
-
pubsub.subscribe(channel)
|
264
|
-
return pubsub
|
265
|
-
|
266
|
-
|
267
|
-
def waitForMessage(pubsub, msg_filter, timeout=55):
|
268
|
-
timeout_at = time.time() + timeout
|
269
|
-
while time.time() < timeout_at:
|
270
|
-
message = pubsub.get_message()
|
271
|
-
if message is not None:
|
272
|
-
if message.get("type") == "message":
|
273
|
-
msg = objict.fromJSON(message.get("data"))
|
274
|
-
if msg_filter(msg):
|
275
|
-
pubsub.unsubscribe()
|
276
|
-
return msg
|
277
|
-
time.sleep(1.0)
|
278
|
-
pubsub.unsubscribe()
|
279
|
-
return None
|
280
|
-
|
281
|
-
|
282
|
-
def isOnline(name, pk):
|
283
|
-
return sismember(f"{name}:online", pk)
|
mojo/ws4redis/connection.py
DELETED
@@ -1,327 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
from objict import objict
|
3
|
-
from django.core.handlers.wsgi import WSGIRequest
|
4
|
-
from django.apps import apps
|
5
|
-
|
6
|
-
from mojo.ws4redis import settings as private_settings
|
7
|
-
from mojo.ws4redis.redis import RedisStore, RedisMessage
|
8
|
-
|
9
|
-
from mojo.apps.account.utils.jwtoken import JWToken
|
10
|
-
from mojo.helpers import request as rhelper
|
11
|
-
from mojo.helpers.logit import get_logger
|
12
|
-
logger = get_logger("async", filename="async.log")
|
13
|
-
|
14
|
-
MODEL_CACHE = dict() # caching of app.Model for faster access
|
15
|
-
|
16
|
-
ALLOW_ANY_FACILITY = not private_settings.WS4REDIS_FACILITIES
|
17
|
-
DISCONNECT_AFTER_NO_CREDS = 30
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
class WebsocketConnection():
|
22
|
-
def __init__(self, server, environ, start_response):
|
23
|
-
self.server = server
|
24
|
-
self.request = WSGIRequest(environ)
|
25
|
-
self.ip = rhelper.get_remote_ip(self.request)
|
26
|
-
self.ua = rhelper.get_user_agent(self.request)
|
27
|
-
self.facility = self.request.path_info.replace(private_settings.WEBSOCKET_URL, '', 1)
|
28
|
-
self.credentials = objict()
|
29
|
-
self.listening_fds = None
|
30
|
-
self.redis = RedisStore(server._redis_connection)
|
31
|
-
self.websocket = server.upgrade_websocket(environ, start_response)
|
32
|
-
self.last_beat = time.time()
|
33
|
-
self.conneted_time = time.time()
|
34
|
-
self.last_msg = None
|
35
|
-
self._heart_beat = private_settings.WS4REDIS_HEARTBEAT
|
36
|
-
self.debug = private_settings.WS4REDIS_LOG_DEBUG
|
37
|
-
|
38
|
-
@property
|
39
|
-
def elapsed_time(self):
|
40
|
-
return time.time() - self.conneted_time
|
41
|
-
|
42
|
-
def refreshFDs(self):
|
43
|
-
if len(self.listening_fds) == 1:
|
44
|
-
sub_sd = self.redis.get_file_descriptor()
|
45
|
-
if sub_sd:
|
46
|
-
self.listening_fds.append(sub_sd)
|
47
|
-
|
48
|
-
def on_auth(self, msg):
|
49
|
-
if msg.kind == "jwt":
|
50
|
-
self.on_auth_jwt(msg)
|
51
|
-
return
|
52
|
-
# check our other auth mechanisms
|
53
|
-
auther = self.getAuthenticator(msg.kind)
|
54
|
-
if auther is None or not hasattr(auther, "authWS4RedisConnection"):
|
55
|
-
logger.error(f"{self.ip} invalid auth", msg)
|
56
|
-
self.sendToWS(msg.channel, dict(error="invalid auth kind", code=500))
|
57
|
-
return
|
58
|
-
|
59
|
-
# logger.info(f"{self.ip} authenticating")
|
60
|
-
self.credentials = auther.authWS4RedisConnection(msg)
|
61
|
-
if self.credentials is None or self.credentials.pk is None:
|
62
|
-
logger.error(f"{self.ip} invalid credentials for", msg, self.credentials)
|
63
|
-
self.sendToWS(msg.channel, dict(error="invalid credentials", code=401))
|
64
|
-
return
|
65
|
-
self.on_authenticated()
|
66
|
-
|
67
|
-
def on_auth_jwt(self, msg):
|
68
|
-
token = JWToken()
|
69
|
-
if token.payload is None:
|
70
|
-
raise Exception("invalid auth token")
|
71
|
-
User = apps.get_model("account", "User")
|
72
|
-
user, error = User.validate_jwt(token)
|
73
|
-
if error is not None:
|
74
|
-
raise Exception(error)
|
75
|
-
self.credentials = objict(kind="user", instance=user, pk=user.pk, uuid=user.username)
|
76
|
-
self.on_authenticated()
|
77
|
-
|
78
|
-
def on_authenticated(self):
|
79
|
-
if self.debug:
|
80
|
-
logger.info(F"authenticated {self.credentials.kind}: {self.credentials.uuid}")
|
81
|
-
if self.credentials.kind == "user":
|
82
|
-
channel_key = self.redis.subscribe("user", self.facility, self.credentials.uuid)
|
83
|
-
self.forwardPending(channel_key)
|
84
|
-
else:
|
85
|
-
channel_key = self.redis.subscribe(self.credentials.kind, self.facility, self.credentials.uuid)
|
86
|
-
self.forwardPending(channel_key)
|
87
|
-
|
88
|
-
self.redis.publishModelOnline(self.credentials.kind, self.credentials.pk, only_one=self.credentials.only_one)
|
89
|
-
self.refreshFDs()
|
90
|
-
if self.credentials and self.credentials.instance:
|
91
|
-
if hasattr(self.credentials.instance, "on_ws_online"):
|
92
|
-
self.credentials.instance.on_ws_online()
|
93
|
-
|
94
|
-
def forwardPending(self, channel_key):
|
95
|
-
msg = self.redis.getPendingMessage(channel_key)
|
96
|
-
if msg:
|
97
|
-
if self.debug:
|
98
|
-
logger.info("pending messages", repr(msg))
|
99
|
-
channel, pk = self.parseChannel(channel_key)
|
100
|
-
self.sendToWS(channel, msg, pk)
|
101
|
-
|
102
|
-
def on_resubscribe(self, msg):
|
103
|
-
for ch in msg.channels:
|
104
|
-
self.redis.subscribe(ch.channel, self.facility, ch.pk)
|
105
|
-
|
106
|
-
def getAppModel(self, app_model):
|
107
|
-
if app_model not in MODEL_CACHE:
|
108
|
-
app_label, model_name = app_model.split('.')
|
109
|
-
MODEL_CACHE[app_model] = apps.get_model(app_label, model_name)
|
110
|
-
return MODEL_CACHE[app_model]
|
111
|
-
|
112
|
-
def getAuthenticator(self, channel):
|
113
|
-
auther_path = private_settings.WS4REDIS_AUTHENTICATORS.get(channel, None)
|
114
|
-
if auther_path is not None:
|
115
|
-
return self.getAppModel(auther_path)
|
116
|
-
return None
|
117
|
-
|
118
|
-
def on_subscribe(self, msg):
|
119
|
-
pks = self.canSubscribeTo(msg)
|
120
|
-
if bool(pks):
|
121
|
-
for pk in pks:
|
122
|
-
channel_key = self.redis.subscribe(msg.channel, self.facility, pk)
|
123
|
-
else:
|
124
|
-
logger.warning("subscribe permission denied", msg)
|
125
|
-
self.sendToWS(msg.channel, dict(error="subscribe permission denied"))
|
126
|
-
|
127
|
-
def canSubscribeTo(self, msg):
|
128
|
-
ch_model = private_settings.WS4REDIS_CHANNELS.get(msg.channel, None)
|
129
|
-
if not ch_model:
|
130
|
-
return None
|
131
|
-
if ch_model == "any":
|
132
|
-
# this allows anything to be sent
|
133
|
-
return [msg.pk]
|
134
|
-
Model = self.getAppModel(ch_model)
|
135
|
-
if not hasattr(Model, "can_ws_subscribe_to"):
|
136
|
-
return None
|
137
|
-
return Model.can_ws_subscribe_to(self.credentials, msg)
|
138
|
-
|
139
|
-
def on_unsubscribe(self, msg):
|
140
|
-
self.redis.unsubscribe(msg.channel, self.facility, msg.pk)
|
141
|
-
|
142
|
-
def on_publish(self, msg):
|
143
|
-
if self.canPublishTo(msg):
|
144
|
-
self.redis.publish(msg.message, channel=msg.channel, pk=msg.pk)
|
145
|
-
else:
|
146
|
-
logger.warning("publish permission denied", msg)
|
147
|
-
self.sendToWS(msg.channel, dict(error="publish permission denied"))
|
148
|
-
|
149
|
-
def canPublishTo(self, msg):
|
150
|
-
ch_model = private_settings.WS4REDIS_CHANNELS.get(msg.channel, None)
|
151
|
-
if not ch_model:
|
152
|
-
# you can publish to any channels you may create
|
153
|
-
return True
|
154
|
-
Model = self.getAppModel(ch_model)
|
155
|
-
if not hasattr(Model, "canPublishTo"):
|
156
|
-
logger.warning("canPublishTo", "no canPublishTo")
|
157
|
-
return False
|
158
|
-
return Model.can_ws_publish_to(self.credentials, msg)
|
159
|
-
|
160
|
-
def on_ws_msg(self, raw_data):
|
161
|
-
if isinstance(raw_data, bytes):
|
162
|
-
raw_data = raw_data.decode()
|
163
|
-
if raw_data == self._heart_beat:
|
164
|
-
# echo the heartbeat
|
165
|
-
self.websocket.send(self._heart_beat)
|
166
|
-
return
|
167
|
-
if self.debug:
|
168
|
-
logger.info("on_ws_msg", repr(raw_data))
|
169
|
-
dmsg = objict.from_json(raw_data, ignore_errors=True)
|
170
|
-
if dmsg.action:
|
171
|
-
# this is a special message that we want to handle directly
|
172
|
-
try:
|
173
|
-
if dmsg.action == "auth":
|
174
|
-
self.on_auth(dmsg)
|
175
|
-
elif dmsg.action == "subscribe":
|
176
|
-
self.on_subscribe(dmsg)
|
177
|
-
elif dmsg.action == "unsubscribe":
|
178
|
-
self.on_unsubscribe(dmsg)
|
179
|
-
elif dmsg.action == "resubscribe":
|
180
|
-
self.on_resubscribe(dmsg)
|
181
|
-
elif dmsg.action == "publish":
|
182
|
-
self.on_publish(dmsg)
|
183
|
-
elif dmsg.action == "save":
|
184
|
-
self.on_save(dmsg)
|
185
|
-
elif dmsg.channel:
|
186
|
-
self.on_channel_msg(dmsg)
|
187
|
-
except Exception as err:
|
188
|
-
self.sendToWS(dmsg.get("channel", "system"), dict(error=str(err)))
|
189
|
-
logger.exception(self.ip)
|
190
|
-
|
191
|
-
def on_save(self, msg):
|
192
|
-
if self.credentials and self.credentials.instance:
|
193
|
-
if hasattr(self.credentials.instance, "on_ws_save"):
|
194
|
-
resp = objict(id=msg.id, name="save")
|
195
|
-
if msg.echo:
|
196
|
-
resp.data = msg.data
|
197
|
-
resp.status = self.credentials.instance.on_ws_save(msg)
|
198
|
-
self.sendToWS(self.credentials.kind, resp)
|
199
|
-
|
200
|
-
def on_channel_msg(self, msg):
|
201
|
-
if not self.canPublishTo(msg):
|
202
|
-
logger.warning("on_channel_msg permission denied", msg, private_settings.WS4REDIS_CHANNELS)
|
203
|
-
self.sendToWS(msg.channel, dict(error="cannot publish to channel"))
|
204
|
-
return None
|
205
|
-
ch_model = private_settings.WS4REDIS_CHANNELS.get(msg.channel, None)
|
206
|
-
if ch_model is None:
|
207
|
-
return None
|
208
|
-
Model = self.getAppModel(ch_model)
|
209
|
-
if not hasattr(Model, "on_ws_message"):
|
210
|
-
logger.warning(f"{msg.channel} does not support on_ws_message")
|
211
|
-
self.sendToWS(msg.channel, dict(error="channel does not support on_ws_message"))
|
212
|
-
return None
|
213
|
-
Model.on_ws_message(self.credentials, msg, self)
|
214
|
-
|
215
|
-
def on_redis_pending(self):
|
216
|
-
sub_resp = self.redis.getSubMessage()
|
217
|
-
if sub_resp:
|
218
|
-
if self.debug:
|
219
|
-
logger.info("incoming redis msg", sub_resp)
|
220
|
-
self.on_redis_msg(sub_resp)
|
221
|
-
|
222
|
-
def sendToWS(self, channel, message, pk=None):
|
223
|
-
msg = objict(channel=channel)
|
224
|
-
if pk is not None:
|
225
|
-
msg.pk = pk
|
226
|
-
if isinstance(message, (str, bytes)):
|
227
|
-
msg.message = objict.from_json(message, ignore_errors=True)
|
228
|
-
if not msg.message:
|
229
|
-
msg.message = message
|
230
|
-
elif msg.message.name == "logout" and msg.message.pk == self.credentials.pk:
|
231
|
-
raise Exception("websocket is being logged out")
|
232
|
-
self.websocket.send(msg.toJSON(as_string=True))
|
233
|
-
|
234
|
-
def on_redis_msg(self, sub_resp):
|
235
|
-
if isinstance(sub_resp, list):
|
236
|
-
if sub_resp[0] == b'subscribe':
|
237
|
-
# this is succesfull subscriptions
|
238
|
-
# notify ws
|
239
|
-
channel, pk = self.parseChannel(sub_resp[1].decode())
|
240
|
-
msg = objict(name="subscribed", channel=channel, status=sub_resp[2])
|
241
|
-
if pk is not None:
|
242
|
-
msg.pk = pk
|
243
|
-
self.websocket.send(msg.toJSON(as_string=True))
|
244
|
-
return
|
245
|
-
elif sub_resp[0] == b'unsubscribe':
|
246
|
-
# this is succesfull subscriptions
|
247
|
-
# notify ws
|
248
|
-
channel, pk = self.parseChannel(sub_resp[1].decode())
|
249
|
-
msg = objict(name="unsubscribed", channel=channel, status=sub_resp[2])
|
250
|
-
if pk is not None:
|
251
|
-
msg.pk = pk
|
252
|
-
self.websocket.send(msg.toJSON(as_string=True))
|
253
|
-
return
|
254
|
-
elif sub_resp[0] == b'message':
|
255
|
-
channel, pk = self.parseChannel(sub_resp[1].decode())
|
256
|
-
self.sendToWS(channel, sub_resp[2].decode(), pk)
|
257
|
-
return
|
258
|
-
sendmsg = RedisMessage(sub_resp)
|
259
|
-
if self.debug:
|
260
|
-
logger.info(sub_resp)
|
261
|
-
if sendmsg:
|
262
|
-
if self.debug:
|
263
|
-
logger.info("pushing to websocket", sendmsg)
|
264
|
-
self.websocket.send(sendmsg)
|
265
|
-
|
266
|
-
def parseChannel(self, channel):
|
267
|
-
fields = channel.split(":")
|
268
|
-
fields.pop(0)
|
269
|
-
fields.pop()
|
270
|
-
if len(fields) == 1:
|
271
|
-
return fields[0], None
|
272
|
-
return fields[0], fields[1]
|
273
|
-
|
274
|
-
def on_ws_pending(self):
|
275
|
-
try:
|
276
|
-
self.last_msg = self.websocket.receive()
|
277
|
-
if bool(self.last_msg):
|
278
|
-
self.on_ws_msg(self.last_msg)
|
279
|
-
except Exception:
|
280
|
-
# logger.exception()
|
281
|
-
logger.error(f"{self.ip}: unable to recv on ws... flushing")
|
282
|
-
self.websocket.flush()
|
283
|
-
|
284
|
-
def checkHeartbeat(self):
|
285
|
-
beat_delta = time.time() - self.last_beat
|
286
|
-
if beat_delta > 30.0:
|
287
|
-
self.last_beat = time.time()
|
288
|
-
if private_settings.WS4REDIS_HEARTBEAT and not self.websocket.closed:
|
289
|
-
# logger.info("send heartbeat")
|
290
|
-
self.websocket.send(private_settings.WS4REDIS_HEARTBEAT)
|
291
|
-
|
292
|
-
def release(self):
|
293
|
-
self.redis.release()
|
294
|
-
self.listening_fds = None
|
295
|
-
if self.websocket:
|
296
|
-
self.websocket.close(code=1001, message='Websocket Closed')
|
297
|
-
if self.credentials and self.credentials.instance:
|
298
|
-
if hasattr(self.credentials.instance, "on_ws_offline"):
|
299
|
-
self.credentials.instance.on_ws_offline()
|
300
|
-
|
301
|
-
def handleComs(self):
|
302
|
-
# check if token is in url
|
303
|
-
if not ALLOW_ANY_FACILITY and self.facility not in private_settings.WS4REDIS_FACILITIES:
|
304
|
-
self.sendToWS("facility", dict(error=f"facility name not allowed: '{self.facility}'. Please check your connection URL."))
|
305
|
-
return
|
306
|
-
|
307
|
-
websocket_fd = self.websocket.get_file_descriptor()
|
308
|
-
self.listening_fds = [websocket_fd]
|
309
|
-
if callable(private_settings.URL_AUTHENTICATOR):
|
310
|
-
private_settings.URL_AUTHENTICATOR(self)
|
311
|
-
|
312
|
-
while self.websocket and not self.websocket.closed:
|
313
|
-
ready = self.server.select(self.listening_fds, [], [], 4.0)[0]
|
314
|
-
if not ready:
|
315
|
-
self.websocket.flush()
|
316
|
-
if not self.credentials:
|
317
|
-
if self.elapsed_time > 8 and self.elapsed_time < 30:
|
318
|
-
logger.error(f"{self.ip} has sent no credentials", self.ua)
|
319
|
-
self.sendToWS("user", dict(error="no credentials received"))
|
320
|
-
elif private_settings.WS4REDIS_NOAUTH_CLOSE and self.elapsed_time > 30:
|
321
|
-
self.release()
|
322
|
-
|
323
|
-
for fd in ready:
|
324
|
-
if fd == websocket_fd:
|
325
|
-
self.on_ws_pending()
|
326
|
-
else:
|
327
|
-
self.on_redis_pending()
|
mojo/ws4redis/exceptions.py
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
#-*- coding: utf-8 -*-
|
2
|
-
from socket import error as socket_error
|
3
|
-
from django.http import BadHeaderError
|
4
|
-
|
5
|
-
|
6
|
-
class WebSocketError(socket_error):
|
7
|
-
"""
|
8
|
-
Raised when an active websocket encounters a problem.
|
9
|
-
"""
|
10
|
-
|
11
|
-
|
12
|
-
class FrameTooLargeException(WebSocketError):
|
13
|
-
"""
|
14
|
-
Raised if a received frame is too large.
|
15
|
-
"""
|
16
|
-
|
17
|
-
|
18
|
-
class HandshakeError(BadHeaderError):
|
19
|
-
"""
|
20
|
-
Raised if an error occurs during protocol handshake.
|
21
|
-
"""
|
22
|
-
|
23
|
-
|
24
|
-
class UpgradeRequiredError(HandshakeError):
|
25
|
-
"""
|
26
|
-
Raised if protocol must be upgraded.
|
27
|
-
"""
|
28
|
-
|
29
|
-
class SSLRequiredError(socket_error):
|
30
|
-
"""
|
31
|
-
Raised if protocol must be upgraded.
|
32
|
-
"""
|