django-nativemojo 0.1.10__py3-none-any.whl → 0.1.16__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. django_nativemojo-0.1.16.dist-info/METADATA +138 -0
  2. django_nativemojo-0.1.16.dist-info/RECORD +302 -0
  3. mojo/__init__.py +1 -1
  4. mojo/apps/account/management/__init__.py +5 -0
  5. mojo/apps/account/management/commands/__init__.py +6 -0
  6. mojo/apps/account/management/commands/serializer_admin.py +651 -0
  7. mojo/apps/account/migrations/0004_user_avatar.py +20 -0
  8. mojo/apps/account/migrations/0005_group_last_activity.py +18 -0
  9. mojo/apps/account/migrations/0006_add_device_tracking_models.py +72 -0
  10. mojo/apps/account/migrations/0007_delete_userdevicelocation.py +16 -0
  11. mojo/apps/account/migrations/0008_userdevicelocation.py +33 -0
  12. mojo/apps/account/migrations/0009_geolocatedip_subnet.py +18 -0
  13. mojo/apps/account/migrations/0010_group_avatar.py +20 -0
  14. mojo/apps/account/migrations/0011_user_org_registereddevice_pushconfig_and_more.py +118 -0
  15. mojo/apps/account/migrations/0012_remove_pushconfig_apns_key_file_and_more.py +21 -0
  16. mojo/apps/account/migrations/0013_pushconfig_test_mode_alter_pushconfig_apns_enabled_and_more.py +28 -0
  17. mojo/apps/account/migrations/0014_notificationdelivery_data_payload_and_more.py +48 -0
  18. mojo/apps/account/models/__init__.py +2 -0
  19. mojo/apps/account/models/device.py +281 -0
  20. mojo/apps/account/models/group.py +319 -15
  21. mojo/apps/account/models/member.py +29 -5
  22. mojo/apps/account/models/push/__init__.py +4 -0
  23. mojo/apps/account/models/push/config.py +112 -0
  24. mojo/apps/account/models/push/delivery.py +93 -0
  25. mojo/apps/account/models/push/device.py +66 -0
  26. mojo/apps/account/models/push/template.py +99 -0
  27. mojo/apps/account/models/user.py +369 -19
  28. mojo/apps/account/rest/__init__.py +2 -0
  29. mojo/apps/account/rest/device.py +39 -0
  30. mojo/apps/account/rest/group.py +9 -0
  31. mojo/apps/account/rest/push.py +187 -0
  32. mojo/apps/account/rest/user.py +100 -6
  33. mojo/apps/account/services/__init__.py +1 -0
  34. mojo/apps/account/services/push.py +363 -0
  35. mojo/apps/aws/migrations/0001_initial.py +206 -0
  36. mojo/apps/aws/migrations/0002_emaildomain_can_recv_emaildomain_can_send_and_more.py +28 -0
  37. mojo/apps/aws/migrations/0003_mailbox_is_domain_default_mailbox_is_system_default_and_more.py +31 -0
  38. mojo/apps/aws/migrations/0004_s3bucket.py +39 -0
  39. mojo/apps/aws/migrations/0005_alter_emaildomain_region_delete_s3bucket.py +21 -0
  40. mojo/apps/aws/models/__init__.py +19 -0
  41. mojo/apps/aws/models/email_attachment.py +99 -0
  42. mojo/apps/aws/models/email_domain.py +218 -0
  43. mojo/apps/aws/models/email_template.py +132 -0
  44. mojo/apps/aws/models/incoming_email.py +197 -0
  45. mojo/apps/aws/models/mailbox.py +288 -0
  46. mojo/apps/aws/models/sent_message.py +175 -0
  47. mojo/apps/aws/rest/__init__.py +7 -0
  48. mojo/apps/aws/rest/email.py +33 -0
  49. mojo/apps/aws/rest/email_ops.py +183 -0
  50. mojo/apps/aws/rest/messages.py +32 -0
  51. mojo/apps/aws/rest/s3.py +64 -0
  52. mojo/apps/aws/rest/send.py +101 -0
  53. mojo/apps/aws/rest/sns.py +403 -0
  54. mojo/apps/aws/rest/templates.py +19 -0
  55. mojo/apps/aws/services/__init__.py +32 -0
  56. mojo/apps/aws/services/email.py +390 -0
  57. mojo/apps/aws/services/email_ops.py +548 -0
  58. mojo/apps/docit/__init__.py +6 -0
  59. mojo/apps/docit/markdown_plugins/syntax_highlight.py +25 -0
  60. mojo/apps/docit/markdown_plugins/toc.py +12 -0
  61. mojo/apps/docit/migrations/0001_initial.py +113 -0
  62. mojo/apps/docit/migrations/0002_alter_book_modified_by_alter_page_modified_by.py +26 -0
  63. mojo/apps/docit/migrations/0003_alter_book_group.py +20 -0
  64. mojo/apps/docit/models/__init__.py +17 -0
  65. mojo/apps/docit/models/asset.py +231 -0
  66. mojo/apps/docit/models/book.py +227 -0
  67. mojo/apps/docit/models/page.py +319 -0
  68. mojo/apps/docit/models/page_revision.py +203 -0
  69. mojo/apps/docit/rest/__init__.py +10 -0
  70. mojo/apps/docit/rest/asset.py +17 -0
  71. mojo/apps/docit/rest/book.py +22 -0
  72. mojo/apps/docit/rest/page.py +22 -0
  73. mojo/apps/docit/rest/page_revision.py +17 -0
  74. mojo/apps/docit/services/__init__.py +11 -0
  75. mojo/apps/docit/services/docit.py +315 -0
  76. mojo/apps/docit/services/markdown.py +44 -0
  77. mojo/apps/fileman/README.md +8 -8
  78. mojo/apps/fileman/backends/base.py +76 -70
  79. mojo/apps/fileman/backends/filesystem.py +86 -86
  80. mojo/apps/fileman/backends/s3.py +409 -108
  81. mojo/apps/fileman/migrations/0001_initial.py +106 -0
  82. mojo/apps/fileman/migrations/0002_filemanager_parent_alter_filemanager_max_file_size.py +24 -0
  83. mojo/apps/fileman/migrations/0003_remove_file_fileman_fil_upload__c4bc35_idx_and_more.py +25 -0
  84. mojo/apps/fileman/migrations/0004_remove_file_original_filename_and_more.py +39 -0
  85. mojo/apps/fileman/migrations/0005_alter_file_upload_token.py +18 -0
  86. mojo/apps/fileman/migrations/0006_file_download_url_filemanager_forever_urls.py +23 -0
  87. mojo/apps/fileman/migrations/0007_remove_filemanager_forever_urls_and_more.py +22 -0
  88. mojo/apps/fileman/migrations/0008_file_category.py +18 -0
  89. mojo/apps/fileman/migrations/0009_rename_file_path_file_storage_file_path.py +18 -0
  90. mojo/apps/fileman/migrations/0010_filerendition.py +33 -0
  91. mojo/apps/fileman/migrations/0011_alter_filerendition_original_file.py +19 -0
  92. mojo/apps/fileman/models/__init__.py +1 -5
  93. mojo/apps/fileman/models/file.py +240 -58
  94. mojo/apps/fileman/models/manager.py +427 -31
  95. mojo/apps/fileman/models/rendition.py +118 -0
  96. mojo/apps/fileman/renderer/__init__.py +111 -0
  97. mojo/apps/fileman/renderer/audio.py +403 -0
  98. mojo/apps/fileman/renderer/base.py +205 -0
  99. mojo/apps/fileman/renderer/document.py +404 -0
  100. mojo/apps/fileman/renderer/image.py +222 -0
  101. mojo/apps/fileman/renderer/utils.py +297 -0
  102. mojo/apps/fileman/renderer/video.py +304 -0
  103. mojo/apps/fileman/rest/__init__.py +1 -18
  104. mojo/apps/fileman/rest/upload.py +22 -32
  105. mojo/apps/fileman/signals.py +58 -0
  106. mojo/apps/fileman/tasks.py +254 -0
  107. mojo/apps/fileman/utils/__init__.py +40 -16
  108. mojo/apps/incident/migrations/0005_incidenthistory.py +39 -0
  109. mojo/apps/incident/migrations/0006_alter_incident_state.py +18 -0
  110. mojo/apps/incident/migrations/0007_event_uid.py +18 -0
  111. mojo/apps/incident/migrations/0008_ticket_ticketnote.py +55 -0
  112. mojo/apps/incident/migrations/0009_incident_status.py +18 -0
  113. mojo/apps/incident/migrations/0010_event_country_code.py +18 -0
  114. mojo/apps/incident/migrations/0011_incident_country_code.py +18 -0
  115. mojo/apps/incident/migrations/0012_alter_incident_status.py +18 -0
  116. mojo/apps/incident/models/__init__.py +2 -0
  117. mojo/apps/incident/models/event.py +35 -0
  118. mojo/apps/incident/models/history.py +36 -0
  119. mojo/apps/incident/models/incident.py +3 -1
  120. mojo/apps/incident/models/ticket.py +62 -0
  121. mojo/apps/incident/reporter.py +21 -1
  122. mojo/apps/incident/rest/__init__.py +1 -0
  123. mojo/apps/incident/rest/event.py +7 -1
  124. mojo/apps/incident/rest/ticket.py +43 -0
  125. mojo/apps/jobs/__init__.py +489 -0
  126. mojo/apps/jobs/adapters.py +24 -0
  127. mojo/apps/jobs/cli.py +616 -0
  128. mojo/apps/jobs/daemon.py +370 -0
  129. mojo/apps/jobs/examples/sample_jobs.py +376 -0
  130. mojo/apps/jobs/examples/webhook_examples.py +203 -0
  131. mojo/apps/jobs/handlers/__init__.py +5 -0
  132. mojo/apps/jobs/handlers/webhook.py +317 -0
  133. mojo/apps/jobs/job_engine.py +734 -0
  134. mojo/apps/jobs/keys.py +203 -0
  135. mojo/apps/jobs/local_queue.py +363 -0
  136. mojo/apps/jobs/management/__init__.py +3 -0
  137. mojo/apps/jobs/management/commands/__init__.py +3 -0
  138. mojo/apps/jobs/manager.py +1327 -0
  139. mojo/apps/jobs/migrations/0001_initial.py +97 -0
  140. mojo/apps/jobs/migrations/0002_alter_job_max_retries_joblog.py +39 -0
  141. mojo/apps/jobs/models/__init__.py +6 -0
  142. mojo/apps/jobs/models/job.py +441 -0
  143. mojo/apps/jobs/rest/__init__.py +2 -0
  144. mojo/apps/jobs/rest/control.py +466 -0
  145. mojo/apps/jobs/rest/jobs.py +421 -0
  146. mojo/apps/jobs/scheduler.py +571 -0
  147. mojo/apps/jobs/services/__init__.py +6 -0
  148. mojo/apps/jobs/services/job_actions.py +465 -0
  149. mojo/apps/jobs/settings.py +209 -0
  150. mojo/apps/logit/migrations/0004_alter_log_level.py +18 -0
  151. mojo/apps/logit/models/log.py +7 -1
  152. mojo/apps/metrics/__init__.py +8 -1
  153. mojo/apps/metrics/redis_metrics.py +198 -0
  154. mojo/apps/metrics/rest/__init__.py +3 -0
  155. mojo/apps/metrics/rest/categories.py +266 -0
  156. mojo/apps/metrics/rest/helpers.py +48 -0
  157. mojo/apps/metrics/rest/permissions.py +99 -0
  158. mojo/apps/metrics/rest/values.py +277 -0
  159. mojo/apps/metrics/utils.py +19 -2
  160. mojo/decorators/auth.py +6 -1
  161. mojo/decorators/http.py +47 -3
  162. mojo/helpers/aws/__init__.py +45 -0
  163. mojo/helpers/aws/ec2.py +804 -0
  164. mojo/helpers/aws/iam.py +748 -0
  165. mojo/helpers/aws/inbound_email.py +309 -0
  166. mojo/helpers/aws/kms.py +413 -0
  167. mojo/helpers/aws/s3.py +451 -11
  168. mojo/helpers/aws/ses.py +483 -0
  169. mojo/helpers/aws/ses_domain.py +959 -0
  170. mojo/helpers/aws/sns.py +461 -0
  171. mojo/helpers/crypto/__init__.py +1 -1
  172. mojo/helpers/crypto/utils.py +15 -0
  173. mojo/helpers/dates.py +18 -0
  174. mojo/helpers/location/__init__.py +2 -0
  175. mojo/helpers/location/countries.py +262 -0
  176. mojo/helpers/location/geolocation.py +196 -0
  177. mojo/helpers/logit.py +37 -0
  178. mojo/helpers/redis/__init__.py +2 -0
  179. mojo/helpers/redis/adapter.py +606 -0
  180. mojo/helpers/redis/client.py +48 -0
  181. mojo/helpers/redis/pool.py +225 -0
  182. mojo/helpers/request.py +8 -0
  183. mojo/helpers/response.py +14 -2
  184. mojo/helpers/settings/__init__.py +2 -0
  185. mojo/helpers/{settings.py → settings/helper.py} +1 -37
  186. mojo/helpers/settings/parser.py +132 -0
  187. mojo/middleware/auth.py +1 -1
  188. mojo/middleware/cors.py +40 -0
  189. mojo/middleware/logging.py +131 -12
  190. mojo/middleware/mojo.py +10 -0
  191. mojo/models/rest.py +494 -65
  192. mojo/models/secrets.py +98 -3
  193. mojo/serializers/__init__.py +106 -0
  194. mojo/serializers/core/__init__.py +90 -0
  195. mojo/serializers/core/cache/__init__.py +121 -0
  196. mojo/serializers/core/cache/backends.py +518 -0
  197. mojo/serializers/core/cache/base.py +102 -0
  198. mojo/serializers/core/cache/disabled.py +181 -0
  199. mojo/serializers/core/cache/memory.py +287 -0
  200. mojo/serializers/core/cache/redis.py +533 -0
  201. mojo/serializers/core/cache/utils.py +454 -0
  202. mojo/serializers/core/manager.py +550 -0
  203. mojo/serializers/core/serializer.py +475 -0
  204. mojo/serializers/examples/settings.py +322 -0
  205. mojo/serializers/formats/csv.py +393 -0
  206. mojo/serializers/formats/localizers.py +509 -0
  207. mojo/serializers/{models.py → simple.py} +38 -15
  208. mojo/serializers/suggested_improvements.md +388 -0
  209. testit/client.py +1 -1
  210. testit/helpers.py +35 -4
  211. testit/runner.py +23 -6
  212. django_nativemojo-0.1.10.dist-info/METADATA +0 -96
  213. django_nativemojo-0.1.10.dist-info/RECORD +0 -194
  214. mojo/apps/metrics/rest/db.py +0 -0
  215. mojo/apps/notify/README.md +0 -91
  216. mojo/apps/notify/README_NOTIFICATIONS.md +0 -566
  217. mojo/apps/notify/admin.py +0 -52
  218. mojo/apps/notify/handlers/example_handlers.py +0 -516
  219. mojo/apps/notify/handlers/ses/__init__.py +0 -25
  220. mojo/apps/notify/handlers/ses/bounce.py +0 -0
  221. mojo/apps/notify/handlers/ses/complaint.py +0 -25
  222. mojo/apps/notify/handlers/ses/message.py +0 -86
  223. mojo/apps/notify/management/commands/__init__.py +0 -1
  224. mojo/apps/notify/management/commands/process_notifications.py +0 -370
  225. mojo/apps/notify/mod +0 -0
  226. mojo/apps/notify/models/__init__.py +0 -12
  227. mojo/apps/notify/models/account.py +0 -128
  228. mojo/apps/notify/models/attachment.py +0 -24
  229. mojo/apps/notify/models/bounce.py +0 -68
  230. mojo/apps/notify/models/complaint.py +0 -40
  231. mojo/apps/notify/models/inbox.py +0 -113
  232. mojo/apps/notify/models/inbox_message.py +0 -173
  233. mojo/apps/notify/models/outbox.py +0 -129
  234. mojo/apps/notify/models/outbox_message.py +0 -288
  235. mojo/apps/notify/models/template.py +0 -30
  236. mojo/apps/notify/providers/aws.py +0 -73
  237. mojo/apps/notify/rest/ses.py +0 -0
  238. mojo/apps/notify/utils/__init__.py +0 -2
  239. mojo/apps/notify/utils/notifications.py +0 -404
  240. mojo/apps/notify/utils/parsing.py +0 -202
  241. mojo/apps/notify/utils/render.py +0 -144
  242. mojo/apps/tasks/README.md +0 -118
  243. mojo/apps/tasks/__init__.py +0 -11
  244. mojo/apps/tasks/manager.py +0 -489
  245. mojo/apps/tasks/rest/__init__.py +0 -2
  246. mojo/apps/tasks/rest/hooks.py +0 -0
  247. mojo/apps/tasks/rest/tasks.py +0 -62
  248. mojo/apps/tasks/runner.py +0 -174
  249. mojo/apps/tasks/tq_handlers.py +0 -14
  250. mojo/helpers/aws/setup_email.py +0 -0
  251. mojo/helpers/redis.py +0 -10
  252. mojo/models/meta.py +0 -262
  253. mojo/ws4redis/README.md +0 -174
  254. mojo/ws4redis/__init__.py +0 -2
  255. mojo/ws4redis/client.py +0 -283
  256. mojo/ws4redis/connection.py +0 -327
  257. mojo/ws4redis/exceptions.py +0 -32
  258. mojo/ws4redis/redis.py +0 -183
  259. mojo/ws4redis/servers/base.py +0 -86
  260. mojo/ws4redis/servers/django.py +0 -171
  261. mojo/ws4redis/servers/uwsgi.py +0 -63
  262. mojo/ws4redis/settings.py +0 -45
  263. mojo/ws4redis/utf8validator.py +0 -128
  264. mojo/ws4redis/websocket.py +0 -403
  265. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/LICENSE +0 -0
  266. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/NOTICE +0 -0
  267. {django_nativemojo-0.1.10.dist-info → django_nativemojo-0.1.16.dist-info}/WHEEL +0 -0
  268. /mojo/apps/{notify → aws}/__init__.py +0 -0
  269. /mojo/apps/{notify/handlers → aws/migrations}/__init__.py +0 -0
  270. /mojo/apps/{notify/management → docit/markdown_plugins}/__init__.py +0 -0
  271. /mojo/apps/{notify/providers → docit/migrations}/__init__.py +0 -0
  272. /mojo/apps/{notify/rest → fileman/migrations}/__init__.py +0 -0
  273. /mojo/{ws4redis/servers → apps/jobs/examples}/__init__.py +0 -0
  274. /mojo/apps/{fileman/models/render.py → jobs/migrations/__init__.py} +0 -0
  275. /mojo/{serializers → rest}/openapi.py +0 -0
  276. /mojo/{apps/fileman/rest/__init__ → serializers/formats/__init__.py} +0 -0
