vortexpy 4.0.23__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 (87) hide show
  1. vortex/DataWrapTuple.py +27 -0
  2. vortex/DeferUtil.py +224 -0
  3. vortex/Jsonable.py +304 -0
  4. vortex/Payload.py +115 -0
  5. vortex/PayloadEndpoint.py +216 -0
  6. vortex/PayloadEndpointTest.py +164 -0
  7. vortex/PayloadEnvelope.py +166 -0
  8. vortex/PayloadFilterKeys.py +15 -0
  9. vortex/PayloadIO.py +152 -0
  10. vortex/PayloadPriority.py +11 -0
  11. vortex/PayloadResponse.py +173 -0
  12. vortex/PayloadResponseTest.py +39 -0
  13. vortex/PayloadTest.py +136 -0
  14. vortex/PerformTestActionTuple.py +30 -0
  15. vortex/SerialiseUtil.py +273 -0
  16. vortex/TestTuple.py +61 -0
  17. vortex/Tuple.py +968 -0
  18. vortex/TupleAction.py +72 -0
  19. vortex/TupleActionVortex.py +5 -0
  20. vortex/TupleDataForTest.py +25 -0
  21. vortex/TuplePerfTest.py +71 -0
  22. vortex/TuplePolymorphicTupleTypeFieldArgTest.py +74 -0
  23. vortex/TupleRestfulTest.py +140 -0
  24. vortex/TupleSelector.py +105 -0
  25. vortex/TupleTest.py +127 -0
  26. vortex/VortexABC.py +55 -0
  27. vortex/VortexClientHttp.py +308 -0
  28. vortex/VortexClientHttpTest.py +71 -0
  29. vortex/VortexClientTcp.py +355 -0
  30. vortex/VortexClientWebsocketFactory.py +383 -0
  31. vortex/VortexConnectionABC.py +88 -0
  32. vortex/VortexFactory.py +932 -0
  33. vortex/VortexPayloadProtocol.py +180 -0
  34. vortex/VortexServer.py +367 -0
  35. vortex/VortexServerConnection.py +127 -0
  36. vortex/VortexServerHttpResource.py +193 -0
  37. vortex/VortexServerTcp.py +104 -0
  38. vortex/VortexServerWebsocket.py +287 -0
  39. vortex/VortexTcpMemoryLeakPayloadTest.py +611 -0
  40. vortex/VortexTcpMemoryLeakRawTest.py +87 -0
  41. vortex/VortexTest.py +22 -0
  42. vortex/VortexUtil.py +314 -0
  43. vortex/VortexWritePushProducer.py +321 -0
  44. vortex/__init__.py +10 -0
  45. vortex/data_loader/TupleDataLiveLockManager.py +383 -0
  46. vortex/data_loader/TupleDataLoader.py +326 -0
  47. vortex/data_loader/TupleDataLoaderDelegate.py +90 -0
  48. vortex/data_loader/TupleDataLoaderTupleABC.py +10 -0
  49. vortex/data_loader/TupleDataLoaderTuples.py +53 -0
  50. vortex/data_loader/__init__.py +0 -0
  51. vortex/event_bus/TupleChangeEventABC.py +15 -0
  52. vortex/event_bus/TupleChangeEventBus.py +192 -0
  53. vortex/event_bus/TupleChangeEventBusObserverABC.py +11 -0
  54. vortex/event_bus/__init__.py +0 -0
  55. vortex/handler/TupleActionProcessor.py +270 -0
  56. vortex/handler/TupleActionProcessorProxy.py +218 -0
  57. vortex/handler/TupleActionProcessorTestHandler.py +27 -0
  58. vortex/handler/TupleDataActionClient.py +67 -0
  59. vortex/handler/TupleDataObservableCache.py +201 -0
  60. vortex/handler/TupleDataObservableHandler.py +371 -0
  61. vortex/handler/TupleDataObservableProxyHandler.py +340 -0
  62. vortex/handler/TupleDataObservableTestHandler.py +60 -0
  63. vortex/handler/TupleDataObserverClient.py +158 -0
  64. vortex/handler/VortexJSTupleLoaderTestHandler.py +42 -0
  65. vortex/handler/__init__.py +0 -0
  66. vortex/restful/GzippedDataHttpClient.py +170 -0
  67. vortex/restful/GzippedDataHttpClientTest.py +103 -0
  68. vortex/restful/GzippedPayloadHttpClient.py +181 -0
  69. vortex/restful/RestfulHttpClient.py +107 -0
  70. vortex/restful/RestfulHttpClientTest.py +60 -0
  71. vortex/restful/RestfulResource.py +153 -0
  72. vortex/restful/RestfulResourceTest.py +100 -0
  73. vortex/restful/__init__.py +0 -0
  74. vortex/rpc/RPC.py +446 -0
  75. vortex/rpc/RPCTest.py +12 -0
  76. vortex/rpc/__init__.py +0 -0
  77. vortex/sqla_orm/OrmCrudHandler.py +431 -0
  78. vortex/sqla_orm/TupleProviderForDb.py +42 -0
  79. vortex/sqla_orm/VortexTupleJson.py +24 -0
  80. vortex/sqla_orm/__init__.py +0 -0
  81. vortex/storage/TupleStorageSqlite.py +359 -0
  82. vortex/storage/__init__.py +0 -0
  83. vortexpy-4.0.23.dist-info/LICENSE +21 -0
  84. vortexpy-4.0.23.dist-info/METADATA +43 -0
  85. vortexpy-4.0.23.dist-info/RECORD +87 -0
  86. vortexpy-4.0.23.dist-info/WHEEL +5 -0
  87. vortexpy-4.0.23.dist-info/top_level.txt +1 -0
