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
vortex/DataWrapTuple.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
from .Tuple import Tuple, TupleField, addTupleType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@addTupleType
|
|
13
|
+
class DataWrapTuple(Tuple):
|
|
14
|
+
"""Data Wrap Tuple
|
|
15
|
+
|
|
16
|
+
This tuple wraps a peice of data for use in RapUI.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
name What ever you want to use it for
|
|
20
|
+
data The data
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
__tupleType__ = "c.s.r.datawraptuple"
|
|
25
|
+
|
|
26
|
+
name = TupleField(None)
|
|
27
|
+
data = TupleField(None)
|
vortex/DeferUtil.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import twisted
|
|
5
|
+
from twisted.internet import reactor
|
|
6
|
+
from twisted.internet.defer import (
|
|
7
|
+
maybeDeferred,
|
|
8
|
+
Deferred,
|
|
9
|
+
inlineCallbacks,
|
|
10
|
+
ensureDeferred,
|
|
11
|
+
)
|
|
12
|
+
from twisted.internet.threads import deferToThread
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def callMethodLater(func):
|
|
18
|
+
def wrapper(*args, **kwargs):
|
|
19
|
+
return reactor.callLater(0, func, *args, **kwargs)
|
|
20
|
+
|
|
21
|
+
return wrapper
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def nonConcurrentMethod(method):
|
|
25
|
+
def call(*args, **kwargs):
|
|
26
|
+
if call.running:
|
|
27
|
+
return
|
|
28
|
+
call.running = True
|
|
29
|
+
try:
|
|
30
|
+
return method(*args, **kwargs)
|
|
31
|
+
finally:
|
|
32
|
+
call.running = False
|
|
33
|
+
|
|
34
|
+
call.running = False
|
|
35
|
+
|
|
36
|
+
return call
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def maybeDeferredWrap(funcToWrap):
|
|
40
|
+
"""Maybe Deferred Wrap
|
|
41
|
+
|
|
42
|
+
A decorator that ensures a function will return a deferred.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def func(*args, **kwargs):
|
|
47
|
+
return maybeDeferred(funcToWrap, *args, **kwargs)
|
|
48
|
+
|
|
49
|
+
return func
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def ensureDeferredWrap(funcToWrap):
|
|
53
|
+
"""Ensured Deferred Wrap
|
|
54
|
+
|
|
55
|
+
A decorator that converts asyncio functions to ones that
|
|
56
|
+
return Twisted Deferred instead.
|
|
57
|
+
|
|
58
|
+
This keeps all the async processing in Twisteds reactive design while providing
|
|
59
|
+
support of the await/async syntax (and asyncio processing)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def func(*args, **kwargs):
|
|
65
|
+
return ensureDeferred(funcToWrap(*args, **kwargs))
|
|
66
|
+
|
|
67
|
+
return func
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def vortexLogFailure(failure, loggerArg, consumeError=False, successValue=True):
|
|
71
|
+
"""
|
|
72
|
+
Log a Twisted Failure instance using the provided logger.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
failure: Twisted Failure instance to log
|
|
76
|
+
loggerArg: Logger instance to use for logging
|
|
77
|
+
consumeError: If True, returns successValue instead of the failure
|
|
78
|
+
successValue: Value to return if consumeError is True
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The original failure or successValue depending on consumeError
|
|
82
|
+
"""
|
|
83
|
+
try:
|
|
84
|
+
if not hasattr(failure, "_vortexFailureLogged"):
|
|
85
|
+
exc_info = (
|
|
86
|
+
failure.type,
|
|
87
|
+
failure.value,
|
|
88
|
+
failure.getTracebackObject(),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Log with full exception info for better tracebacks
|
|
92
|
+
loggerArg.exception(str(failure.value), exc_info=exc_info)
|
|
93
|
+
|
|
94
|
+
# Mark as logged to prevent duplicate logging
|
|
95
|
+
failure._vortexFailureLogged = True
|
|
96
|
+
|
|
97
|
+
return successValue if consumeError else failure
|
|
98
|
+
|
|
99
|
+
except Exception as e:
|
|
100
|
+
# If something goes wrong in our logging logic, log that error
|
|
101
|
+
loggerArg.exception("Error while logging failure: %s", str(e))
|
|
102
|
+
return failure
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def vortexLogAndConsumeFailure(failure, loggerArg, successValue=True):
|
|
106
|
+
return vortexLogFailure(
|
|
107
|
+
failure, loggerArg, consumeError=True, successValue=successValue
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
printFailure = vortexLogFailure
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def vortexInlineCallbacksLogAndConsumeFailure(loggerArg):
|
|
115
|
+
"""Vortex InlineCallbacks Log and Consume Failure
|
|
116
|
+
|
|
117
|
+
This is exactly the same as @inlineCallbacks decorator, except it will log
|
|
118
|
+
and consume any deferred failures that are thrown.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def wrapper(funcToWrap):
|
|
122
|
+
funcIcb = inlineCallbacks(funcToWrap)
|
|
123
|
+
|
|
124
|
+
def called(*args, **kwargs):
|
|
125
|
+
d = funcIcb(*args, **kwargs)
|
|
126
|
+
d.addErrback(vortexLogAndConsumeFailure, loggerArg)
|
|
127
|
+
return d
|
|
128
|
+
|
|
129
|
+
return called
|
|
130
|
+
|
|
131
|
+
return wrapper
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class YieldInDeferToThreadException(Exception):
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def deferToThreadWrapWithLogger(
|
|
139
|
+
logger, consumeError=False, checkMainThread=True
|
|
140
|
+
):
|
|
141
|
+
"""Defer To Thread Wrap With Logger
|
|
142
|
+
|
|
143
|
+
This method is a decorator used to send blocking methods to threads easily.
|
|
144
|
+
|
|
145
|
+
:param logger: An instance from logging.getLogger(..)
|
|
146
|
+
:param consumeError: Should errors occuring in the thread be consumed or errback'd
|
|
147
|
+
:param checkMainThread: Don't use this parameter, it's only needed during reactor
|
|
148
|
+
startup when the reactors thread isn't properly set and the check
|
|
149
|
+
fails.
|
|
150
|
+
|
|
151
|
+
:return: A deferred.
|
|
152
|
+
|
|
153
|
+
Usage: ::
|
|
154
|
+
|
|
155
|
+
import logging
|
|
156
|
+
logger = logging.getLogger(__name__)
|
|
157
|
+
@deferToThreadWrapWithLogger(logger)
|
|
158
|
+
def myFunction(arg1, kw=True):
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
assert isinstance(
|
|
164
|
+
logger, logging.Logger
|
|
165
|
+
), """Usage:
|
|
166
|
+
import logging
|
|
167
|
+
logger = logging.getLogger(__name__)
|
|
168
|
+
@deferToThreadWrapWithLogger(logger)
|
|
169
|
+
def myFunction(arg1, kw=True):
|
|
170
|
+
pass
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
def wrapper(funcToWrap) -> Deferred:
|
|
174
|
+
def checkForInlineCallbackYield(result):
|
|
175
|
+
if inspect.isgenerator(result):
|
|
176
|
+
logger.error(
|
|
177
|
+
"Module Name %s, Class Name %s, Function Name %s"
|
|
178
|
+
" has a yield in it. IT SHOULD NOT.",
|
|
179
|
+
funcToWrap.__module__,
|
|
180
|
+
funcToWrap.__class__.__name__,
|
|
181
|
+
funcToWrap.__name__,
|
|
182
|
+
)
|
|
183
|
+
raise YieldInDeferToThreadException()
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
def func(*args, **kwargs):
|
|
187
|
+
if not twisted.python.threadable.isInIOThread() and checkMainThread:
|
|
188
|
+
logger.error(
|
|
189
|
+
"Module Name %s, Class Name %s, Function Name %s"
|
|
190
|
+
" is being deferred from a thread. IT SHOULD NOT.",
|
|
191
|
+
funcToWrap.__module__,
|
|
192
|
+
funcToWrap.__class__.__name__,
|
|
193
|
+
funcToWrap.__name__,
|
|
194
|
+
)
|
|
195
|
+
raise Exception(
|
|
196
|
+
"Deferring to a thread can only be done from the main thread"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
d = deferToThread(funcToWrap, *args, **kwargs)
|
|
200
|
+
d.addCallback(checkForInlineCallbackYield)
|
|
201
|
+
d.addErrback(vortexLogFailure, logger, consumeError=consumeError)
|
|
202
|
+
return d
|
|
203
|
+
|
|
204
|
+
return func
|
|
205
|
+
|
|
206
|
+
return wrapper
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def isMainThread():
|
|
210
|
+
return twisted.python.threadable.isInIOThread()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def noMainThread():
|
|
214
|
+
if isMainThread():
|
|
215
|
+
raise Exception(
|
|
216
|
+
"Blocking operations shouldn't occur in the reactors thread."
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def yesMainThread():
|
|
221
|
+
if not isMainThread():
|
|
222
|
+
raise Exception(
|
|
223
|
+
"Async operations must occur in the reactors main thread."
|
|
224
|
+
)
|
vortex/Jsonable.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
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 traceback
|
|
12
|
+
import typing
|
|
13
|
+
from copy import copy, deepcopy
|
|
14
|
+
from json import JSONEncoder
|
|
15
|
+
from typing import List
|
|
16
|
+
from _collections import defaultdict
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
from .SerialiseUtil import *
|
|
21
|
+
|
|
22
|
+
###############################################################################
|
|
23
|
+
# JSON Constants
|
|
24
|
+
###############################################################################
|
|
25
|
+
|
|
26
|
+
# Globally mess with the json dump separators for more compact json
|
|
27
|
+
JSONEncoder.item_separator = ","
|
|
28
|
+
JSONEncoder.key_separator = ":"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Jsonable(object):
|
|
32
|
+
"""Xmlable
|
|
33
|
+
Inherit from this class if you want to serialise to XML.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
__fieldNames__: List[str] = None
|
|
37
|
+
__rapuiSerialiseType__ = T_GENERIC_CLASS
|
|
38
|
+
|
|
39
|
+
__rawJonableFields__: List[str] = None
|
|
40
|
+
|
|
41
|
+
JSON_CLASS_TYPE = "_ct"
|
|
42
|
+
JSON_CLASS = "_c"
|
|
43
|
+
JSON_TUPLE_TYPE = "_c"
|
|
44
|
+
JSON_FIELD_TYPE = "_ft"
|
|
45
|
+
JSON_FIELD_DATA = "_fd"
|
|
46
|
+
|
|
47
|
+
__memoryLoggingRefs = None
|
|
48
|
+
__memoryLoggingEnabled = False
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def setupMemoryLogging(cls) -> None:
|
|
52
|
+
cls.__memoryLoggingRefs = defaultdict(lambda: 0)
|
|
53
|
+
cls.__memoryLoggingEnabled = True
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def memoryLoggingDump(
|
|
57
|
+
cls, top=10, over=100
|
|
58
|
+
) -> List[typing.Tuple[str, int]]:
|
|
59
|
+
if not Jsonable.__memoryLoggingRefs:
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
data = sorted(
|
|
63
|
+
Jsonable.__memoryLoggingRefs.items(),
|
|
64
|
+
key=lambda x: x[1],
|
|
65
|
+
reverse=True,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return list(filter(lambda x: x[1] >= over, data))[:top]
|
|
69
|
+
|
|
70
|
+
def __memLoggingKey(self):
|
|
71
|
+
if getattr(self, "__rapuiSerialiseType__") == T_RAPUI_TUPLE:
|
|
72
|
+
key = "Tuple: " + getattr(self, "__tupleType__")
|
|
73
|
+
|
|
74
|
+
elif getattr(self, "__rapuiSerialiseType__") == T_RAPUI_PAYLOAD:
|
|
75
|
+
assert hasattr(self, "filt"), "Payload is missing filt"
|
|
76
|
+
if "key" in self.filt:
|
|
77
|
+
key = "Payload: key=" + self.filt["key"]
|
|
78
|
+
else:
|
|
79
|
+
key = "Payload: " + str(self.filt)
|
|
80
|
+
|
|
81
|
+
else:
|
|
82
|
+
key = "Jsonable: " + getattr(self, "__rapuiSerialiseType__")
|
|
83
|
+
|
|
84
|
+
return key
|
|
85
|
+
|
|
86
|
+
def __init__(self):
|
|
87
|
+
if Jsonable.__memoryLoggingEnabled:
|
|
88
|
+
Jsonable.__memoryLoggingRefs[self.__memLoggingKey()] += 1
|
|
89
|
+
|
|
90
|
+
def __del__(self):
|
|
91
|
+
if Jsonable.__memoryLoggingEnabled:
|
|
92
|
+
Jsonable.__memoryLoggingRefs[self.__memLoggingKey()] -= 1
|
|
93
|
+
|
|
94
|
+
def __isRawJsonableField(self, name: str) -> bool:
|
|
95
|
+
if not (name and self.__rawJonableFields__):
|
|
96
|
+
return False
|
|
97
|
+
return name in self.__rawJonableFields__
|
|
98
|
+
|
|
99
|
+
def toJsonField(self, value, jsonDict=None, name=None):
|
|
100
|
+
"""To Json Field"""
|
|
101
|
+
from .Tuple import TupleField
|
|
102
|
+
|
|
103
|
+
if isinstance(value, TupleField):
|
|
104
|
+
value = (
|
|
105
|
+
None if value.defaultValue == None else copy(value.defaultValue)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
valueType = V_NULL if value is None else toRapuiType(value)
|
|
110
|
+
except KeyError as e:
|
|
111
|
+
raise KeyError(
|
|
112
|
+
"%s field name is %s, type is %s", e, name, value.__class__
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Payloads and Tuples
|
|
116
|
+
if self.__isRawJsonableField(name):
|
|
117
|
+
# This sho
|
|
118
|
+
convertedValue = deepcopy(value)
|
|
119
|
+
|
|
120
|
+
elif isinstance(value, Jsonable):
|
|
121
|
+
convertedValue = value.toJsonDict()
|
|
122
|
+
|
|
123
|
+
elif isinstance(value, dict):
|
|
124
|
+
convertedValue = {}
|
|
125
|
+
for key, value in list(value.items()):
|
|
126
|
+
self.toJsonField(value, convertedValue, key)
|
|
127
|
+
|
|
128
|
+
elif isinstance(value, (set, list, tuple)):
|
|
129
|
+
convertedValue = []
|
|
130
|
+
for val in value:
|
|
131
|
+
convertedValue.append(self.toJsonField(val))
|
|
132
|
+
|
|
133
|
+
# Decimals are detected as floats for some reason, so convert it here
|
|
134
|
+
elif valueType == T_FLOAT:
|
|
135
|
+
convertedValue = float(value)
|
|
136
|
+
|
|
137
|
+
elif valueType in (T_INT, T_BOOL, T_STR):
|
|
138
|
+
convertedValue = value
|
|
139
|
+
|
|
140
|
+
elif valueType == V_NULL:
|
|
141
|
+
convertedValue = None
|
|
142
|
+
|
|
143
|
+
else:
|
|
144
|
+
convertedValue = toStr(value)
|
|
145
|
+
|
|
146
|
+
# Non standard values need a dict to store their value type attributes
|
|
147
|
+
# Create a sub dict that contains the value and type
|
|
148
|
+
if valueType not in (
|
|
149
|
+
T_FLOAT,
|
|
150
|
+
T_STR,
|
|
151
|
+
V_NULL,
|
|
152
|
+
T_BOOL,
|
|
153
|
+
T_LIST,
|
|
154
|
+
T_DICT,
|
|
155
|
+
) and not isinstance(value, Jsonable):
|
|
156
|
+
convertedValue = {
|
|
157
|
+
Jsonable.JSON_FIELD_TYPE: valueType,
|
|
158
|
+
Jsonable.JSON_FIELD_DATA: convertedValue,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if name and jsonDict is not None:
|
|
162
|
+
# Now assign the value and it's data type if applicable
|
|
163
|
+
if not toRapuiType(name) in (T_STR, T_INT, T_FLOAT):
|
|
164
|
+
raise Exception(
|
|
165
|
+
"name=%s, type=%s, is not an allowed dict key",
|
|
166
|
+
name,
|
|
167
|
+
toRapuiType(name),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
jsonDict[name] = convertedValue
|
|
171
|
+
|
|
172
|
+
return convertedValue
|
|
173
|
+
|
|
174
|
+
# -----------------------------------------------------------------------------
|
|
175
|
+
def fromJsonField(self, value, valueType=None):
|
|
176
|
+
# Single Value
|
|
177
|
+
if (
|
|
178
|
+
valueType == V_NULL or value is None
|
|
179
|
+
): # V_NULL will never be set in toJsonField
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
if valueType == T_INT:
|
|
183
|
+
return int(value)
|
|
184
|
+
|
|
185
|
+
if isinstance(value, dict) and Jsonable.JSON_CLASS_TYPE in value:
|
|
186
|
+
valueType = value[Jsonable.JSON_CLASS_TYPE]
|
|
187
|
+
|
|
188
|
+
# JSON handles these types natively,
|
|
189
|
+
# if there is no type then these are the right types
|
|
190
|
+
if valueType is None:
|
|
191
|
+
valueType = toRapuiType(value)
|
|
192
|
+
if valueType in (T_BOOL, T_FLOAT, T_INT, T_STR):
|
|
193
|
+
return value
|
|
194
|
+
|
|
195
|
+
# Non standard values need a dict to store their value type attributes, decode these
|
|
196
|
+
if isinstance(value, dict) and Jsonable.JSON_FIELD_TYPE in value:
|
|
197
|
+
return self.fromJsonField(
|
|
198
|
+
value[Jsonable.JSON_FIELD_DATA], value[Jsonable.JSON_FIELD_TYPE]
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Tuple
|
|
202
|
+
if valueType == T_RAPUI_TUPLE:
|
|
203
|
+
if not Jsonable.JSON_TUPLE_TYPE in value:
|
|
204
|
+
raise Exception(
|
|
205
|
+
"Tuple is missing type field '_c',"
|
|
206
|
+
" if this is a javascript tuple,"
|
|
207
|
+
" ensure it's constructor calls super()"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
tupleType = value[Jsonable.JSON_TUPLE_TYPE]
|
|
211
|
+
|
|
212
|
+
from .Tuple import TUPLE_TYPES_BY_NAME
|
|
213
|
+
|
|
214
|
+
if not tupleType in TUPLE_TYPES_BY_NAME:
|
|
215
|
+
raise Exception(
|
|
216
|
+
"Tuple type |%s| not registered within this program.",
|
|
217
|
+
tupleType,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
from .Tuple import TUPLE_TYPES_BY_NAME
|
|
222
|
+
|
|
223
|
+
return TUPLE_TYPES_BY_NAME[tupleType]().fromJsonDict(value)
|
|
224
|
+
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logger.critical(traceback.format_exc())
|
|
227
|
+
raise Exception("%s for tuple type %s" % (str(e), tupleType))
|
|
228
|
+
|
|
229
|
+
# Handle the case of payloads within payloads
|
|
230
|
+
if valueType == T_RAPUI_PAYLOAD:
|
|
231
|
+
from .Payload import Payload
|
|
232
|
+
|
|
233
|
+
return Payload().fromJsonDict(value)
|
|
234
|
+
|
|
235
|
+
if valueType == T_RAPUI_PAYLOAD_ENVELOPE:
|
|
236
|
+
from .PayloadEnvelope import PayloadEnvelope
|
|
237
|
+
|
|
238
|
+
return PayloadEnvelope().fromJsonDict(value)
|
|
239
|
+
|
|
240
|
+
if valueType == T_GENERIC_CLASS:
|
|
241
|
+
# OTHER JSONABLES GO HERE, INSPECT THE tuple_TYPE
|
|
242
|
+
# jsonClass = value[JSON_CLASS]
|
|
243
|
+
raise NotImplementedError()
|
|
244
|
+
# import importlib
|
|
245
|
+
# MyClass = getattr(importlib.import_module("module.submodule"), "Klass")
|
|
246
|
+
# instance = MyClass()
|
|
247
|
+
|
|
248
|
+
if valueType == T_DICT:
|
|
249
|
+
# Dict
|
|
250
|
+
restoredDict = {}
|
|
251
|
+
for subName, subVal in list(value.items()):
|
|
252
|
+
try:
|
|
253
|
+
subNamStr = subName
|
|
254
|
+
subName = float(subNamStr)
|
|
255
|
+
subName = int(subNamStr)
|
|
256
|
+
except ValueError:
|
|
257
|
+
pass
|
|
258
|
+
restoredDict[subName] = self.fromJsonField(subVal)
|
|
259
|
+
|
|
260
|
+
return restoredDict
|
|
261
|
+
|
|
262
|
+
if valueType == T_LIST:
|
|
263
|
+
restoredList = []
|
|
264
|
+
for subVal in value:
|
|
265
|
+
restoredList.append(self.fromJsonField(subVal))
|
|
266
|
+
|
|
267
|
+
return restoredList
|
|
268
|
+
|
|
269
|
+
return fromStr(value, valueType)
|
|
270
|
+
|
|
271
|
+
def toJsonDict(self):
|
|
272
|
+
|
|
273
|
+
fieldNames = self.__fieldNames__
|
|
274
|
+
|
|
275
|
+
jsonDict = {Jsonable.JSON_CLASS_TYPE: self.__rapuiSerialiseType__}
|
|
276
|
+
|
|
277
|
+
if hasattr(self, "tupleName"):
|
|
278
|
+
jsonDict[Jsonable.JSON_TUPLE_TYPE] = self.tupleName()
|
|
279
|
+
|
|
280
|
+
else:
|
|
281
|
+
jsonDict[Jsonable.JSON_CLASS] = className(self)
|
|
282
|
+
|
|
283
|
+
for name in fieldNames:
|
|
284
|
+
data = getattr(self, name)
|
|
285
|
+
self.toJsonField(data, jsonDict, name)
|
|
286
|
+
|
|
287
|
+
return jsonDict
|
|
288
|
+
|
|
289
|
+
def fromJsonDict(self, jsonDict):
|
|
290
|
+
"""From Xml
|
|
291
|
+
Returns and instance of this object populated with data from the json dict
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
# Use the fromJsonField code to convert all the values
|
|
295
|
+
for name, value in list(jsonDict.items()):
|
|
296
|
+
if name.startswith("_"):
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
if self.__isRawJsonableField(name):
|
|
300
|
+
setattr(self, name, value)
|
|
301
|
+
else:
|
|
302
|
+
setattr(self, name, self.fromJsonField(value))
|
|
303
|
+
|
|
304
|
+
return self
|
vortex/Payload.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
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 zlib
|
|
11
|
+
from base64 import b64encode, b64decode
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import List, Optional, Dict
|
|
14
|
+
|
|
15
|
+
import pytz
|
|
16
|
+
import json
|
|
17
|
+
|
|
18
|
+
from vortex.DeferUtil import deferToThreadWrapWithLogger, noMainThread
|
|
19
|
+
from .Jsonable import Jsonable
|
|
20
|
+
from .SerialiseUtil import T_RAPUI_PAYLOAD
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
PayloadList = List["Payload"]
|
|
25
|
+
EncodedPayloadList = List[bytes]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Payload(Jsonable):
|
|
29
|
+
"""Payload
|
|
30
|
+
This object represents a hierarchy of data transferred between client and server
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
__fieldNames__ = ["filt", "tuples", "date"]
|
|
34
|
+
__rapuiSerialiseType__ = T_RAPUI_PAYLOAD
|
|
35
|
+
|
|
36
|
+
def __init__(self, filt: Optional[Dict] = None, tuples=None) -> None:
|
|
37
|
+
"""Constructor"""
|
|
38
|
+
super().__init__()
|
|
39
|
+
self.filt = {} if filt is None else filt
|
|
40
|
+
self.tuples = [] if tuples is None else tuples
|
|
41
|
+
self.date = datetime.now(pytz.utc)
|
|
42
|
+
|
|
43
|
+
if isinstance(filt, str):
|
|
44
|
+
self.filt = {self.filt: None}
|
|
45
|
+
|
|
46
|
+
if not isinstance(self.tuples, list):
|
|
47
|
+
self.tuples = [self.tuples]
|
|
48
|
+
|
|
49
|
+
# -------------------------------------------
|
|
50
|
+
# PayloadEnvelopes helper messages
|
|
51
|
+
|
|
52
|
+
def makePayloadEnvelope(self, result=None, compressionLevel: int = 9):
|
|
53
|
+
from .PayloadEnvelope import PayloadEnvelope
|
|
54
|
+
|
|
55
|
+
noMainThread()
|
|
56
|
+
encodedSelf = self.toEncodedPayload(compressionLevel=compressionLevel)
|
|
57
|
+
return PayloadEnvelope(
|
|
58
|
+
self.filt, encodedPayload=encodedSelf, date=self.date, result=result
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
@deferToThreadWrapWithLogger(logger)
|
|
62
|
+
def makePayloadEnvelopeDefer(self, result=None, compressionLevel: int = 9):
|
|
63
|
+
return self.makePayloadEnvelope(
|
|
64
|
+
result=result, compressionLevel=compressionLevel
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# -------------------------------------------
|
|
68
|
+
# VortexMsg helper messages
|
|
69
|
+
|
|
70
|
+
def makePayloadEnvelopeVortexMsg(
|
|
71
|
+
self, result=None, compressionLevel: int = 9
|
|
72
|
+
):
|
|
73
|
+
noMainThread()
|
|
74
|
+
return self.makePayloadEnvelope(
|
|
75
|
+
result=result, compressionLevel=compressionLevel
|
|
76
|
+
).toVortexMsg()
|
|
77
|
+
|
|
78
|
+
@deferToThreadWrapWithLogger(logger)
|
|
79
|
+
def makePayloadEnvelopeVortexMsgDefer(
|
|
80
|
+
self, result=None, compressionLevel: int = 9
|
|
81
|
+
):
|
|
82
|
+
return self.makePayloadEnvelopeVortexMsg(
|
|
83
|
+
result=result, compressionLevel=compressionLevel
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# -------------------------------------------
|
|
87
|
+
# JSON Related methods
|
|
88
|
+
def _fromJson(self, jsonStr):
|
|
89
|
+
jsonDict = json.loads(jsonStr)
|
|
90
|
+
|
|
91
|
+
assert jsonDict[Jsonable.JSON_CLASS_TYPE] == self.__rapuiSerialiseType__
|
|
92
|
+
return self.fromJsonDict(jsonDict)
|
|
93
|
+
|
|
94
|
+
def _toJson(self) -> str:
|
|
95
|
+
return json.dumps(self.toJsonDict())
|
|
96
|
+
|
|
97
|
+
# -------------------------------------------
|
|
98
|
+
# VortexServer Message Methods
|
|
99
|
+
def toEncodedPayload(self, compressionLevel: int = 9) -> str:
|
|
100
|
+
jsonStr = self._toJson()
|
|
101
|
+
return b64encode(
|
|
102
|
+
zlib.compress(jsonStr.encode("UTF-8"), compressionLevel)
|
|
103
|
+
).decode()
|
|
104
|
+
|
|
105
|
+
@deferToThreadWrapWithLogger(logger)
|
|
106
|
+
def toEncodedPayloadDefer(self, compressionLevel: int = 9) -> str:
|
|
107
|
+
return self.toEncodedPayload(compressionLevel=compressionLevel)
|
|
108
|
+
|
|
109
|
+
def fromEncodedPayload(self, encodedPayload: str):
|
|
110
|
+
jsonStr = zlib.decompress(b64decode(encodedPayload))
|
|
111
|
+
return self._fromJson(jsonStr)
|
|
112
|
+
|
|
113
|
+
@deferToThreadWrapWithLogger(logger)
|
|
114
|
+
def fromEncodedPayloadDefer(self, encodedPayload: str):
|
|
115
|
+
return self.fromEncodedPayload(encodedPayload)
|