mojo/ws4redis/redis.py DELETED
@@ -1,183 +0,0 @@
1
- from redis import ConnectionPool, StrictRedis
2
-
3
- from mojo.ws4redis import settings
4
- import time
5
- from objict import objict
6
-
7
- from mojo.helpers.logit import get_logger
8
- logger = get_logger("async", filename="async.log")
9
-
10
-
11
- REDIS_CON_POOL = None
12
-
13
-
14
- def getRedisClient():
15
- global REDIS_CON_POOL
16
- if REDIS_CON_POOL is None:
17
- REDIS_CON_POOL = ConnectionPool(**settings.WS4REDIS_CONNECTION)
18
- return StrictRedis(connection_pool=REDIS_CON_POOL)
19
-
20
-
21
- # Function to check the number of connections in use and available
22
- def getPoolStatus():
23
- status = objict()
24
- if REDIS_CON_POOL is None:
25
- return status
26
- status.max_size = REDIS_CON_POOL.max_connections
27
- status.size = REDIS_CON_POOL._created_connections
28
- status.in_use = len(REDIS_CON_POOL._in_use_connections)
29
- status.available = len(REDIS_CON_POOL._available_connections)
30
- return status
31
-
32
-
33
- class RedisMessage(bytes):
34
- def __new__(cls, value):
35
- if isinstance(value, str):
36
- if value != settings.WS4REDIS_HEARTBEAT:
37
- return bytes(value, 'utf-8')
38
- elif isinstance(value, list):
39
- if len(value) >= 2 and value[0] == b'message':
40
- return value[2]
41
- elif isinstance(value, dict):
42
- if not hasattr(value, "toJSON"):
43
- value = objict(value)
44
- return bytes(value.toJSON(as_string=True), 'utf-8')
45
- elif isinstance(value, bytes):
46
- return value
47
- return None
48
-
49
-
50
- class RedisStore():
51
- def __init__(self, connection=None):
52
- self.connection = connection
53
- if self.connection is None:
54
- self.connection = getRedisClient()
55
- self.subscriptions = []
56
- self.pubsub = None
57
- self.online_pk = None
58
- self.online_channel = None
59
- self.only_one = False # don't count connections, only allows one instance
60
- self.expire = settings.WS4REDIS_EXPIRE
61
-
62
- def publish(self, message, channel, facility="events", pk=None, expire=None, prefix=settings.WS4REDIS_PREFIX):
63
- if expire is None:
64
- expire = self.expire
65
- if not isinstance(message, RedisMessage):
66
- message = RedisMessage(message)
67
-
68
- if not isinstance(message, bytes):
69
- raise ValueError('message is {} but should be bytes'.format(type(message)))
70
-
71
- if isinstance(pk, list):
72
- count = 0
73
- for spk in pk:
74
- count += self.publish(message, channel, facility, pk=spk, expire=expire, prefix=prefix)
75
- return count
76
-
77
- channel_key = self.channelToKey(channel, facility, pk, prefix)
78
- if settings.WS4REDIS_LOG_DEBUG:
79
- logger.info("publishing msg to: {0}".format(channel_key), message)
80
- count = self.connection.publish(channel_key, message)
81
- return count
82
-
83
- def getSubMessage(self):
84
- # get a message pending from subscription
85
- if self.pubsub:
86
- return self.pubsub.parse_response()
87
- return None
88
-
89
- def getPendingMessage(self, channel, facility="events", pk=None, prefix=settings.WS4REDIS_PREFIX):
90
- # get a message from the connection channel
91
- channel_key = self.channelToKey(channel, facility, pk, prefix)
92
- return self.connection.get(channel_key)
93
-
94
- def channelToKey(self, channel, facility="events", pk=None, prefix=settings.WS4REDIS_PREFIX):
95
- if not pk:
96
- key = F'{prefix}:{channel}:{facility}'
97
- else:
98
- key = F'{prefix}:{channel}:{pk}:{facility}'
99
- return key
100
-
101
- def subscribe(self, channel, facility="events", pk=None, prefix=settings.WS4REDIS_PREFIX):
102
- if self.pubsub is None:
103
- self.pubsub = self.connection.pubsub()
104
- key = self.channelToKey(channel, facility, pk, prefix)
105
- if key not in self.subscriptions:
106
- if settings.WS4REDIS_LOG_DEBUG:
107
- logger.info(F"subscribing to: {key}")
108
- self.subscriptions.append(key)
109
- self.pubsub.subscribe(key)
110
- return key
111
-
112
- def unsubscribe(self, channel, facility, pk=None, prefix=settings.WS4REDIS_PREFIX):
113
- key = self.channelToKey(channel, facility, pk, prefix)
114
- if key in self.subscriptions:
115
- if settings.WS4REDIS_LOG_DEBUG:
116
- logger.info(F"unsubscribing to: {key}")
117
- self.subscriptions.remove(key)
118
- self.pubsub.unsubscribe(key)
119
-
120
- def publishModelOnline(self, name, pk, only_one=False):
121
- if self.online_pk is None:
122
- self.online_pk = pk
123
- self.online_channel = name
124
- self.only_one = only_one
125
- if self.only_one:
126
- self.connection.sadd(F"{name}:online", pk)
127
- else:
128
- count = self.connection.hincrby(F"{name}:online:connections", pk, 1)
129
- if count == 1:
130
- self.connection.sadd(F"{name}:online", pk)
131
-
132
- def unpublishModelOnline(self):
133
- if self.online_pk:
134
- name = self.online_channel
135
- pk = self.online_pk
136
- self.online_channel = None
137
- self.online_pk = None
138
- if self.only_one:
139
- self.connection.srem(F"{name}:online", pk)
140
- else:
141
- count = self.connection.hincrby(F"{name}:online:connections", pk, -1)
142
- if count == 0:
143
- self.connection.srem(F"{name}:online", pk)
144
-
145
- def waitForMessage(self, muid=None, timeout=55):
146
- timeout_at = time.time() + timeout
147
- while time.time() < timeout_at:
148
- message = self.pubsub.get_message()
149
- if message is not None and message.get("type") == "message":
150
- imsg = objict.from_json(message.get("data"))
151
- if muid is not None and imsg.muid == muid:
152
- return imsg
153
- elif muid is None:
154
- return imsg
155
- time.sleep(1.0)
156
- return None
157
-
158
- def get_file_descriptor(self):
159
- """
160
- Returns the file descriptor used for passing to the select call when listening
161
- on the message queue.
162
- """
163
- if self.pubsub.connection:
164
- return self.pubsub.connection._sock.fileno()
165
- return None
166
-
167
- def release(self):
168
- """
169
- New implementation to free up Redis subscriptions when websockets close. This prevents
170
- memory sap when Redis Output Buffer and Output Lists build when websockets are abandoned.
171
- """
172
- self.unpublishModelOnline()
173
- if self.pubsub and self.pubsub.subscribed:
174
- self.pubsub.unsubscribe()
175
- self.pubsub.reset()
176
- self.connection = None
177
-
178
- def __enter__(self):
179
- return self
180
-
181
- def __exit__(self, exc_type, exc_value, traceback):
182
- self.release()
183
- return False
@@ -1,86 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- import sys
3
- from redis import StrictRedis
4
- from http import client as http_client
5
- from mojo.ws4redis import settings as private_settings
6
- from mojo.ws4redis.exceptions import WebSocketError, HandshakeError, UpgradeRequiredError, SSLRequiredError
7
- from mojo.ws4redis.connection import WebsocketConnection
8
-
9
- from django.core.exceptions import PermissionDenied
10
- from django.utils.encoding import force_str
11
- from django import http
12
-
13
- from mojo.helpers.logit import get_logger
14
- logger = get_logger("async", filename="async.log")
15
-
16
-
17
- class WebsocketServerBase(object):
18
- def __init__(self, redis_connection=None):
19
- """
20
- redis_connection can be overriden by a mock object.
21
- """
22
- self._websockets = set() # a list of currently active websockets
23
- self._redis_connection = redis_connection
24
- if redis_connection is None:
25
- self._redis_connection = StrictRedis(**private_settings.WS4REDIS_CONNECTION)
26
-
27
- # clear out all online user connections from redis
28
- self._redis_connection.delete("users:online:connections")
29
- self._redis_connection.delete("users:online")
30
-
31
- def assure_protocol_requirements(self, environ):
32
- if environ.get('REQUEST_METHOD') != 'GET':
33
- raise HandshakeError('HTTP method must be a GET')
34
-
35
- if environ.get('SERVER_PROTOCOL') != 'HTTP/1.1':
36
- raise HandshakeError('HTTP server protocol must be 1.1')
37
-
38
- if environ.get('HTTP_UPGRADE', '').lower() != 'websocket':
39
- raise HandshakeError('Client does not wish to upgrade to a websocket')
40
-
41
- @property
42
- def websockets(self):
43
- return self._websockets
44
-
45
- def __call__(self, environ, start_response):
46
- """ Hijack the main loop from the original thread and listen on events on Redis and Websockets"""
47
- connection = None
48
- try:
49
- self.assure_protocol_requirements(environ)
50
- connection = WebsocketConnection(self, environ, start_response)
51
- connection.handleComs()
52
- except WebSocketError:
53
- logger.exception()
54
- response = http.HttpResponse(status=1001, content='Websocket Closed')
55
- except UpgradeRequiredError as excpt:
56
- logger.exception()
57
- response = http.HttpResponseBadRequest(status=426, content=excpt)
58
- except HandshakeError as excpt:
59
- logger.exception()
60
- response = http.HttpResponseBadRequest(content=excpt)
61
- except PermissionDenied as excpt:
62
- logger.exception("PermissionDenied")
63
- logger.warning('PermissionDenied: {}'.format(excpt), exc_info=sys.exc_info())
64
- response = http.HttpResponseForbidden(content=excpt)
65
- except SSLRequiredError as excpt:
66
- logger.exception("SSLRequiredError")
67
- response = http.HttpResponseServerError(content=excpt)
68
- except Exception as excpt:
69
- logger.exception()
70
- response = http.HttpResponseServerError(content=excpt)
71
- else:
72
- response = http.HttpResponse()
73
- finally:
74
- logger.info("closing websocket")
75
- if connection:
76
- connection.release()
77
- else:
78
- logger.warning('Starting late response on websocket')
79
- status_text = http_client.responses.get(response.status_code, 'UNKNOWN STATUS CODE')
80
- status = '{0} {1}'.format(response.status_code, status_text)
81
- # headers = list(response._headers.values())
82
- # if six.PY3:
83
- # headers = list(headers)
84
- start_response(force_str(status), [])
85
- logger.info('Finish non-websocket response with status code: {}'.format(response.status_code))
86
- return response
@@ -1,171 +0,0 @@
1
- #-*- coding: utf-8 -*-
2
- import base64
3
- import select
4
- from hashlib import sha1
5
- from wsgiref import util
6
- from django.core.handlers import wsgi as django_wsgi
7
- from django.core.wsgi import get_wsgi_application
8
- from django.core.servers.basehttp import WSGIServer, WSGIRequestHandler, ServerHandler
9
-
10
- from django.conf import settings
11
- from django.core.management.commands import runserver
12
- import socketserver
13
- from django.utils.encoding import force_str
14
- from mojo.ws4redis.websocket import WebSocket
15
- from mojo.ws4redis.servers.base import WebsocketServerBase, HandshakeError, UpgradeRequiredError
16
-
17
- from io import IOBase
18
-
19
-
20
- from mojo.helpers.logit import get_logger
21
- logger = get_logger("async", filename="async.log")
22
-
23
- logger.info("YES")
24
-
25
- util._hoppish = {}.__contains__
26
-
27
-
28
- class LimitedStreamPatched(IOBase):
29
- """
30
- Wrap another stream to disallow reading it past a number of bytes.
31
-
32
- Based on the implementation from werkzeug.wsgi.LimitedStream
33
- See https://github.com/pallets/werkzeug/blob/dbf78f67/src/werkzeug/wsgi.py#L828
34
- """
35
-
36
- def __init__(self, stream, limit):
37
- self.stream = stream
38
- self._read = stream.read
39
- self._readline = stream.readline
40
- self._pos = 0
41
- self.limit = limit
42
-
43
- def read(self, size=-1, /):
44
- _pos = self._pos
45
- limit = self.limit
46
- if _pos >= limit:
47
- return b""
48
- if size == -1 or size is None:
49
- size = limit - _pos
50
- else:
51
- size = min(size, limit - _pos)
52
- data = self._read(size)
53
- self._pos += len(data)
54
- return data
55
-
56
- def readline(self, size=-1, /):
57
- _pos = self._pos
58
- limit = self.limit
59
- if _pos >= limit:
60
- return b""
61
- if size == -1 or size is None:
62
- size = limit - _pos
63
- else:
64
- size = min(size, limit - _pos)
65
- line = self._readline(size)
66
- self._pos += len(line)
67
- return line
68
-
69
-
70
- def patchLimitedStream():
71
- django_wsgi.LimitedStream = LimitedStreamPatched
72
-
73
-
74
- class WSGIRequestHandlerRunServer(WSGIRequestHandler):
75
- def handle(self):
76
- self.raw_requestline = self.rfile.readline(65537)
77
- if len(self.raw_requestline) > 65536:
78
- self.requestline = ''
79
- self.request_version = ''
80
- self.command = ''
81
- self.send_error(414)
82
- return
83
-
84
- if not self.parse_request(): # An error code has been sent, just exit
85
- return
86
-
87
- handler = ServerHandler(
88
- self.rfile, self.wfile, self.get_stderr(), self.get_environ()
89
- )
90
- handler.request_handler = self # backpointer for logging
91
- handler.http_version = '1.1'
92
- handler.run(self.server.get_app())
93
-
94
-
95
- class WebsocketRunServer(WebsocketServerBase):
96
- WS_GUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
97
- WS_VERSIONS = ('13', '8', '7')
98
-
99
- def upgrade_websocket(self, environ, start_response):
100
- """
101
- Attempt to upgrade the socket environ['wsgi.input'] into a websocket enabled connection.
102
- """
103
- websocket_version = environ.get('HTTP_SEC_WEBSOCKET_VERSION', '')
104
- if not websocket_version:
105
- raise UpgradeRequiredError
106
- elif websocket_version not in self.WS_VERSIONS:
107
- raise HandshakeError('Unsupported WebSocket Version: {0}'.format(websocket_version))
108
-
109
- key = environ.get('HTTP_SEC_WEBSOCKET_KEY', '').strip()
110
- if not key:
111
- raise HandshakeError('Sec-WebSocket-Key header is missing/empty')
112
- try:
113
- key_len = len(base64.b64decode(key))
114
- except TypeError:
115
- raise HandshakeError('Invalid key: {0}'.format(key))
116
- if key_len != 16:
117
- # 5.2.1 (3)
118
- raise HandshakeError('Invalid key: {0}'.format(key))
119
-
120
- sec_ws_accept = base64.b64encode(sha1(key.encode('utf-8') + self.WS_GUID).digest())
121
- sec_ws_accept = sec_ws_accept.decode('ascii')
122
- headers = [
123
- ('Upgrade', 'websocket'),
124
- ('Connection', 'Upgrade'),
125
- ('Sec-WebSocket-Accept', sec_ws_accept),
126
- ('Sec-WebSocket-Version', str(websocket_version)),
127
- ]
128
- if environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL') is not None:
129
- headers.append(('Sec-WebSocket-Protocol', environ.get('HTTP_SEC_WEBSOCKET_PROTOCOL')))
130
-
131
- logger.debug('WebSocket request accepted, switching protocols')
132
- start_response(force_str('101 Switching Protocols'), headers)
133
- start_response.__self__.finish_content()
134
- winput = environ['wsgi.input']
135
- if not hasattr(winput, "stream"):
136
- # hack to get the stream back in later DJANGO
137
- winput.stream = winput._read.__self__
138
- return WebSocket(winput.stream)
139
-
140
- def select(self, rlist, wlist, xlist, timeout=None):
141
- return select.select(rlist, wlist, xlist, timeout)
142
-
143
-
144
- def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=None, **kwargs):
145
- """
146
- Function to monkey patch the internal Django command: manage.py runserver
147
- """
148
- server_address = (addr, port)
149
- logger.info('Websocket support is enabled', server_address)
150
- if not threading:
151
- raise Exception("Django's Websocket server must run with threading enabled")
152
- httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, WSGIServer), {'daemon_threads': True})
153
- httpd = httpd_cls(server_address, WSGIRequestHandlerRunServer, ipv6=ipv6)
154
- httpd.set_app(wsgi_handler)
155
- httpd.serve_forever()
156
-
157
-
158
- patchLimitedStream()
159
-
160
- runserver.run = run
161
-
162
- _django_app = get_wsgi_application()
163
- _websocket_app = WebsocketRunServer()
164
- _websocket_url = getattr(settings, 'WEBSOCKET_URL')
165
-
166
-
167
- def application(environ, start_response):
168
- if _websocket_url and environ.get('PATH_INFO').startswith(_websocket_url):
169
- # logger.info("environ", environ)
170
- return _websocket_app(environ, start_response)
171
- return _django_app(environ, start_response)
@@ -1,63 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- import uwsgi
3
- import gevent.select
4
-
5
- import django
6
- django.setup()
7
-
8
- from mojo.ws4redis.exceptions import WebSocketError, SSLRequiredError
9
- from mojo.ws4redis.servers.base import WebsocketServerBase
10
-
11
-
12
- class uWSGIWebsocket(object):
13
- def __init__(self):
14
- self._closed = False
15
-
16
- def get_file_descriptor(self):
17
- """Return the file descriptor for the given websocket"""
18
- try:
19
- return uwsgi.connection_fd()
20
- except IOError as e:
21
- self.close()
22
- raise WebSocketError(e)
23
-
24
- @property
25
- def closed(self):
26
- return self._closed
27
-
28
- def receive(self):
29
- if self._closed:
30
- raise WebSocketError("Connection is already closed")
31
- try:
32
- return uwsgi.websocket_recv_nb()
33
- except IOError as e:
34
- self.close()
35
- raise WebSocketError(e)
36
-
37
- def flush(self):
38
- try:
39
- uwsgi.websocket_recv_nb()
40
- except IOError:
41
- self.close()
42
-
43
- def send(self, message, binary=None):
44
- try:
45
- uwsgi.websocket_send(message)
46
- except IOError as e:
47
- self.close()
48
- raise WebSocketError(e)
49
-
50
- def close(self, code=1000, message=''):
51
- self._closed = True
52
-
53
-
54
- class uWSGIWebsocketServer(WebsocketServerBase):
55
- def upgrade_websocket(self, environ, start_response):
56
- try:
57
- uwsgi.websocket_handshake(environ['HTTP_SEC_WEBSOCKET_KEY'], environ.get('HTTP_ORIGIN', ''))
58
- except Exception:
59
- raise SSLRequiredError("UWSGI REQUIRES SSL SUPPORT!")
60
- return uWSGIWebsocket()
61
-
62
- def select(self, rlist, wlist, xlist, timeout=None):
63
- return gevent.select.select(rlist, wlist, xlist, timeout)
mojo/ws4redis/settings.py DELETED
@@ -1,45 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from django.conf import settings
3
-
4
- WEBSOCKET_URL = getattr(settings, 'WEBSOCKET_URL', '/ws/')
5
-
6
- WS4REDIS_CONNECTION = getattr(settings, 'WS4REDIS_CONNECTION', {
7
- 'host': 'localhost',
8
- 'port': 6379,
9
- 'db': 0,
10
- 'password': None,
11
- })
12
-
13
- """
14
- A string to prefix elements in the Redis datastore, to avoid naming conflicts with other services.
15
- """
16
- WS4REDIS_PREFIX = getattr(settings, 'WS4REDIS_PREFIX', None)
17
-
18
- """
19
- The time in seconds, items shall be persisted by the Redis datastore.
20
- """
21
- WS4REDIS_EXPIRE = getattr(settings, 'WS4REDIS_EXPIRE', 3600)
22
-
23
- """
24
- Replace the subscriber class by a customized version.
25
- """
26
- WS4REDIS_SUBSCRIBER = getattr(settings, 'WS4REDIS_SUBSCRIBER', 'ws4redis.subscriber.RedisSubscriber')
27
-
28
- """
29
- This set the magic string to recognize heartbeat messages. If set, this message string is ignored
30
- by the server and also shall be ignored on the client.
31
-
32
- If WS4REDIS_HEARTBEAT is not None, the server sends at least every 4 seconds a heartbeat message.
33
- It is then up to the client to decide, what to do with these messages.
34
- """
35
- WS4REDIS_HEARTBEAT = getattr(settings, 'WS4REDIS_HEARTBEAT', None)
36
-
37
- # by default we only allow the "events" facillity
38
- WS4REDIS_FACILITIES = getattr(settings, "WS4REDIS_FACILITIES", ["events"])
39
- WS4REDIS_CHANNELS = getattr(settings, "WS4REDIS_CHANNELS", {})
40
- WS4REDIS_AUTHENTICATORS = getattr(settings, "WS4REDIS_AUTHENTICATORS", {})
41
-
42
- URL_AUTHENTICATOR = getattr(settings, "URL_AUTHENTICATOR", None)
43
-
44
- WS4REDIS_LOG_DEBUG = getattr(settings, 'WS4REDIS_LOG_DEBUG', False)
45
- WS4REDIS_NOAUTH_CLOSE = getattr(settings, 'WS4REDIS_NOAUTH_CLOSE', False)
@@ -1,128 +0,0 @@
1
- ###############################################################################
2
- ##
3
- ## Copyright 2011-2013 Tavendo GmbH
4
- ##
5
- ## Note:
6
- ##
7
- ## This code is a Python implementation of the algorithm
8
- ##
9
- ## "Flexible and Economical UTF-8 Decoder"
10
- ##
11
- ## by Bjoern Hoehrmann
12
- ##
13
- ## bjoern@hoehrmann.de
14
- ## http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
15
- ##
16
- ## Licensed under the Apache License, Version 2.0 (the "License");
17
- ## you may not use this file except in compliance with the License.
18
- ## You may obtain a copy of the License at
19
- ##
20
- ## http://www.apache.org/licenses/LICENSE-2.0
21
- ##
22
- ## Unless required by applicable law or agreed to in writing, software
23
- ## distributed under the License is distributed on an "AS IS" BASIS,
24
- ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
25
- ## See the License for the specific language governing permissions and
26
- ## limitations under the License.
27
- ##
28
- ###############################################################################
29
-
30
-
31
- ## use Cython implementation of UTF8 validator if available
32
- ##
33
- try:
34
- from wsaccel.utf8validator import Utf8Validator
35
- except:
36
- ## fallback to pure Python implementation
37
-
38
- class Utf8Validator:
39
- """
40
- Incremental UTF-8 validator with constant memory consumption (minimal
41
- state).
42
-
43
- Implements the algorithm "Flexible and Economical UTF-8 Decoder" by
44
- Bjoern Hoehrmann (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/).
45
- """
46
-
47
- ## DFA transitions
48
- UTF8VALIDATOR_DFA = [
49
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 00..1f
50
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 20..3f
51
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 40..5f
52
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 60..7f
53
- 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, # 80..9f
54
- 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, # a0..bf
55
- 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, # c0..df
56
- 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, # e0..ef
57
- 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, # f0..ff
58
- 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, # s0..s0
59
- 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, # s1..s2
60
- 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, # s3..s4
61
- 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, # s5..s6
62
- 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, # s7..s8
63
- ]
64
-
65
- UTF8_ACCEPT = 0
66
- UTF8_REJECT = 1
67
-
68
- def __init__(self):
69
- self.reset()
70
-
71
- def decode(self, b):
72
- """
73
- Eat one UTF-8 octet, and validate on the fly.
74
-
75
- Returns UTF8_ACCEPT when enough octets have been consumed, in which case
76
- self.codepoint contains the decoded Unicode code point.
77
-
78
- Returns UTF8_REJECT when invalid UTF-8 was encountered.
79
-
80
- Returns some other positive integer when more octets need to be eaten.
81
- """
82
- type = Utf8Validator.UTF8VALIDATOR_DFA[b]
83
-
84
- if self.state != Utf8Validator.UTF8_ACCEPT:
85
- self.codepoint = (b & 0x3f) | (self.codepoint << 6)
86
- else:
87
- self.codepoint = (0xff >> type) & b
88
-
89
- self.state = Utf8Validator.UTF8VALIDATOR_DFA[256 + self.state * 16 + type]
90
-
91
- return self.state
92
-
93
- def reset(self):
94
- """
95
- Reset validator to start new incremental UTF-8 decode/validation.
96
- """
97
- self.state = Utf8Validator.UTF8_ACCEPT
98
- self.codepoint = 0
99
- self.i = 0
100
-
101
- def validate(self, ba):
102
- """
103
- Incrementally validate a chunk of bytes provided as string.
104
-
105
- Will return a quad (valid?, endsOnCodePoint?, currentIndex, totalIndex).
106
-
107
- As soon as an octet is encountered which renders the octet sequence
108
- invalid, a quad with valid? == False is returned. currentIndex returns
109
- the index within the currently consumed chunk, and totalIndex the
110
- index within the total consumed sequence that was the point of bail out.
111
- When valid? == True, currentIndex will be len(ba) and totalIndex the
112
- total amount of consumed bytes.
113
- """
114
-
115
- l = len(ba)
116
-
117
- for i in range(l):
118
- ## optimized version of decode(), since we are not interested in actual code points
119
-
120
- self.state = Utf8Validator.UTF8VALIDATOR_DFA[256 + (self.state << 4) + Utf8Validator.UTF8VALIDATOR_DFA[ord(ba[i])]]
121
-
122
- if self.state == Utf8Validator.UTF8_REJECT:
123
- self.i += i
124
- return False, False, i, self.i
125
-
126
- self.i += l
127
-
128
- return True, self.state == Utf8Validator.UTF8_ACCEPT, l, self.i