@@ -0,0 +1,383 @@
1
+ """
2
+ * Created by Synerty Pty Ltd
3
+ *
4
+ * This software is open source, the MIT license applies.
5
+ *
6
+ * Website : http://www.synerty.com
7
+ * Support : support@synerty.com
8
+ """
9
+
10
+ import logging
11
+ import uuid
12
+ from collections import deque, namedtuple
13
+ from datetime import datetime
14
+ from typing import Union, Optional, List, Deque
15
+ from urllib.parse import urlencode, urlparse, urlunparse
16
+
17
+ import pytz
18
+ import twisted
19
+ from autobahn.twisted.websocket import (
20
+ WebSocketClientProtocol,
21
+ WebSocketClientFactory,
22
+ connectWS,
23
+ )
24
+ from twisted.internet import reactor
25
+ from twisted.internet import task
26
+ from twisted.internet.defer import Deferred, inlineCallbacks
27
+ from twisted.internet.protocol import connectionDone, ReconnectingClientFactory
28
+
29
+ from vortex.DeferUtil import isMainThread, vortexLogFailure, nonConcurrentMethod
30
+ from vortex.PayloadEnvelope import PayloadEnvelope, VortexMsgList
31
+ from vortex.PayloadPriority import DEFAULT_PRIORITY
32
+ from vortex.VortexABC import VortexABC, VortexInfo
33
+ from vortex.VortexPayloadProtocol import VortexPayloadProtocol
34
+ from vortex.VortexServer import HEART_BEAT_PERIOD
35
+ from vortex.VortexServer import HEART_BEAT_TIMEOUT
36
+ from vortex.VortexUtil import logLargeMessages
37
+
38
+ logger = logging.getLogger(name=__name__)
39
+
40
+ VortexMsgToSend = namedtuple("VortexMsgToSend", ["deferred", "vortexMsgs"])
41
+
42
+
43
+ class VortexPayloadWebsocketClientProtocol(
44
+ WebSocketClientProtocol, VortexPayloadProtocol
45
+ ):
46
+ def __init__(self, vortexClient: "VortexClientWebsocketFactory"):
47
+ WebSocketClientProtocol.__init__(self)
48
+ VortexPayloadProtocol.__init__(self, logger)
49
+ self._vortexClient = vortexClient
50
+
51
+ self._vortexClientConnectionMade()
52
+
53
+ def _vortexClientConnectionMade(self):
54
+ self._closed = False
55
+
56
+ # Start our heart beat
57
+ self._sendBeatLoopingCall = task.LoopingCall(self._sendBeat)
58
+ d = self._sendBeatLoopingCall.start(HEART_BEAT_PERIOD, now=False)
59
+ d.addErrback(lambda f: logger.exception(f.value))
60
+
61
+ def onMessage(self, message, isBinary):
62
+ VortexPayloadProtocol.vortexMsgReceived(self, message)
63
+
64
+ def _beat(self):
65
+ if self._vortexClient:
66
+ self._vortexClient._beat()
67
+
68
+ def _nameAndUuidReceived(self, name, uuid):
69
+ from vortex.VortexFactory import VortexFactory
70
+
71
+ # self._producer.setRemoteVortexName(name)
72
+
73
+ if self._vortexClient:
74
+ self._vortexClient._setNameAndUuid(name=name, uuid=uuid)
75
+
76
+ VortexFactory._notifyOfVortexStatusChange(name, online=True)
77
+
78
+ def _createResponseSenderCallable(self):
79
+ def sendResponse(
80
+ vortexMsgs: Union[VortexMsgList, bytes],
81
+ priority: int = DEFAULT_PRIORITY,
82
+ ):
83
+ return self._vortexClient.sendVortexMsg(
84
+ vortexMsgs=vortexMsgs, priority=priority
85
+ )
86
+
87
+ return sendResponse
88
+
89
+ def _sendBeat(self):
90
+ if self._closed:
91
+ return
92
+
93
+ # Send the heartbeats
94
+ self.sendMessage(b".")
95
+
96
+ def write(self, payloadVortexStr: bytes, _priority: int = DEFAULT_PRIORITY):
97
+ if not twisted.python.threadable.isInIOThread():
98
+ e = Exception("Write called from NON main thread")
99
+ logger.exception(str(e))
100
+ raise e
101
+
102
+ assert not self._closed
103
+ self.sendMessage(payloadVortexStr)
104
+
105
+ def onConnect(self, response):
106
+ logger.info(f"Connected to {response.peer}")
107
+
108
+ self._vortexClientConnectionMade()
109
+
110
+ # Send a heart beat down the new connection, tell it who we are.
111
+ connectPayloadFilt = {
112
+ PayloadEnvelope.vortexUuidKey: self._vortexClient.uuid,
113
+ PayloadEnvelope.vortexNameKey: self._vortexClient.name,
114
+ }
115
+ self.write(PayloadEnvelope(filt=connectPayloadFilt).toVortexMsg())
116
+
117
+ def connectionLost(self, reason=connectionDone):
118
+ from vortex.VortexFactory import VortexFactory
119
+
120
+ VortexFactory._notifyOfVortexStatusChange(
121
+ self._serverVortexName, online=False
122
+ )
123
+
124
+ if self._sendBeatLoopingCall.running:
125
+ self._sendBeatLoopingCall.stop()
126
+ self._closed = True
127
+
128
+ def close(self):
129
+ self.transport.loseConnection()
130
+ if self._sendBeatLoopingCall.running:
131
+ self._sendBeatLoopingCall.stop()
132
+ self._closed = True
133
+
134
+ @property
135
+ def closed(self) -> bool:
136
+ return self._closed
137
+
138
+
139
+ class VortexClientWebsocketFactory(
140
+ WebSocketClientFactory, ReconnectingClientFactory, VortexABC
141
+ ):
142
+ maxDelay = 1.0
143
+
144
+ def __init__(self, name: str, *args, **kwargs):
145
+
146
+ ReconnectingClientFactory.__init__(self)
147
+
148
+ self._vortexName = name
149
+ self._vortexUuid = str(uuid.uuid4())
150
+
151
+ if "url" in kwargs:
152
+ params = dict(
153
+ vortexName=self._vortexName, vortexUuid=self._vortexUuid
154
+ )
155
+
156
+ url_parts = list(urlparse(kwargs["url"]))
157
+ query = dict(url_parts[4])
158
+ query.update(params)
159
+
160
+ url_parts[4] = urlencode(query)
161
+ url = urlunparse(url_parts)
162
+ kwargs["url"] = url
163
+
164
+ WebSocketClientFactory.__init__(self, *args, **kwargs)
165
+ self.setProtocolOptions(utf8validateIncoming=False)
166
+
167
+ self._server = None
168
+ self._port = None
169
+
170
+ self._retrying = False
171
+
172
+ self._serverVortexName = None
173
+ self._serverVortexUuid = None
174
+
175
+ self._lastBeatReceiveTime = None
176
+ self._lastHeartBeatCheckTime = datetime.now(pytz.utc)
177
+
178
+ # Start our heart beat checker
179
+ self._beatLoopingCall = task.LoopingCall(self._checkBeat)
180
+
181
+ self._reconnectVortexMsgs = [PayloadEnvelope().toVortexMsg()]
182
+
183
+ # Buffer messages while the reconnecting factory is reconnecting
184
+ # but only in that state, if the vortex times out, we errback alll these
185
+ self._pendingMessages: Deque[VortexMsgToSend] = deque()
186
+
187
+ self.__protocol = None
188
+
189
+ @property
190
+ def name(self):
191
+ return self._vortexName
192
+
193
+ @property
194
+ def uuid(self):
195
+ return self._vortexUuid
196
+
197
+ @property
198
+ def requiresBase64Encoding(self) -> bool:
199
+ return False
200
+
201
+ @property
202
+ def localVortexInfo(self) -> VortexInfo:
203
+ return VortexInfo(name=self._vortexName, uuid=self._vortexUuid)
204
+
205
+ @property
206
+ def remoteVortexInfo(self) -> List[VortexInfo]:
207
+ if not self.__protocol:
208
+ return []
209
+
210
+ if not self._serverVortexUuid:
211
+ return []
212
+
213
+ return [
214
+ VortexInfo(name=self._serverVortexName, uuid=self._serverVortexUuid)
215
+ ]
216
+
217
+ def buildProtocol(self, addr) -> VortexPayloadWebsocketClientProtocol:
218
+ logger.debug(f"Building protocol for {addr}")
219
+ self._close()
220
+
221
+ # Reset the times in the ReconnectingClientFactory
222
+ self.resetDelay()
223
+
224
+ self.__protocol = VortexPayloadWebsocketClientProtocol(self)
225
+ self.__protocol.factory = self
226
+ return self.__protocol
227
+
228
+ def clientConnectionLost(self, connector, reason):
229
+ logger.debug("Lost connection, Reason: %s", reason.value)
230
+ logger.info("Lost connection, Trying to reconnect.")
231
+ ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
232
+
233
+ def clientConnectionFailed(self, connector, reason):
234
+ logger.debug("Connection failed. Reason: %s", reason.value)
235
+ logger.info("Connection failed, Trying to reconnect.")
236
+ ReconnectingClientFactory.clientConnectionFailed(
237
+ self, connector, reason
238
+ )
239
+
240
+ @inlineCallbacks
241
+ def connect(self, server, port, sslContextFactory):
242
+ self._server = server
243
+ self._port = port
244
+ self._sslContextFactory = sslContextFactory
245
+
246
+ self._beat()
247
+ d = self._beatLoopingCall.start(HEART_BEAT_PERIOD, now=False)
248
+ d.addErrback(vortexLogFailure, logger, consumeError=True)
249
+ deferred = Deferred()
250
+
251
+ yield connectWS(self, sslContextFactory)
252
+
253
+ def checkUuid():
254
+ if self._serverVortexName:
255
+ deferred.callback(True)
256
+ else:
257
+ reactor.callLater(0.1, checkUuid)
258
+
259
+ checkUuid()
260
+
261
+ return (yield deferred)
262
+
263
+ def close(self):
264
+ if self._beatLoopingCall and self._beatLoopingCall.running:
265
+ self._beatLoopingCall.stop()
266
+ self._beatLoopingCall = None
267
+
268
+ # Stop the ReconnectingClientFactory from trying to reconnect
269
+ self.stopTrying()
270
+ self._close()
271
+
272
+ def addReconnectVortexMsg(self, vortexMsg: bytes):
273
+ """Add Reconnect Payload
274
+
275
+ :param vortexMsg: An encoded PayloadEnvelope to send when the connection reconnects
276
+ :return:
277
+ """
278
+ self._reconnectVortexMsgs.append(vortexMsg)
279
+
280
+ def sendVortexMsg(
281
+ self,
282
+ vortexMsgs: Union[VortexMsgList, bytes, None] = None,
283
+ vortexUuid: Optional[str] = None,
284
+ priority: int = DEFAULT_PRIORITY,
285
+ ) -> Deferred:
286
+ """Send Vortex Msg
287
+
288
+ NOTE: Priority ins't supported as there is no buffer for this class.
289
+
290
+ """
291
+ if not self.__protocol:
292
+ from vortex.VortexFactory import NoVortexException
293
+
294
+ raise NoVortexException("The vortex is not yet connected")
295
+
296
+ if vortexMsgs is None:
297
+ vortexMsgs = self._reconnectVortexMsgs
298
+
299
+ if not isinstance(vortexMsgs, list):
300
+ vortexMsgs = [vortexMsgs]
301
+
302
+ logLargeMessages(logger, vortexMsgs, self._serverVortexUuid)
303
+
304
+ if isMainThread():
305
+ return self._sendVortexMsgLater(vortexMsgs)
306
+
307
+ return task.deferLater(reactor, 0, self._sendVortexMsgLater, vortexMsgs)
308
+
309
+ @inlineCallbacks
310
+ def _sendVortexMsgLater(self, vortexMsgs: VortexMsgList):
311
+ yield None
312
+
313
+ if not self.__protocol:
314
+ from vortex.VortexFactory import NoVortexException
315
+
316
+ raise NoVortexException("The vortex is not yet connected")
317
+
318
+ assert self._server
319
+ assert vortexMsgs
320
+
321
+ # If this transport requires base64 encoding
322
+ if self.requiresBase64Encoding:
323
+ for index, vortexMsg in enumerate(vortexMsgs):
324
+ if vortexMsg.startswith(b"{"):
325
+ vortexMsg = yield PayloadEnvelope.base64EncodeDefer(
326
+ vortexMsg
327
+ )
328
+ vortexMsgs[index] = vortexMsg + b"."
329
+
330
+ for vortexMsg in vortexMsgs:
331
+ self.__protocol.write(vortexMsg)
332
+
333
+ return True
334
+
335
+ def _beat(self):
336
+ """Beat, Called by protocol"""
337
+ self._lastBeatReceiveTime = datetime.now(pytz.utc)
338
+
339
+ def _setNameAndUuid(self, name, uuid):
340
+ """Set Name And Uuid, Called by protocol"""
341
+ self._serverVortexName = name
342
+ self._serverVortexUuid = uuid
343
+
344
+ def _checkBeat(self):
345
+ # If we've been asleep, then make note of that (VM suspended)
346
+ checkTimout = (
347
+ datetime.now(pytz.utc) - self._lastHeartBeatCheckTime
348
+ ).seconds > HEART_BEAT_TIMEOUT
349
+
350
+ # Has the heart beat expired?
351
+ beatTimeout = (
352
+ datetime.now(pytz.utc) - self._lastBeatReceiveTime
353
+ ).seconds > HEART_BEAT_TIMEOUT
354
+
355
+ # Mark that we've just checked it
356
+ self._lastHeartBeatCheckTime = datetime.now(pytz.utc)
357
+
358
+ if checkTimout:
359
+ self._lastBeatReceiveTime = datetime.now(pytz.utc)
360
+ return
361
+
362
+ if beatTimeout:
363
+ self._reconnectAfterHeartBeatLost()
364
+ return
365
+
366
+ def _reconnectAfterHeartBeatLost(self):
367
+ if self._retrying:
368
+ return
369
+
370
+ self._retrying = True
371
+
372
+ logger.info(
373
+ "VortexServer client dead, reconnecting %s:%s",
374
+ self._server,
375
+ self._port,
376
+ )
377
+
378
+ self._close()
379
+
380
+ def _close(self):
381
+ if self.__protocol:
382
+ self.__protocol.close()
383
+ self.__protocol = None
@@ -0,0 +1,88 @@
1
+ """
2
+ * Created by Synerty Pty Ltd
3
+ *
4
+ * This software is open source, the MIT license applies.
5
+ *
6
+ * Website : http://www.synerty.com
7
+ * Support : support@synerty.com
8
+ """
9
+
10
+ import logging
11
+ from abc import ABCMeta, abstractmethod
12
+ from datetime import datetime
13
+
14
+ import pytz
15
+
16
+ from .PayloadPriority import DEFAULT_PRIORITY
17
+ from .VortexServer import VortexServer
18
+
19
+ logger = logging.getLogger(name=__name__)
20
+
21
+
22
+ class VortexConnectionABC(metaclass=ABCMeta):
23
+ def __init__(
24
+ self,
25
+ logger,
26
+ vortexServer: VortexServer,
27
+ remoteVortexUuid: str,
28
+ remoteVortexName: str,
29
+ httpSessionUuid=None,
30
+ ) -> None:
31
+ self._vortexServer = vortexServer
32
+ self._logger = logger
33
+ self._remoteVortexUuid = remoteVortexUuid
34
+ self._remoteVortexName = remoteVortexName
35
+ self._closed = False
36
+ self._timedOut = False
37
+ self._httpSessionUuid = httpSessionUuid
38
+ self._connectDateTime = datetime.now(pytz.UTC)
39
+
40
+ @property
41
+ def closed(self) -> bool:
42
+ return self._closed
43
+
44
+ @property
45
+ def timedOut(self) -> bool:
46
+ return self._timedOut
47
+
48
+ @property
49
+ def connectDateTime(self) -> datetime:
50
+ return self._connectDateTime
51
+
52
+ @property
53
+ def httpSessionUuid(self):
54
+ return self._httpSessionUuid
55
+
56
+ @property
57
+ def remoteVortexUuid(self):
58
+ """Vortex UUID
59
+
60
+ The vortex UUID of the remote vortex which this connection represents.
61
+
62
+ """
63
+ return self._remoteVortexUuid
64
+
65
+ @property
66
+ def remoteVortexName(self):
67
+ """Vortex Name
68
+
69
+ The name of the remote vortex which this connection represents.
70
+
71
+ """
72
+ return self._remoteVortexName
73
+
74
+ @abstractmethod
75
+ def write(self, payloadVortexStr: bytes, priority: int = DEFAULT_PRIORITY):
76
+ """Write
77
+
78
+ EG
79
+
80
+ > assert not self._closed
81
+ > self._request.write(payloadVortexStr)
82
+ > self._request.write('.')
83
+
84
+ """
85
+
86
+ def close(self):
87
+ self._vortexServer.connectionClosed(self)
88
+ self._closed = True