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,216 @@
|
|
|
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 inspect
|
|
10
|
+
import logging
|
|
11
|
+
import types
|
|
12
|
+
import weakref
|
|
13
|
+
from copy import copy
|
|
14
|
+
from typing import Callable, Optional, Dict, Union
|
|
15
|
+
|
|
16
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
17
|
+
from vortex.PayloadIO import PayloadIO
|
|
18
|
+
from vortex.VortexABC import SendVortexMsgResponseCallable
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# TODO: typings needs to support keyword args before tbis will work.
|
|
24
|
+
# PayloadEndpointProcessCallable = Callable[
|
|
25
|
+
# [
|
|
26
|
+
# ...
|
|
27
|
+
# # PayloadEnvelope, # payloadEnvelope
|
|
28
|
+
# # str, # vortexUuid
|
|
29
|
+
# # str, # vortexName
|
|
30
|
+
# # Any, # httpSession
|
|
31
|
+
# # SendVortexMsgResponseCallable # sendResponse
|
|
32
|
+
# ],
|
|
33
|
+
# Optional[Deferred] # Return
|
|
34
|
+
# ]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class PayloadEndpoint:
|
|
38
|
+
"""
|
|
39
|
+
The payload endpoint is responsible for matching payloads filters against
|
|
40
|
+
filters defined in the endpoint. If the end point filters are within the
|
|
41
|
+
payload filter then the payload will be passed to the supplied callable.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
PERMITTER = None
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
filt: Dict,
|
|
49
|
+
callable_,
|
|
50
|
+
acceptOnlyFromVortex: Optional[Union[str, tuple]] = None,
|
|
51
|
+
ignoreFromVortex: Optional[Union[str, tuple]] = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
:param filt: The filter to match against payloads
|
|
55
|
+
:param callable_: This will be called and passed the payload if it matches
|
|
56
|
+
:param acceptOnlyFromVortex: Accept payloads only from this vortex,
|
|
57
|
+
The vortex can be str or tuple of str, or None to accept from any.
|
|
58
|
+
"""
|
|
59
|
+
self._acceptOnlyFromVortex = acceptOnlyFromVortex
|
|
60
|
+
if isinstance(self._acceptOnlyFromVortex, str):
|
|
61
|
+
self._acceptOnlyFromVortex = (self._acceptOnlyFromVortex,)
|
|
62
|
+
|
|
63
|
+
self._ignoreFromVortex = ignoreFromVortex
|
|
64
|
+
if isinstance(self._ignoreFromVortex, str):
|
|
65
|
+
self._ignoreFromVortex = (self._ignoreFromVortex,)
|
|
66
|
+
|
|
67
|
+
self._keyCheck(filt)
|
|
68
|
+
|
|
69
|
+
self._wref: Callable[[], Optional[Callable]] = None
|
|
70
|
+
if isinstance(callable_, types.FunctionType):
|
|
71
|
+
w = None
|
|
72
|
+
if hasattr(callable_, "_endpointWeakClass"):
|
|
73
|
+
w = callable_._endpointWeakClass
|
|
74
|
+
|
|
75
|
+
else:
|
|
76
|
+
|
|
77
|
+
class W:
|
|
78
|
+
def __init__(self, callable_):
|
|
79
|
+
self._callable = callable_
|
|
80
|
+
self._callable._endpointWeakClass = self
|
|
81
|
+
|
|
82
|
+
def __call__(self, payload):
|
|
83
|
+
self._callable(payload)
|
|
84
|
+
|
|
85
|
+
w = W(callable_)
|
|
86
|
+
|
|
87
|
+
self._wref = weakref.ref(w)
|
|
88
|
+
|
|
89
|
+
else:
|
|
90
|
+
weakObject = weakref.ref(callable_.__self__)
|
|
91
|
+
weakMethod = weakref.ref(callable_.__func__)
|
|
92
|
+
|
|
93
|
+
if callable_.__func__.__name__ == "func":
|
|
94
|
+
logger.warning(
|
|
95
|
+
"The registered callback is 'func' this is likely"
|
|
96
|
+
" a method wrapped in @deferToThreadWrapWithLogger, %s",
|
|
97
|
+
filt,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def getCallable():
|
|
101
|
+
obj = weakObject()
|
|
102
|
+
func = weakMethod()
|
|
103
|
+
if obj and func:
|
|
104
|
+
return getattr(obj, func.__name__)
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
self._wref = getCallable
|
|
108
|
+
|
|
109
|
+
self._filt = filt
|
|
110
|
+
PayloadIO().add(self)
|
|
111
|
+
|
|
112
|
+
def _keyCheck(self, filt):
|
|
113
|
+
if not "key" in filt:
|
|
114
|
+
e = Exception(
|
|
115
|
+
"There is no 'key' in the payload filt"
|
|
116
|
+
", There must be one for routing"
|
|
117
|
+
)
|
|
118
|
+
logger.exception(e)
|
|
119
|
+
raise e
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def filt(self):
|
|
123
|
+
return copy(self._filt)
|
|
124
|
+
|
|
125
|
+
def check(self, payloadEnvelope: PayloadEnvelope, vortexName: str) -> bool:
|
|
126
|
+
|
|
127
|
+
# Filter for the backend vortexes.
|
|
128
|
+
if self._ignoreFromVortex and vortexName in self._ignoreFromVortex:
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
# Filter for the backend vortexes.
|
|
132
|
+
if (
|
|
133
|
+
self._acceptOnlyFromVortex
|
|
134
|
+
and vortexName not in self._acceptOnlyFromVortex
|
|
135
|
+
):
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def removeUnhashable(filt):
|
|
139
|
+
items = set()
|
|
140
|
+
for key, value in list(filt.items()):
|
|
141
|
+
# We can compare an array of primitives
|
|
142
|
+
if isinstance(value, list):
|
|
143
|
+
value = tuple(sorted(value))
|
|
144
|
+
|
|
145
|
+
# We don't compare complex structures
|
|
146
|
+
if isinstance(value, dict):
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
if inspect.isclass(value) and (
|
|
150
|
+
issubclass(value, dict) or issubclass(value, list)
|
|
151
|
+
):
|
|
152
|
+
raise Exception(
|
|
153
|
+
"Class type passed instead of an instance"
|
|
154
|
+
" key:%s, value:%s" % (key, value)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
items.add((key, value))
|
|
158
|
+
return items
|
|
159
|
+
|
|
160
|
+
theirFilt = removeUnhashable(payloadEnvelope.filt)
|
|
161
|
+
ourFilt = removeUnhashable(self._filt)
|
|
162
|
+
|
|
163
|
+
return set(ourFilt).issubset(theirFilt)
|
|
164
|
+
|
|
165
|
+
def process(
|
|
166
|
+
self,
|
|
167
|
+
payloadEnvelope: PayloadEnvelope,
|
|
168
|
+
vortexUuid: str,
|
|
169
|
+
vortexName: str,
|
|
170
|
+
httpSession,
|
|
171
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
172
|
+
):
|
|
173
|
+
|
|
174
|
+
if not self.check(payloadEnvelope, vortexName):
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
if self.PERMITTER and not self.PERMITTER.check(
|
|
178
|
+
payloadEnvelope, httpSession
|
|
179
|
+
):
|
|
180
|
+
logger.debug("Permission failed for %s", payloadEnvelope.filt)
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
callable_ = self._wref()
|
|
184
|
+
if callable_:
|
|
185
|
+
return callable_(
|
|
186
|
+
payloadEnvelope=payloadEnvelope,
|
|
187
|
+
vortexUuid=vortexUuid,
|
|
188
|
+
vortexName=vortexName,
|
|
189
|
+
httpSession=httpSession,
|
|
190
|
+
sendResponse=sendResponse,
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
PayloadIO().remove(self)
|
|
194
|
+
|
|
195
|
+
def _callableExpired(self, expiredCallable):
|
|
196
|
+
pass
|
|
197
|
+
# PayloadIO().remove(self)
|
|
198
|
+
|
|
199
|
+
def __repr__(self):
|
|
200
|
+
callable_ = self._wref()
|
|
201
|
+
if callable_:
|
|
202
|
+
try:
|
|
203
|
+
callbackStr = "%s.%s" % (
|
|
204
|
+
callable_.__self__.__class__.__name__,
|
|
205
|
+
callable_.__name__,
|
|
206
|
+
)
|
|
207
|
+
except Exception as e:
|
|
208
|
+
callbackStr = str(e)
|
|
209
|
+
else:
|
|
210
|
+
callbackStr = "None"
|
|
211
|
+
|
|
212
|
+
s = "Payload id=%s\nfilt=%s\ncallback=%s"
|
|
213
|
+
return s % (id(self), self._filt, callbackStr)
|
|
214
|
+
|
|
215
|
+
def shutdown(self):
|
|
216
|
+
PayloadIO().remove(self)
|
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
from twisted.trial import unittest
|
|
11
|
+
|
|
12
|
+
from vortex.Payload import Payload
|
|
13
|
+
from vortex.PayloadEndpoint import PayloadEndpoint
|
|
14
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
15
|
+
from vortex.PayloadIO import PayloadIO
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PayloadEndpointPyTestAssignPayload:
|
|
19
|
+
def __init__(self, unitTest):
|
|
20
|
+
self._ut = unitTest
|
|
21
|
+
|
|
22
|
+
def process(self, payloadEnvelope: PayloadEnvelope, **kwargs):
|
|
23
|
+
self._ut.deliveredPayload = payloadEnvelope
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PayloadEndpointPyTest(unittest.TestCase):
|
|
27
|
+
def setUp(self):
|
|
28
|
+
PayloadIO._instance = None
|
|
29
|
+
|
|
30
|
+
self.deliveredPayloadEnvelope = None
|
|
31
|
+
|
|
32
|
+
def _testBuild(self, plFilt, epFilt):
|
|
33
|
+
payload = Payload()
|
|
34
|
+
payload.filt.update(plFilt)
|
|
35
|
+
for x in range(6):
|
|
36
|
+
payload.filt["%s" % x] = x
|
|
37
|
+
|
|
38
|
+
def processPayload(payloadEnvelope: PayloadEnvelope, **kwargs):
|
|
39
|
+
self.deliveredPayloadEnvelope = payloadEnvelope
|
|
40
|
+
|
|
41
|
+
self._keepFuncInScope = processPayload
|
|
42
|
+
|
|
43
|
+
PayloadEndpoint(epFilt, processPayload)
|
|
44
|
+
|
|
45
|
+
PayloadIO().process(
|
|
46
|
+
payloadEnvelope=payload.makePayloadEnvelope(),
|
|
47
|
+
vortexUuid="test",
|
|
48
|
+
vortexName="test",
|
|
49
|
+
httpSession=None,
|
|
50
|
+
sendResponse=lambda x: x,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return payload
|
|
54
|
+
|
|
55
|
+
def testFiltMatches(self):
|
|
56
|
+
plFilt = {"key": "unittest", "This matches": 555}
|
|
57
|
+
epFilt = {"key": "unittest", "This matches": 555}
|
|
58
|
+
|
|
59
|
+
payload = self._testBuild(plFilt, epFilt)
|
|
60
|
+
|
|
61
|
+
self.assertEqual(
|
|
62
|
+
payload,
|
|
63
|
+
self.deliveredPayloadEnvelope,
|
|
64
|
+
"PayloadIO/PayloadEndpoint delivery error",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def testFiltValueUnmatched(self):
|
|
68
|
+
plFilt = {"key": "unittest", "This matches": 555}
|
|
69
|
+
epFilt = {"key": "unittest", "This matches": 0}
|
|
70
|
+
|
|
71
|
+
self._testBuild(plFilt, epFilt)
|
|
72
|
+
|
|
73
|
+
self.assertEqual(
|
|
74
|
+
self.deliveredPayloadEnvelope,
|
|
75
|
+
None,
|
|
76
|
+
"PayloadIO/PayloadEndpoint unmatched value test error",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def testFiltKeyUnmatched(self):
|
|
80
|
+
plFilt = {"key": "unittest", "This matches": 555}
|
|
81
|
+
epFilt = {"key": "unittest", "This doesnt matches": 555}
|
|
82
|
+
|
|
83
|
+
self._testBuild(plFilt, epFilt)
|
|
84
|
+
|
|
85
|
+
self.assertEqual(
|
|
86
|
+
self.deliveredPayloadEnvelope,
|
|
87
|
+
None,
|
|
88
|
+
"PayloadIO/PayloadEndpoint unmatched value test error",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def testFunctionGoesOutOfScope(self):
|
|
92
|
+
filt = {"key": "unittest", "This matches": 555}
|
|
93
|
+
|
|
94
|
+
payload = Payload()
|
|
95
|
+
payload.filt = filt
|
|
96
|
+
|
|
97
|
+
def subScope():
|
|
98
|
+
def outOfScopeFunc(
|
|
99
|
+
payloadEnvelope: PayloadEnvelope, *args, **kwargs
|
|
100
|
+
):
|
|
101
|
+
self.deliveredPayloadEnvelope = payloadEnvelope
|
|
102
|
+
|
|
103
|
+
PayloadEndpoint(filt, outOfScopeFunc)
|
|
104
|
+
|
|
105
|
+
PayloadIO().process(
|
|
106
|
+
payloadEnvelope=payload.makePayloadEnvelope(),
|
|
107
|
+
vortexUuid="test",
|
|
108
|
+
vortexName="test",
|
|
109
|
+
httpSession=None,
|
|
110
|
+
sendResponse=lambda x: x,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
self.assertEqual(
|
|
114
|
+
self.deliveredPayloadEnvelope,
|
|
115
|
+
None,
|
|
116
|
+
"PayloadIO/PayloadEndpoint unmatched value test error",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def testClassGoesOutOdScope(self):
|
|
120
|
+
filt = {"key": "unittest", "This matches": 555}
|
|
121
|
+
|
|
122
|
+
payload = Payload()
|
|
123
|
+
payload.filt = filt
|
|
124
|
+
|
|
125
|
+
def subScope():
|
|
126
|
+
inst = PayloadEndpointPyTestAssignPayload(self)
|
|
127
|
+
PayloadEndpoint(filt, inst.process)
|
|
128
|
+
|
|
129
|
+
PayloadIO().process(
|
|
130
|
+
payloadEnvelope=payload.makePayloadEnvelope(),
|
|
131
|
+
vortexUuid="test",
|
|
132
|
+
vortexName="test",
|
|
133
|
+
httpSession=None,
|
|
134
|
+
sendResponse=lambda x: x,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
self.assertEqual(
|
|
138
|
+
self.deliveredPayloadEnvelope,
|
|
139
|
+
None,
|
|
140
|
+
"PayloadIO/PayloadEndpoint unmatched value test error",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def testClassStaysInScope(self):
|
|
144
|
+
filt = {"key": "unittest", "This matches": 555}
|
|
145
|
+
|
|
146
|
+
payload = Payload()
|
|
147
|
+
payload.filt = filt
|
|
148
|
+
|
|
149
|
+
inst = PayloadEndpointPyTestAssignPayload(self)
|
|
150
|
+
PayloadEndpoint(filt, inst.process)
|
|
151
|
+
|
|
152
|
+
PayloadIO().process(
|
|
153
|
+
payloadEnvelope=payload.makePayloadEnvelope(),
|
|
154
|
+
vortexUuid="test",
|
|
155
|
+
vortexName="test",
|
|
156
|
+
httpSession=None,
|
|
157
|
+
sendResponse=lambda x: x,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
self.assertEqual(
|
|
161
|
+
self.deliveredPayloadEnvelope,
|
|
162
|
+
payload,
|
|
163
|
+
"PayloadIO/PayloadEndpoint unmatched value test error",
|
|
164
|
+
)
|
|
@@ -0,0 +1,166 @@
|
|
|
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 base64
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
from base64 import b64decode
|
|
14
|
+
from base64 import b64encode
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from typing import Any
|
|
17
|
+
from typing import Dict
|
|
18
|
+
from typing import List
|
|
19
|
+
from typing import Optional
|
|
20
|
+
from typing import Union
|
|
21
|
+
|
|
22
|
+
import pytz
|
|
23
|
+
|
|
24
|
+
from .DeferUtil import deferToThreadWrapWithLogger
|
|
25
|
+
from .DeferUtil import noMainThread
|
|
26
|
+
from .Jsonable import Jsonable
|
|
27
|
+
from .Payload import Payload
|
|
28
|
+
from .SerialiseUtil import T_RAPUI_PAYLOAD_ENVELOPE
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
PayloadEnvelopeList = List["PayloadEnvelope"]
|
|
33
|
+
VortexMsgList = List[bytes]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class NoPayloadException(Exception):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PayloadEnvelope(Jsonable):
|
|
41
|
+
"""Payload
|
|
42
|
+
This object represents a hierarchy of data transferred between client and server
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
__fieldNames__ = ["filt", "data", "result", "date"]
|
|
46
|
+
__rapuiSerialiseType__ = T_RAPUI_PAYLOAD_ENVELOPE
|
|
47
|
+
|
|
48
|
+
vortexUuidKey = "__vortexUuid__"
|
|
49
|
+
vortexNameKey = "__vortexName__"
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
filt: Optional[Dict] = None,
|
|
54
|
+
encodedPayload: Optional[str] = None,
|
|
55
|
+
result=None,
|
|
56
|
+
date: Optional[datetime] = None,
|
|
57
|
+
data: Optional[Any] = None,
|
|
58
|
+
) -> None:
|
|
59
|
+
Jsonable.__init__(self)
|
|
60
|
+
"""Constructor"""
|
|
61
|
+
assert not (
|
|
62
|
+
data and encodedPayload
|
|
63
|
+
), "You can not pass encodedData and encodedPayload"
|
|
64
|
+
|
|
65
|
+
self.filt: Dict = {} if filt is None else filt
|
|
66
|
+
self.data: Union[Any, str, None] = data if data else encodedPayload
|
|
67
|
+
self.result = result
|
|
68
|
+
self.date: datetime = date if date else datetime.now(pytz.utc)
|
|
69
|
+
|
|
70
|
+
if isinstance(filt, str):
|
|
71
|
+
self.filt = {self.filt: None}
|
|
72
|
+
|
|
73
|
+
# -------------------------------------------
|
|
74
|
+
# PayloadEnvelope Methods
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def encodedPayload(self) -> Optional[str]:
|
|
78
|
+
# self.data could be from VortexJS/Typescript where we allow `str` data
|
|
79
|
+
assert self.data is None or isinstance(
|
|
80
|
+
self.data, str
|
|
81
|
+
), "Encoded payload is not None or a string"
|
|
82
|
+
return self.data
|
|
83
|
+
|
|
84
|
+
@encodedPayload.setter
|
|
85
|
+
def encodedPayload(self, val: Optional[str]):
|
|
86
|
+
# This value should only be set from users of VortexPy and
|
|
87
|
+
# Payload.toEncodedPayload so we only allow str or None
|
|
88
|
+
assert val is None or isinstance(
|
|
89
|
+
val, str
|
|
90
|
+
), "Encoded payload is not None or a string"
|
|
91
|
+
self.data = val
|
|
92
|
+
|
|
93
|
+
def isEmpty(self):
|
|
94
|
+
return (
|
|
95
|
+
(
|
|
96
|
+
not self.filt
|
|
97
|
+
or (
|
|
98
|
+
self.vortexNameKey in self.filt
|
|
99
|
+
and self.vortexUuidKey in self.filt
|
|
100
|
+
and len(self.filt) == 2
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
and not self.encodedPayload
|
|
104
|
+
and not self.result
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# -------------------------------------------
|
|
108
|
+
# Decode Helper Methods
|
|
109
|
+
|
|
110
|
+
def decodePayload(self) -> Payload:
|
|
111
|
+
if not self.encodedPayload:
|
|
112
|
+
if self.result:
|
|
113
|
+
logger.debug(
|
|
114
|
+
"encodedPayload is None, but maybe there was an error: %s",
|
|
115
|
+
self.result,
|
|
116
|
+
)
|
|
117
|
+
raise NoPayloadException()
|
|
118
|
+
|
|
119
|
+
noMainThread()
|
|
120
|
+
return Payload().fromEncodedPayload(self.encodedPayload)
|
|
121
|
+
|
|
122
|
+
@deferToThreadWrapWithLogger(logger)
|
|
123
|
+
def decodePayloadDefer(self) -> "Payload":
|
|
124
|
+
return self.decodePayload()
|
|
125
|
+
|
|
126
|
+
# -------------------------------------------
|
|
127
|
+
# JSON Related methods
|
|
128
|
+
def _fromJson(self, jsonStr):
|
|
129
|
+
jsonDict = json.loads(jsonStr)
|
|
130
|
+
|
|
131
|
+
assert jsonDict[Jsonable.JSON_CLASS_TYPE] == self.__rapuiSerialiseType__
|
|
132
|
+
return self.fromJsonDict(jsonDict)
|
|
133
|
+
|
|
134
|
+
def _toJson(self) -> str:
|
|
135
|
+
return json.dumps(self.toJsonDict())
|
|
136
|
+
|
|
137
|
+
# -------------------------------------------
|
|
138
|
+
# VortexServer Message Methods
|
|
139
|
+
def toVortexMsg(self, base64Encode=False) -> bytes:
|
|
140
|
+
jsonStr = self._toJson().encode()
|
|
141
|
+
if base64Encode:
|
|
142
|
+
return b64encode(jsonStr)
|
|
143
|
+
return jsonStr
|
|
144
|
+
|
|
145
|
+
@deferToThreadWrapWithLogger(logger)
|
|
146
|
+
def toVortexMsgDefer(self, base64Encode=False) -> bytes:
|
|
147
|
+
return self.toVortexMsg(base64Encode=base64Encode)
|
|
148
|
+
|
|
149
|
+
def fromVortexMsg(
|
|
150
|
+
self,
|
|
151
|
+
vortexMsg: bytes,
|
|
152
|
+
) -> "PayloadEnvelope":
|
|
153
|
+
if vortexMsg.startswith(b"{"):
|
|
154
|
+
jsonStr = vortexMsg.decode()
|
|
155
|
+
else:
|
|
156
|
+
jsonStr = b64decode(vortexMsg).decode()
|
|
157
|
+
return self._fromJson(jsonStr)
|
|
158
|
+
|
|
159
|
+
@deferToThreadWrapWithLogger(logger)
|
|
160
|
+
def fromVortexMsgDefer(self, vortexMsg: bytes) -> "PayloadEnvelope":
|
|
161
|
+
return self.fromVortexMsg(vortexMsg)
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
@deferToThreadWrapWithLogger(logger)
|
|
165
|
+
def base64EncodeDefer(cls, vortexMsg) -> bytes:
|
|
166
|
+
return base64.b64encode(vortexMsg)
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
rapuiServerEcho = "rapuiServerEcho"
|
|
11
|
+
rapuiClientEcho = "rapuiClientEcho"
|
|
12
|
+
rapuiVortexUuid = "rapuiVortexUuid"
|
|
13
|
+
|
|
14
|
+
plIdKey = "id"
|
|
15
|
+
plDeleteKey = "delete"
|
vortex/PayloadIO.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
import pytz
|
|
13
|
+
from twisted.internet import reactor
|
|
14
|
+
from twisted.internet.defer import Deferred
|
|
15
|
+
from twisted.python.failure import Failure
|
|
16
|
+
|
|
17
|
+
from vortex.DeferUtil import vortexLogFailure
|
|
18
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
19
|
+
from vortex.VortexABC import SendVortexMsgResponseCallable
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(name="PayloadIO")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PayloadIO(object):
|
|
25
|
+
"""
|
|
26
|
+
PayloadIO, Processes payloads received from the vortex and distributes
|
|
27
|
+
them to where they need to go.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Singleton
|
|
31
|
+
_instance = None
|
|
32
|
+
|
|
33
|
+
def __new__(cls):
|
|
34
|
+
if not cls._instance:
|
|
35
|
+
cls._instance = super(PayloadIO, cls).__new__(cls)
|
|
36
|
+
cls._instance.__singleton_init__()
|
|
37
|
+
return cls._instance
|
|
38
|
+
|
|
39
|
+
def __singleton_init__(self):
|
|
40
|
+
self._endpoints = set()
|
|
41
|
+
|
|
42
|
+
def remove(self, endpoint):
|
|
43
|
+
if endpoint in self._endpoints:
|
|
44
|
+
self._endpoints.remove(endpoint)
|
|
45
|
+
|
|
46
|
+
def add(self, endpoint):
|
|
47
|
+
self._endpoints.add(endpoint)
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def endpoints(self):
|
|
51
|
+
"""Endpoints property
|
|
52
|
+
|
|
53
|
+
@:return a copy of the list of endpoints
|
|
54
|
+
"""
|
|
55
|
+
return list(self._endpoints)
|
|
56
|
+
|
|
57
|
+
def process(
|
|
58
|
+
self,
|
|
59
|
+
payloadEnvelope: PayloadEnvelope,
|
|
60
|
+
vortexUuid: str,
|
|
61
|
+
vortexName: str,
|
|
62
|
+
httpSession,
|
|
63
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
64
|
+
):
|
|
65
|
+
from vortex.VortexFactory import VortexFactory
|
|
66
|
+
|
|
67
|
+
# noinspection PyProtectedMember
|
|
68
|
+
if VortexFactory._DEBUG_LOGGING:
|
|
69
|
+
logger.debug(
|
|
70
|
+
"Received payloadEnvelope from %s:%s for %s",
|
|
71
|
+
vortexName,
|
|
72
|
+
vortexUuid,
|
|
73
|
+
payloadEnvelope.filt,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
immutableEndpoints = list(self._endpoints)
|
|
77
|
+
for endpoint in immutableEndpoints:
|
|
78
|
+
if not endpoint.check(payloadEnvelope, vortexName):
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
reactor.callLater(
|
|
82
|
+
0,
|
|
83
|
+
self._processLater,
|
|
84
|
+
endpoint,
|
|
85
|
+
payloadEnvelope,
|
|
86
|
+
vortexUuid,
|
|
87
|
+
vortexName,
|
|
88
|
+
httpSession,
|
|
89
|
+
sendResponse,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _processLater(
|
|
93
|
+
self,
|
|
94
|
+
endpoint,
|
|
95
|
+
payloadEnvelope: PayloadEnvelope,
|
|
96
|
+
vortexUuid: str,
|
|
97
|
+
vortexName: str,
|
|
98
|
+
httpSession,
|
|
99
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
100
|
+
):
|
|
101
|
+
startDate = datetime.now(pytz.utc)
|
|
102
|
+
|
|
103
|
+
def respondToException(failure):
|
|
104
|
+
"""Respond To Exception
|
|
105
|
+
Putting the exception into a failure messes with the stack, hence the
|
|
106
|
+
common function
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
sendResponse(
|
|
110
|
+
PayloadEnvelope(
|
|
111
|
+
filt=payloadEnvelope.filt,
|
|
112
|
+
result=str(failure.getTraceback()),
|
|
113
|
+
).toVortexMsg()
|
|
114
|
+
)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.exception(e)
|
|
117
|
+
|
|
118
|
+
vortexLogFailure(failure, logger)
|
|
119
|
+
logger.error(payloadEnvelope.filt)
|
|
120
|
+
|
|
121
|
+
def callback(value, blocking=False):
|
|
122
|
+
# A blocking call taking more than 100ms is BAD
|
|
123
|
+
# Otherwise a call taking more than a 1s is just poor performance.
|
|
124
|
+
secondsTaken = (datetime.now(pytz.utc) - startDate).total_seconds()
|
|
125
|
+
if secondsTaken > 0.1 and blocking or secondsTaken > 2.0:
|
|
126
|
+
func = logger.critical if blocking else logger.debug
|
|
127
|
+
func(
|
|
128
|
+
"%s Payload endpoint took %s\npayload.filt=%s\n%s",
|
|
129
|
+
"BLOCKING " if blocking else "",
|
|
130
|
+
secondsTaken,
|
|
131
|
+
payloadEnvelope.filt,
|
|
132
|
+
endpoint,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
d = endpoint.process(
|
|
137
|
+
payloadEnvelope,
|
|
138
|
+
vortexUuid,
|
|
139
|
+
vortexName,
|
|
140
|
+
httpSession,
|
|
141
|
+
sendResponse,
|
|
142
|
+
)
|
|
143
|
+
if isinstance(d, Deferred):
|
|
144
|
+
d.addCallback(callback)
|
|
145
|
+
d.addErrback(respondToException)
|
|
146
|
+
elif hasattr(d, "addCallback"):
|
|
147
|
+
raise Exception("isinstance FAILED")
|
|
148
|
+
else:
|
|
149
|
+
callback(True, blocking=True)
|
|
150
|
+
|
|
151
|
+
except Exception as e:
|
|
152
|
+
respondToException(Failure(e))
|