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.
- vortex/DataWrapTuple.py +27 -0
- vortex/DeferUtil.py +224 -0
- vortex/Jsonable.py +304 -0
- vortex/Payload.py +115 -0
- vortex/PayloadEndpoint.py +216 -0
- vortex/PayloadEndpointTest.py +164 -0
- vortex/PayloadEnvelope.py +166 -0
- vortex/PayloadFilterKeys.py +15 -0
- vortex/PayloadIO.py +152 -0
- vortex/PayloadPriority.py +11 -0
- vortex/PayloadResponse.py +173 -0
- vortex/PayloadResponseTest.py +39 -0
- vortex/PayloadTest.py +136 -0
- vortex/PerformTestActionTuple.py +30 -0
- vortex/SerialiseUtil.py +273 -0
- vortex/TestTuple.py +61 -0
- vortex/Tuple.py +968 -0
- vortex/TupleAction.py +72 -0
- vortex/TupleActionVortex.py +5 -0
- vortex/TupleDataForTest.py +25 -0
- vortex/TuplePerfTest.py +71 -0
- vortex/TuplePolymorphicTupleTypeFieldArgTest.py +74 -0
- vortex/TupleRestfulTest.py +140 -0
- vortex/TupleSelector.py +105 -0
- vortex/TupleTest.py +127 -0
- vortex/VortexABC.py +55 -0
- vortex/VortexClientHttp.py +308 -0
- vortex/VortexClientHttpTest.py +71 -0
- vortex/VortexClientTcp.py +355 -0
- vortex/VortexClientWebsocketFactory.py +383 -0
- vortex/VortexConnectionABC.py +88 -0
- vortex/VortexFactory.py +932 -0
- vortex/VortexPayloadProtocol.py +180 -0
- vortex/VortexServer.py +367 -0
- vortex/VortexServerConnection.py +127 -0
- vortex/VortexServerHttpResource.py +193 -0
- vortex/VortexServerTcp.py +104 -0
- vortex/VortexServerWebsocket.py +287 -0
- vortex/VortexTcpMemoryLeakPayloadTest.py +611 -0
- vortex/VortexTcpMemoryLeakRawTest.py +87 -0
- vortex/VortexTest.py +22 -0
- vortex/VortexUtil.py +314 -0
- vortex/VortexWritePushProducer.py +321 -0
- vortex/__init__.py +10 -0
- vortex/data_loader/TupleDataLiveLockManager.py +383 -0
- vortex/data_loader/TupleDataLoader.py +326 -0
- vortex/data_loader/TupleDataLoaderDelegate.py +90 -0
- vortex/data_loader/TupleDataLoaderTupleABC.py +10 -0
- vortex/data_loader/TupleDataLoaderTuples.py +53 -0
- vortex/data_loader/__init__.py +0 -0
- vortex/event_bus/TupleChangeEventABC.py +15 -0
- vortex/event_bus/TupleChangeEventBus.py +192 -0
- vortex/event_bus/TupleChangeEventBusObserverABC.py +11 -0
- vortex/event_bus/__init__.py +0 -0
- vortex/handler/TupleActionProcessor.py +270 -0
- vortex/handler/TupleActionProcessorProxy.py +218 -0
- vortex/handler/TupleActionProcessorTestHandler.py +27 -0
- vortex/handler/TupleDataActionClient.py +67 -0
- vortex/handler/TupleDataObservableCache.py +201 -0
- vortex/handler/TupleDataObservableHandler.py +371 -0
- vortex/handler/TupleDataObservableProxyHandler.py +340 -0
- vortex/handler/TupleDataObservableTestHandler.py +60 -0
- vortex/handler/TupleDataObserverClient.py +158 -0
- vortex/handler/VortexJSTupleLoaderTestHandler.py +42 -0
- vortex/handler/__init__.py +0 -0
- vortex/restful/GzippedDataHttpClient.py +170 -0
- vortex/restful/GzippedDataHttpClientTest.py +103 -0
- vortex/restful/GzippedPayloadHttpClient.py +181 -0
- vortex/restful/RestfulHttpClient.py +107 -0
- vortex/restful/RestfulHttpClientTest.py +60 -0
- vortex/restful/RestfulResource.py +153 -0
- vortex/restful/RestfulResourceTest.py +100 -0
- vortex/restful/__init__.py +0 -0
- vortex/rpc/RPC.py +446 -0
- vortex/rpc/RPCTest.py +12 -0
- vortex/rpc/__init__.py +0 -0
- vortex/sqla_orm/OrmCrudHandler.py +431 -0
- vortex/sqla_orm/TupleProviderForDb.py +42 -0
- vortex/sqla_orm/VortexTupleJson.py +24 -0
- vortex/sqla_orm/__init__.py +0 -0
- vortex/storage/TupleStorageSqlite.py +359 -0
- vortex/storage/__init__.py +0 -0
- vortexpy-4.0.23.dist-info/LICENSE +21 -0
- vortexpy-4.0.23.dist-info/METADATA +43 -0
- vortexpy-4.0.23.dist-info/RECORD +87 -0
- vortexpy-4.0.23.dist-info/WHEEL +5 -0
- vortexpy-4.0.23.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,355 @@
|
|
|
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
|
+
import logging
|
|
10
|
+
import uuid
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Union, Optional, List
|
|
13
|
+
|
|
14
|
+
import pytz
|
|
15
|
+
import twisted
|
|
16
|
+
from twisted.internet import reactor
|
|
17
|
+
from twisted.internet import task
|
|
18
|
+
from twisted.internet.defer import Deferred, inlineCallbacks
|
|
19
|
+
from twisted.internet.error import ConnectionDone, ConnectionRefusedError
|
|
20
|
+
from twisted.internet.protocol import connectionDone, ReconnectingClientFactory
|
|
21
|
+
|
|
22
|
+
from vortex.DeferUtil import vortexLogFailure, isMainThread
|
|
23
|
+
from vortex.PayloadEnvelope import PayloadEnvelope, VortexMsgList
|
|
24
|
+
from vortex.PayloadPriority import DEFAULT_PRIORITY
|
|
25
|
+
from vortex.VortexABC import VortexABC, VortexInfo
|
|
26
|
+
from vortex.VortexPayloadProtocol import VortexPayloadProtocol
|
|
27
|
+
from vortex.VortexServer import HEART_BEAT_PERIOD
|
|
28
|
+
from vortex.VortexServer import HEART_BEAT_TIMEOUT
|
|
29
|
+
from vortex.VortexUtil import logLargeMessages
|
|
30
|
+
from vortex.VortexWritePushProducer import VortexWritePushProducer
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(name=__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class VortexPayloadTcpClientProtocol(VortexPayloadProtocol):
|
|
36
|
+
def __init__(self, vortexClient=None) -> None:
|
|
37
|
+
VortexPayloadProtocol.__init__(self, logger)
|
|
38
|
+
self._vortexClient = vortexClient
|
|
39
|
+
|
|
40
|
+
self._closed = False
|
|
41
|
+
|
|
42
|
+
# Start our heart beat
|
|
43
|
+
self._sendBeatLoopingCall = task.LoopingCall(self._sendBeat)
|
|
44
|
+
d = self._sendBeatLoopingCall.start(HEART_BEAT_PERIOD, now=False)
|
|
45
|
+
d.addErrback(lambda f: logger.exception(f.value))
|
|
46
|
+
|
|
47
|
+
self._producer: Optional[VortexWritePushProducer] = None
|
|
48
|
+
|
|
49
|
+
def _beat(self):
|
|
50
|
+
if self._vortexClient:
|
|
51
|
+
self._vortexClient._beat()
|
|
52
|
+
|
|
53
|
+
def _nameAndUuidReceived(self, name, uuid):
|
|
54
|
+
from vortex.VortexFactory import VortexFactory
|
|
55
|
+
|
|
56
|
+
self._producer.setRemoteVortexName(name)
|
|
57
|
+
|
|
58
|
+
if self._vortexClient:
|
|
59
|
+
self._vortexClient._setNameAndUuid(name=name, uuid=uuid)
|
|
60
|
+
|
|
61
|
+
VortexFactory._notifyOfVortexStatusChange(name, online=True)
|
|
62
|
+
|
|
63
|
+
def _createResponseSenderCallable(self):
|
|
64
|
+
def sendResponse(
|
|
65
|
+
vortexMsgs: Union[VortexMsgList, bytes],
|
|
66
|
+
priority: int = DEFAULT_PRIORITY,
|
|
67
|
+
):
|
|
68
|
+
return self._vortexClient.sendVortexMsg(
|
|
69
|
+
vortexMsgs=vortexMsgs, priority=priority
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return sendResponse
|
|
73
|
+
|
|
74
|
+
def _sendBeat(self):
|
|
75
|
+
if self._closed:
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# Send the heartbeats
|
|
79
|
+
self._producer.write(b".", DEFAULT_PRIORITY)
|
|
80
|
+
|
|
81
|
+
def write(self, payloadVortexStr: bytes, priority: int = DEFAULT_PRIORITY):
|
|
82
|
+
if not twisted.python.threadable.isInIOThread():
|
|
83
|
+
e = Exception("Write called from NON main thread")
|
|
84
|
+
logger.exception(str(e))
|
|
85
|
+
raise e
|
|
86
|
+
|
|
87
|
+
assert not self._closed
|
|
88
|
+
self._producer.write(payloadVortexStr, priority)
|
|
89
|
+
|
|
90
|
+
def connectionMade(self):
|
|
91
|
+
self._producer = VortexWritePushProducer(
|
|
92
|
+
self.transport, lambda: self.close()
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Register the producer if there isn't one already.
|
|
96
|
+
if not self.transport.producer:
|
|
97
|
+
self.transport.registerProducer(self._producer, True)
|
|
98
|
+
|
|
99
|
+
# Send a heart beat down the new connection, tell it who we are.
|
|
100
|
+
connectPayloadFilt = {
|
|
101
|
+
PayloadEnvelope.vortexUuidKey: self._vortexClient.uuid,
|
|
102
|
+
PayloadEnvelope.vortexNameKey: self._vortexClient.name,
|
|
103
|
+
}
|
|
104
|
+
self._producer.write(
|
|
105
|
+
PayloadEnvelope(filt=connectPayloadFilt).toVortexMsg(),
|
|
106
|
+
DEFAULT_PRIORITY,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def connectionLost(self, reason=connectionDone):
|
|
110
|
+
from vortex.VortexFactory import VortexFactory
|
|
111
|
+
|
|
112
|
+
VortexFactory._notifyOfVortexStatusChange(
|
|
113
|
+
self._serverVortexName, online=False
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if self._sendBeatLoopingCall.running:
|
|
117
|
+
self._sendBeatLoopingCall.stop()
|
|
118
|
+
self._closed = False
|
|
119
|
+
|
|
120
|
+
def close(self):
|
|
121
|
+
self.transport.loseConnection()
|
|
122
|
+
if self._sendBeatLoopingCall.running:
|
|
123
|
+
self._sendBeatLoopingCall.stop()
|
|
124
|
+
self._closed = False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class VortexClientTcp(ReconnectingClientFactory, VortexABC):
|
|
128
|
+
"""VortexServer Client
|
|
129
|
+
Connects to a votex server
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
RETRY_DELAY = 1.5 # Seconds
|
|
133
|
+
|
|
134
|
+
# The time it takes after recieving a response from the server to receive the
|
|
135
|
+
INFO_PAYLOAD_TIMEOUT = 5 # Seconds
|
|
136
|
+
|
|
137
|
+
# Set the ReconnectingClientFactory max delay
|
|
138
|
+
maxDelay = 1
|
|
139
|
+
|
|
140
|
+
def __init__(self, name: str) -> None:
|
|
141
|
+
self._vortexName = name
|
|
142
|
+
self._vortexUuid = str(uuid.uuid4())
|
|
143
|
+
|
|
144
|
+
self._server = None
|
|
145
|
+
self._port = None
|
|
146
|
+
|
|
147
|
+
self._retrying = False
|
|
148
|
+
|
|
149
|
+
self._serverVortexUuid = None
|
|
150
|
+
self._serverVortexName = None
|
|
151
|
+
|
|
152
|
+
self._lastBeatReceiveTime = None
|
|
153
|
+
self._lastHeartBeatCheckTime = datetime.now(pytz.utc)
|
|
154
|
+
|
|
155
|
+
# Start our heart beat checker
|
|
156
|
+
self._beatLoopingCall = task.LoopingCall(self._checkBeat)
|
|
157
|
+
|
|
158
|
+
self._reconnectVortexMsgs = [PayloadEnvelope().toVortexMsg()]
|
|
159
|
+
|
|
160
|
+
self.__protocol = None
|
|
161
|
+
|
|
162
|
+
#####################################################################################3
|
|
163
|
+
|
|
164
|
+
# class EchoClientFactory(ReconnectingClientFactory):
|
|
165
|
+
def startedConnecting(self, connector):
|
|
166
|
+
logger.debug("Started to connect.")
|
|
167
|
+
|
|
168
|
+
def buildProtocol(self, addr):
|
|
169
|
+
|
|
170
|
+
if self.__protocol:
|
|
171
|
+
self.__protocol.close()
|
|
172
|
+
self.__protocol = None
|
|
173
|
+
|
|
174
|
+
logger.debug("Connected.")
|
|
175
|
+
self.resetDelay()
|
|
176
|
+
self.__protocol = VortexPayloadTcpClientProtocol(self)
|
|
177
|
+
return self.__protocol
|
|
178
|
+
|
|
179
|
+
def clientConnectionLost(self, connector, reason):
|
|
180
|
+
if not reason.check(ConnectionDone):
|
|
181
|
+
logger.debug("Lost connection. Reason: %s", reason)
|
|
182
|
+
ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
|
|
183
|
+
|
|
184
|
+
def clientConnectionFailed(self, connector, reason):
|
|
185
|
+
if reason.check(ConnectionRefusedError):
|
|
186
|
+
logger.debug("Connection refused (We'll retry)")
|
|
187
|
+
else:
|
|
188
|
+
logger.debug("Connection failed. Reason: %s", reason)
|
|
189
|
+
ReconnectingClientFactory.clientConnectionFailed(
|
|
190
|
+
self, connector, reason
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
#####################################################################################3
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def localVortexInfo(self) -> VortexInfo:
|
|
197
|
+
return VortexInfo(name=self._vortexName, uuid=self._vortexUuid)
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def remoteVortexInfo(self) -> List[VortexInfo]:
|
|
201
|
+
if not self.__protocol:
|
|
202
|
+
return []
|
|
203
|
+
|
|
204
|
+
if not self._serverVortexUuid:
|
|
205
|
+
return []
|
|
206
|
+
|
|
207
|
+
return [
|
|
208
|
+
VortexInfo(name=self._serverVortexName, uuid=self._serverVortexUuid)
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def name(self):
|
|
213
|
+
return self._vortexName
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def uuid(self):
|
|
217
|
+
return self._vortexUuid
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def requiresBase64Encoding(self) -> bool:
|
|
221
|
+
return True
|
|
222
|
+
|
|
223
|
+
def connect(self, server, port):
|
|
224
|
+
self._server = server
|
|
225
|
+
self._port = port
|
|
226
|
+
|
|
227
|
+
self._beat()
|
|
228
|
+
d = self._beatLoopingCall.start(HEART_BEAT_PERIOD, now=False)
|
|
229
|
+
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
230
|
+
|
|
231
|
+
deferred = Deferred()
|
|
232
|
+
|
|
233
|
+
reactor.connectTCP(self._server, self._port, self)
|
|
234
|
+
|
|
235
|
+
def checkUuid():
|
|
236
|
+
if self._serverVortexName:
|
|
237
|
+
deferred.callback(True)
|
|
238
|
+
else:
|
|
239
|
+
reactor.callLater(0.1, checkUuid)
|
|
240
|
+
|
|
241
|
+
checkUuid()
|
|
242
|
+
|
|
243
|
+
return deferred
|
|
244
|
+
|
|
245
|
+
def close(self):
|
|
246
|
+
if self._beatLoopingCall and self._beatLoopingCall.running:
|
|
247
|
+
self._beatLoopingCall.stop()
|
|
248
|
+
self._beatLoopingCall = None
|
|
249
|
+
|
|
250
|
+
self.stopTrying()
|
|
251
|
+
self.__protocol.close()
|
|
252
|
+
|
|
253
|
+
def addReconnectVortexMsg(self, vortexMsg: bytes):
|
|
254
|
+
"""Add Reconnect Payload
|
|
255
|
+
:param vortexMsg: An encoded PayloadEnvelope to send when the connection reconnects
|
|
256
|
+
:return:
|
|
257
|
+
"""
|
|
258
|
+
self._reconnectVortexMsgs.append(vortexMsg)
|
|
259
|
+
|
|
260
|
+
def sendVortexMsg(
|
|
261
|
+
self,
|
|
262
|
+
vortexMsgs: Union[VortexMsgList, bytes, None] = None,
|
|
263
|
+
vortexUuid: Optional[str] = None,
|
|
264
|
+
priority: int = DEFAULT_PRIORITY,
|
|
265
|
+
) -> Deferred:
|
|
266
|
+
"""Send Vortex Msg
|
|
267
|
+
|
|
268
|
+
NOTE: Priority ins't supported as there is no buffer for this class.
|
|
269
|
+
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
if vortexMsgs is None:
|
|
273
|
+
vortexMsgs = self._reconnectVortexMsgs
|
|
274
|
+
|
|
275
|
+
if not isinstance(vortexMsgs, list):
|
|
276
|
+
vortexMsgs = [vortexMsgs]
|
|
277
|
+
|
|
278
|
+
logLargeMessages(logger, vortexMsgs, self._serverVortexUuid)
|
|
279
|
+
|
|
280
|
+
# Check if the vortexUuid matches the destination uuid
|
|
281
|
+
#
|
|
282
|
+
# if vortexUuid and vortexUuid != self._
|
|
283
|
+
#
|
|
284
|
+
# if not self.__protocol.serverVortexUuid:
|
|
285
|
+
# return []
|
|
286
|
+
|
|
287
|
+
if isMainThread():
|
|
288
|
+
return self._sendVortexMsgLater(vortexMsgs)
|
|
289
|
+
|
|
290
|
+
return task.deferLater(reactor, 0, self._sendVortexMsgLater, vortexMsgs)
|
|
291
|
+
|
|
292
|
+
@inlineCallbacks
|
|
293
|
+
def _sendVortexMsgLater(self, vortexMsgs: VortexMsgList):
|
|
294
|
+
yield None
|
|
295
|
+
assert self._server
|
|
296
|
+
assert vortexMsgs
|
|
297
|
+
|
|
298
|
+
# This transport requires base64 encoding
|
|
299
|
+
for index, vortexMsg in enumerate(vortexMsgs):
|
|
300
|
+
if vortexMsg.startswith(b"{"):
|
|
301
|
+
vortexMsgs[index] = yield PayloadEnvelope.base64EncodeDefer(
|
|
302
|
+
vortexMsg
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
for vortexMsg in vortexMsgs:
|
|
306
|
+
self.__protocol.write(vortexMsg)
|
|
307
|
+
|
|
308
|
+
return True
|
|
309
|
+
|
|
310
|
+
def _beat(self):
|
|
311
|
+
"""Beat, Called by protocol"""
|
|
312
|
+
self._lastBeatReceiveTime = datetime.now(pytz.utc)
|
|
313
|
+
|
|
314
|
+
def _setNameAndUuid(self, name, uuid):
|
|
315
|
+
"""Set Name And Uuid, Called by protocol"""
|
|
316
|
+
self._serverVortexName = name
|
|
317
|
+
self._serverVortexUuid = uuid
|
|
318
|
+
|
|
319
|
+
def _checkBeat(self):
|
|
320
|
+
|
|
321
|
+
# If we've been asleep, then make note of that (VM suspended)
|
|
322
|
+
checkTimout = (
|
|
323
|
+
datetime.now(pytz.utc) - self._lastHeartBeatCheckTime
|
|
324
|
+
).seconds > HEART_BEAT_TIMEOUT
|
|
325
|
+
|
|
326
|
+
# Has the heart beat expired?
|
|
327
|
+
beatTimeout = (
|
|
328
|
+
datetime.now(pytz.utc) - self._lastBeatReceiveTime
|
|
329
|
+
).seconds > HEART_BEAT_TIMEOUT
|
|
330
|
+
|
|
331
|
+
# Mark that we've just checked it
|
|
332
|
+
self._lastHeartBeatCheckTime = datetime.now(pytz.utc)
|
|
333
|
+
|
|
334
|
+
if checkTimout:
|
|
335
|
+
self._lastBeatReceiveTime = datetime.now(pytz.utc)
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
if beatTimeout:
|
|
339
|
+
self._reconnectAfterHeartBeatLost()
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
def _reconnectAfterHeartBeatLost(self):
|
|
343
|
+
if self._retrying:
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
self._retrying = True
|
|
347
|
+
|
|
348
|
+
logger.info(
|
|
349
|
+
"VortexServer client dead, reconnecting %s:%s",
|
|
350
|
+
self._server,
|
|
351
|
+
self._port,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if self.__protocol:
|
|
355
|
+
self.__protocol.close()
|