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,173 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from copy import copy
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
import pytz
|
|
9
|
+
from twisted.internet import reactor
|
|
10
|
+
from twisted.internet.defer import Deferred
|
|
11
|
+
from twisted.python.failure import Failure
|
|
12
|
+
|
|
13
|
+
from vortex.PayloadEndpoint import PayloadEndpoint
|
|
14
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
15
|
+
from vortex.VortexFactory import VortexFactory
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PayloadResponse(Deferred):
|
|
21
|
+
"""Payload Response
|
|
22
|
+
|
|
23
|
+
This class is used to catch responses from a sent payload.
|
|
24
|
+
If the remote end is going to send back a payload, with the same filt, this class can
|
|
25
|
+
be used to capture this can and call it's call back.
|
|
26
|
+
|
|
27
|
+
If the response is not received within the timeout, the errback is called.
|
|
28
|
+
|
|
29
|
+
Here is some example usage.
|
|
30
|
+
|
|
31
|
+
::
|
|
32
|
+
|
|
33
|
+
logger = logging.getLogger("Example Payload Response")
|
|
34
|
+
payload = Payload(filt={"rapuiServerEcho":True})
|
|
35
|
+
responseDeferred = PayloadResponse(payload)
|
|
36
|
+
VortexFactory.sendVortexMsg(payload.toVortexMsg())
|
|
37
|
+
logger.info("Payload Sent")
|
|
38
|
+
|
|
39
|
+
responseDeferred.addCallbacks(logger.info, logger.error)
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
MESSAGE_ID_KEY = "PayloadResponse.messageId"
|
|
44
|
+
|
|
45
|
+
PROCESSING = "Processing"
|
|
46
|
+
# NO_ENDPOINT = "No Endpoint"
|
|
47
|
+
FAILED = "Failed"
|
|
48
|
+
SUCCESS = "Success"
|
|
49
|
+
TIMED_OUT = "Timed Out"
|
|
50
|
+
|
|
51
|
+
TIMEOUT: float = 30.00
|
|
52
|
+
|
|
53
|
+
__SEQ = 1
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
payloadEnvelope: PayloadEnvelope,
|
|
58
|
+
destVortexName: Optional[str] = None,
|
|
59
|
+
destVortexUuid: Optional[str] = None,
|
|
60
|
+
timeout: Optional[float] = None,
|
|
61
|
+
resultCheck=True,
|
|
62
|
+
logTimeoutError=True,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Constructor
|
|
65
|
+
|
|
66
|
+
Tag and optionally send a payload.
|
|
67
|
+
|
|
68
|
+
The timeout starts as soon as the constructor is called.
|
|
69
|
+
|
|
70
|
+
:param payloadEnvelope The payloadEnvelope to send to the remote and, and wait for a response for
|
|
71
|
+
:param destVortexName The name of the vortex to send to.
|
|
72
|
+
:param destVortexUuid The UUID of the vortex to send a payload to.
|
|
73
|
+
:param timeout The timeout to wait for a response
|
|
74
|
+
:param resultCheck Should the response payload.result be checked, if it fails
|
|
75
|
+
it errback will be called.
|
|
76
|
+
"""
|
|
77
|
+
Deferred.__init__(self)
|
|
78
|
+
|
|
79
|
+
if not timeout:
|
|
80
|
+
timeout = self.TIMEOUT
|
|
81
|
+
|
|
82
|
+
self._resultCheck = resultCheck
|
|
83
|
+
self._logTimeoutError = logTimeoutError
|
|
84
|
+
|
|
85
|
+
# uuid4 can have collisions
|
|
86
|
+
self._messageId = str(uuid4()) + str(PayloadResponse.__SEQ)
|
|
87
|
+
PayloadResponse.__SEQ += 1
|
|
88
|
+
|
|
89
|
+
payloadEnvelope.filt[self.MESSAGE_ID_KEY] = self._messageId
|
|
90
|
+
self._filt = copy(payloadEnvelope.filt)
|
|
91
|
+
self._destVortexName = destVortexName
|
|
92
|
+
|
|
93
|
+
self._status = self.PROCESSING
|
|
94
|
+
self._date = datetime.now(pytz.utc)
|
|
95
|
+
|
|
96
|
+
self._endpoint = PayloadEndpoint(self._filt, self._process)
|
|
97
|
+
|
|
98
|
+
if destVortexName or destVortexUuid:
|
|
99
|
+
d: Deferred = payloadEnvelope.toVortexMsgDefer()
|
|
100
|
+
d.addCallback(
|
|
101
|
+
VortexFactory.sendVortexMsg,
|
|
102
|
+
destVortexName=destVortexName,
|
|
103
|
+
destVortexUuid=destVortexUuid,
|
|
104
|
+
)
|
|
105
|
+
d.addErrback(self.errback)
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
raise Exception()
|
|
109
|
+
except:
|
|
110
|
+
self._stack = sys.exc_info()[2]
|
|
111
|
+
|
|
112
|
+
# noinspection PyTypeChecker
|
|
113
|
+
self.addTimeout(timeout, reactor)
|
|
114
|
+
self.addErrback(self._timedOut)
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def isResponsePayloadEnvelope(cls, payloadEnvelope: PayloadEnvelope):
|
|
118
|
+
"""Is Response Payload Envelope
|
|
119
|
+
|
|
120
|
+
The PayloadResponse tags the payloads, so it expects a unique message back.
|
|
121
|
+
|
|
122
|
+
:returns: True if this payload has been tagged by a PayloadResponse class
|
|
123
|
+
"""
|
|
124
|
+
return cls.MESSAGE_ID_KEY in payloadEnvelope.filt
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def status(self):
|
|
128
|
+
return self._status
|
|
129
|
+
|
|
130
|
+
def _timedOut(self, failure: Failure):
|
|
131
|
+
if self._endpoint:
|
|
132
|
+
self._endpoint.shutdown()
|
|
133
|
+
self._endpoint = None
|
|
134
|
+
|
|
135
|
+
if self._logTimeoutError:
|
|
136
|
+
logger.error("Timed out for payload %s", self._filt)
|
|
137
|
+
self._status = self.TIMED_OUT
|
|
138
|
+
return failure
|
|
139
|
+
|
|
140
|
+
def _process(self, payloadEnvelope: PayloadEnvelope, vortexName, **kwargs):
|
|
141
|
+
if self._endpoint:
|
|
142
|
+
self._endpoint.shutdown()
|
|
143
|
+
self._endpoint = None
|
|
144
|
+
|
|
145
|
+
if self._destVortexName and vortexName != self._destVortexName:
|
|
146
|
+
logger.debug(
|
|
147
|
+
"Received response from a vortex other than the dest vortex, "
|
|
148
|
+
"Expected %s, Received %s",
|
|
149
|
+
self._destVortexName,
|
|
150
|
+
vortexName,
|
|
151
|
+
)
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
if self.called:
|
|
155
|
+
logger.error("Received response after timeout for %s" % self._filt)
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
if self._resultCheck and not payloadEnvelope.result in (None, True):
|
|
159
|
+
self._status = self.FAILED
|
|
160
|
+
self.errback(
|
|
161
|
+
Failure(
|
|
162
|
+
Exception(payloadEnvelope.result).with_traceback(
|
|
163
|
+
self._stack
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
else:
|
|
169
|
+
self._status = self.SUCCESS
|
|
170
|
+
self.callback(payloadEnvelope)
|
|
171
|
+
|
|
172
|
+
# Return self, so that PayloadIO knows this is a asyncronous method
|
|
173
|
+
return self
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from twisted.internet import defer
|
|
2
|
+
from twisted.trial import unittest
|
|
3
|
+
|
|
4
|
+
from vortex.Payload import Payload
|
|
5
|
+
from vortex.PayloadIO import PayloadIO
|
|
6
|
+
from vortex.PayloadResponse import PayloadResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PayloadResponseTest(unittest.TestCase):
|
|
10
|
+
def testSuccesCb(self):
|
|
11
|
+
payload = Payload(filt={"key": "PayloadResponseTest"})
|
|
12
|
+
|
|
13
|
+
payloadReponse = PayloadResponse(payload)
|
|
14
|
+
payloadReponse.addCallback(lambda pl: self.assertIs(pl, payload))
|
|
15
|
+
payloadReponse.addErrback(lambda pl: self.assertTrue(False))
|
|
16
|
+
|
|
17
|
+
PayloadIO().process(payload, "", "", None, lambda _: None)
|
|
18
|
+
|
|
19
|
+
return payloadReponse
|
|
20
|
+
|
|
21
|
+
def testTimeoutErrback(self):
|
|
22
|
+
payload = Payload(filt={"key": "PayloadResponseTest"})
|
|
23
|
+
|
|
24
|
+
payloadReponse = PayloadResponse(payload, timeout=3)
|
|
25
|
+
payloadReponse.addCallback(lambda pl: self.assertFalse(True))
|
|
26
|
+
self.assertFailure(payloadReponse, defer.TimeoutError)
|
|
27
|
+
|
|
28
|
+
# PayloadIO().process(payload)
|
|
29
|
+
|
|
30
|
+
return payloadReponse
|
|
31
|
+
|
|
32
|
+
def testSerialiseDeserialise(self):
|
|
33
|
+
payload = Payload(filt={"key": "PayloadResponseTest"})
|
|
34
|
+
|
|
35
|
+
d = PayloadResponse(payload, timeout=0)
|
|
36
|
+
d.addErrback(lambda _: True) # Handle the errback
|
|
37
|
+
|
|
38
|
+
vortexMsg = payload.toVortexMsg()
|
|
39
|
+
Payload().fromVortexMsg(vortexMsg)
|
vortex/PayloadTest.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
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 gc
|
|
10
|
+
|
|
11
|
+
from twisted.trial import unittest
|
|
12
|
+
|
|
13
|
+
from vortex.Payload import Payload
|
|
14
|
+
from vortex.PayloadFilterKeys import rapuiClientEcho
|
|
15
|
+
from vortex.Tuple import TupleHash
|
|
16
|
+
from vortex.VortexTcpMemoryLeakPayloadTest import MemoryCheckerTestMixin
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def makeTestPayloadA(tupleDataSize=10 * 1024):
|
|
20
|
+
payload = Payload()
|
|
21
|
+
payload.filt[rapuiClientEcho] = None
|
|
22
|
+
payload.tuples = ["12345678" * int(tupleDataSize / 8)]
|
|
23
|
+
return payload
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PayloadPyTest(unittest.TestCase, MemoryCheckerTestMixin):
|
|
27
|
+
PERF_TEST_COUNT = 10000
|
|
28
|
+
|
|
29
|
+
def setUp(self):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def tearDown(self):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
# @profile
|
|
36
|
+
def _makeStr(self, size):
|
|
37
|
+
_ = ["12345678" * int(size / 8)]
|
|
38
|
+
del _
|
|
39
|
+
gc.collect()
|
|
40
|
+
|
|
41
|
+
# @profile
|
|
42
|
+
def _payloadEncodeDecode(self, size):
|
|
43
|
+
origPayload = makeTestPayloadA(size)
|
|
44
|
+
origPayload.date = None
|
|
45
|
+
|
|
46
|
+
encodedPayload = origPayload.toEncodedPayload()
|
|
47
|
+
payload = Payload().fromEncodedPayload(encodedPayload)
|
|
48
|
+
|
|
49
|
+
self.assertEqual(payload.tuples[0], origPayload.tuples[0])
|
|
50
|
+
|
|
51
|
+
def testMakeStr_100m_x100(self):
|
|
52
|
+
self._memCheckFunction(self._makeStr, 1000, 100 * 1024**2)
|
|
53
|
+
|
|
54
|
+
def testPayloadToFromEncodedPayload_10k_x1000(self):
|
|
55
|
+
self._memCheckFunction(self._payloadEncodeDecode, 1000, 10 * 1024)
|
|
56
|
+
|
|
57
|
+
def testPayloadToFromEncodedPayload_100m_x100(self):
|
|
58
|
+
self._memCheckFunction(self._payloadEncodeDecode, 100, 10 * 1024**2)
|
|
59
|
+
|
|
60
|
+
def testPayloadToFromEncodedPayload_1m_x1000(self):
|
|
61
|
+
self._memCheckFunction(self._payloadEncodeDecode, 1000, 1 * 1024**2)
|
|
62
|
+
|
|
63
|
+
def testPayloadToFromEncodedPayload10mb(self):
|
|
64
|
+
# Create Payload
|
|
65
|
+
origPayload = makeTestPayloadA()
|
|
66
|
+
origPayload.tuples = ["1234567890" * 1024**2]
|
|
67
|
+
|
|
68
|
+
encodedPayload = origPayload.toEncodedPayload()
|
|
69
|
+
payload = Payload().fromEncodedPayload(encodedPayload)
|
|
70
|
+
|
|
71
|
+
self.assertEqual(payload.tuples[0], origPayload.tuples[0])
|
|
72
|
+
|
|
73
|
+
def testPayloadToFromEncodedPayload(self):
|
|
74
|
+
# Create Payload
|
|
75
|
+
origPayload = makeTestPayloadA()
|
|
76
|
+
|
|
77
|
+
# To VortexServer Message
|
|
78
|
+
encodedPayload = origPayload.toEncodedPayload()
|
|
79
|
+
# print encodedPayload
|
|
80
|
+
|
|
81
|
+
payload = Payload().fromEncodedPayload(encodedPayload)
|
|
82
|
+
|
|
83
|
+
self.assertEqual(TupleHash(payload), TupleHash(payload))
|
|
84
|
+
self.assertEqual(TupleHash(origPayload), TupleHash(origPayload))
|
|
85
|
+
|
|
86
|
+
self.assertEqual(
|
|
87
|
+
TupleHash(origPayload),
|
|
88
|
+
TupleHash(payload),
|
|
89
|
+
"Payload serialise -> deserialize error",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def testPayloadToFromJson(self):
|
|
93
|
+
# Create Payload
|
|
94
|
+
origPayload = makeTestPayloadA()
|
|
95
|
+
|
|
96
|
+
# To JSON
|
|
97
|
+
jsonStr = origPayload._toJson()
|
|
98
|
+
# print encodedPayload
|
|
99
|
+
# print(jsonStr)
|
|
100
|
+
|
|
101
|
+
payload = Payload()._fromJson(jsonStr)
|
|
102
|
+
|
|
103
|
+
self.assertEqual(TupleHash(payload), TupleHash(payload))
|
|
104
|
+
self.assertEqual(TupleHash(origPayload), TupleHash(origPayload))
|
|
105
|
+
|
|
106
|
+
self.assertEqual(
|
|
107
|
+
TupleHash(origPayload),
|
|
108
|
+
TupleHash(payload),
|
|
109
|
+
"Payload serialise -> deserialise error",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def testPayloadJsonPerformanceSingle(self):
|
|
113
|
+
origPayload = makeTestPayloadA()
|
|
114
|
+
for _ in range(self.PERF_TEST_COUNT):
|
|
115
|
+
origPayload.tuples.append(makeTestPayloadA())
|
|
116
|
+
jsonStr = origPayload._toJson()
|
|
117
|
+
payload = Payload()._fromJson(jsonStr)
|
|
118
|
+
|
|
119
|
+
def testPayloadEncodedPayloadPerformanceSingle(self):
|
|
120
|
+
origPayload = makeTestPayloadA()
|
|
121
|
+
for x in range(self.PERF_TEST_COUNT):
|
|
122
|
+
origPayload.tuples.append(makeTestPayloadA())
|
|
123
|
+
encodedPayload = origPayload.toEncodedPayload()
|
|
124
|
+
payload = Payload().fromEncodedPayload(encodedPayload)
|
|
125
|
+
|
|
126
|
+
def testPayloadJsonPerformanceMultiple(self):
|
|
127
|
+
origPayload = makeTestPayloadA()
|
|
128
|
+
for _ in range(self.PERF_TEST_COUNT):
|
|
129
|
+
jsonStr = origPayload._toJson()
|
|
130
|
+
payload = Payload()._fromJson(jsonStr)
|
|
131
|
+
|
|
132
|
+
def testPayloadEncodedPayloadPerformanceMultiple(self):
|
|
133
|
+
origPayload = makeTestPayloadA()
|
|
134
|
+
for _ in range(self.PERF_TEST_COUNT):
|
|
135
|
+
encodedPayload = origPayload.toEncodedPayload()
|
|
136
|
+
payload = Payload().fromEncodedPayload(encodedPayload)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
*
|
|
3
|
+
* Copyright Synerty Pty Ltd 2013
|
|
4
|
+
*
|
|
5
|
+
* This software is proprietary, you are not free to copy
|
|
6
|
+
* or redistribute this code in any format.
|
|
7
|
+
*
|
|
8
|
+
* All rights to this software are reserved by
|
|
9
|
+
* Synerty Pty Ltd
|
|
10
|
+
*
|
|
11
|
+
* Website : http://www.synerty.com
|
|
12
|
+
* Support : support@synerty.com
|
|
13
|
+
*
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from vortex.Tuple import TupleField, Tuple, addTupleType
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@addTupleType
|
|
20
|
+
class PerformTestActionTuple(Tuple):
|
|
21
|
+
"""Perform Test Action Tuple
|
|
22
|
+
|
|
23
|
+
This tuple is used for testing the action code.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
__tupleType__ = "synerty.vortex.PerformTestActionTuple"
|
|
28
|
+
actionDataInt = TupleField(typingType=int)
|
|
29
|
+
actionDataUnicode = TupleField(typingType=str)
|
|
30
|
+
failProcessing = TupleField(typingType=bool)
|
vortex/SerialiseUtil.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
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 decimal
|
|
11
|
+
import logging
|
|
12
|
+
from base64 import b64encode, b64decode
|
|
13
|
+
from collections import defaultdict
|
|
14
|
+
from datetime import datetime, date
|
|
15
|
+
|
|
16
|
+
import pytz
|
|
17
|
+
from sqlalchemy.orm.collections import InstrumentedList
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from geoalchemy2.elements import WKBElement
|
|
23
|
+
|
|
24
|
+
except ImportError:
|
|
25
|
+
|
|
26
|
+
class WKBElement:
|
|
27
|
+
def __init__(self):
|
|
28
|
+
raise NotImplementedError("")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
from geoalchemy2.elements import WKBElement
|
|
33
|
+
|
|
34
|
+
except ImportError:
|
|
35
|
+
|
|
36
|
+
class WKBElement:
|
|
37
|
+
def __init__(self):
|
|
38
|
+
raise NotImplementedError("")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
###############################################################################
|
|
42
|
+
# Utility functions
|
|
43
|
+
###############################################################################
|
|
44
|
+
|
|
45
|
+
T_RAPUI_TUPLE = "rt"
|
|
46
|
+
T_RAPUI_PAYLOAD = "rp"
|
|
47
|
+
T_RAPUI_PAYLOAD_ENVELOPE = "rpe"
|
|
48
|
+
T_GENERIC_CLASS = "gen" # NOT SUPPORTED
|
|
49
|
+
T_FLOAT = "float"
|
|
50
|
+
T_INT = "int"
|
|
51
|
+
T_STR = "str"
|
|
52
|
+
T_BYTES = "bytes"
|
|
53
|
+
T_BOOL = "bool"
|
|
54
|
+
T_DATETIME = "datetime"
|
|
55
|
+
T_DICT = "dict"
|
|
56
|
+
T_LIST = "list"
|
|
57
|
+
T_WKB = "wkb" # Well Known Binary, a part of the GEO library
|
|
58
|
+
|
|
59
|
+
V_NULL = "null"
|
|
60
|
+
V_TRUE = "1"
|
|
61
|
+
V_FALSE = "0"
|
|
62
|
+
|
|
63
|
+
ISO8601_ORA = "YYYY-MM-DD HH24:MI:SS.FF6"
|
|
64
|
+
|
|
65
|
+
# This will print a date using TO_CHAR(), in the format fromStr() below expects
|
|
66
|
+
ISO8601_ORA_WITH_TZ = "YYYY-MM-DD HH24:MI:SS.FF6TZHTZM"
|
|
67
|
+
ISO8601 = "%Y-%m-%d %H:%M:%S.%f%z"
|
|
68
|
+
ISO8601_JS_REGEXP = r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[Z]{1}"
|
|
69
|
+
ISO8601_JS_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
|
|
70
|
+
ISO8601_REGEXP = r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}[+-]\d{4}$"
|
|
71
|
+
ISO8601_NOTZ = "%Y-%m-%d %H:%M:%S.%f"
|
|
72
|
+
|
|
73
|
+
TYPE_MAP_PY_TO_RAPUI = {
|
|
74
|
+
decimal.Decimal: T_FLOAT,
|
|
75
|
+
float: T_FLOAT,
|
|
76
|
+
str: T_STR,
|
|
77
|
+
bytes: T_BYTES,
|
|
78
|
+
int: T_INT,
|
|
79
|
+
bool: T_BOOL,
|
|
80
|
+
datetime: T_DATETIME,
|
|
81
|
+
date: T_DATETIME,
|
|
82
|
+
dict: T_DICT,
|
|
83
|
+
defaultdict: T_DICT,
|
|
84
|
+
list: T_LIST,
|
|
85
|
+
set: T_LIST,
|
|
86
|
+
tuple: T_LIST,
|
|
87
|
+
WKBElement: T_WKB,
|
|
88
|
+
InstrumentedList: T_LIST,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def className(o):
|
|
93
|
+
cls = o if o.__class__ == type else o.__class__
|
|
94
|
+
return str(cls).split("'")[1]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def convertFromShape(shapelyShape):
|
|
98
|
+
from shapely.geometry.polygon import Polygon
|
|
99
|
+
|
|
100
|
+
if isinstance(shapelyShape, Polygon):
|
|
101
|
+
coords = shapelyShape.exterior.coords
|
|
102
|
+
else:
|
|
103
|
+
coords = shapelyShape.coords
|
|
104
|
+
return [{"x": i[0], "y": i[1]} for i in coords]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def convertFromWkbElement(value):
|
|
108
|
+
from geoalchemy2.shape import to_shape
|
|
109
|
+
|
|
110
|
+
return convertFromShape(to_shape(value))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def serialiseWkb(wkb: WKBElement) -> str:
|
|
114
|
+
from geoalchemy2.shape import to_shape
|
|
115
|
+
import shapely
|
|
116
|
+
|
|
117
|
+
return b64encode(shapely.wkb.dumps(to_shape(wkb))).decode()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def deserialiseWkb(wkbB64Data: str) -> WKBElement:
|
|
121
|
+
from geoalchemy2.shape import from_shape
|
|
122
|
+
import shapely
|
|
123
|
+
|
|
124
|
+
return from_shape(shapely.wkb.loads(b64decode(wkbB64Data)))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
TYPE_MAP_RAPUI_TO_PY = {
|
|
128
|
+
T_FLOAT: float,
|
|
129
|
+
T_STR: str,
|
|
130
|
+
T_BYTES: bytes,
|
|
131
|
+
T_INT: int,
|
|
132
|
+
T_BOOL: bool,
|
|
133
|
+
T_DATETIME: datetime,
|
|
134
|
+
T_DICT: dict,
|
|
135
|
+
T_LIST: list,
|
|
136
|
+
T_WKB: deserialiseWkb,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def decimalToStr(dec):
|
|
141
|
+
"""Return string representation of the number in scientific notation.
|
|
142
|
+
|
|
143
|
+
Captures all of the information in the underlying representation.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
if str(dec) == "0E-10":
|
|
147
|
+
return "0"
|
|
148
|
+
|
|
149
|
+
sign = ["", "-"][dec._sign]
|
|
150
|
+
if dec._is_special:
|
|
151
|
+
if dec._exp == "F":
|
|
152
|
+
return sign + "Infinity"
|
|
153
|
+
elif dec._exp == "n":
|
|
154
|
+
return sign + "NaN" + dec._int
|
|
155
|
+
else: # dec._exp == 'N'
|
|
156
|
+
return sign + "sNaN" + dec._int
|
|
157
|
+
|
|
158
|
+
# number of digits of dec._int to left of decimal point
|
|
159
|
+
leftdigits = dec._exp + len(dec._int)
|
|
160
|
+
|
|
161
|
+
# no exponent required
|
|
162
|
+
dotplace = leftdigits
|
|
163
|
+
|
|
164
|
+
if dotplace <= 0:
|
|
165
|
+
intpart = "0"
|
|
166
|
+
fracpart = "." + "0" * (-dotplace) + dec._int
|
|
167
|
+
elif dotplace >= len(dec._int):
|
|
168
|
+
intpart = dec._int + "0" * (dotplace - len(dec._int))
|
|
169
|
+
fracpart = ""
|
|
170
|
+
else:
|
|
171
|
+
intpart = dec._int[:dotplace]
|
|
172
|
+
fracpart = "." + dec._int[dotplace:]
|
|
173
|
+
|
|
174
|
+
if leftdigits == dotplace:
|
|
175
|
+
exp = ""
|
|
176
|
+
else:
|
|
177
|
+
exp = "E%+d" % (leftdigits - dotplace)
|
|
178
|
+
|
|
179
|
+
s = sign + intpart + fracpart + exp
|
|
180
|
+
if "." in s:
|
|
181
|
+
s = s.rstrip("0").rstrip(".")
|
|
182
|
+
assert len(s)
|
|
183
|
+
return s
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def toStr(obj) -> str:
|
|
187
|
+
if isinstance(obj, WKBElement):
|
|
188
|
+
# noinspection PyTypeChecker
|
|
189
|
+
return serialiseWkb(obj)
|
|
190
|
+
|
|
191
|
+
if isinstance(obj, decimal.Decimal):
|
|
192
|
+
return decimalToStr(obj)
|
|
193
|
+
|
|
194
|
+
if isinstance(obj, datetime):
|
|
195
|
+
return obj.strftime(ISO8601)
|
|
196
|
+
|
|
197
|
+
if isinstance(obj, bool):
|
|
198
|
+
return V_TRUE if obj else V_FALSE
|
|
199
|
+
|
|
200
|
+
if isinstance(obj, bytes):
|
|
201
|
+
return b64encode(obj).decode() # to make it a str
|
|
202
|
+
|
|
203
|
+
if isinstance(obj, str):
|
|
204
|
+
return obj
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
return str(obj)
|
|
208
|
+
except:
|
|
209
|
+
logger.critical("Failed to convert the following to string")
|
|
210
|
+
logger.critical(obj)
|
|
211
|
+
raise
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def fromStr(val: str, typeName: str):
|
|
215
|
+
if typeName == T_DATETIME:
|
|
216
|
+
# TODO: Check with ISO8601_JS_REGEXP
|
|
217
|
+
if len(val) == len("2017-06-06T09:40:37.097Z"):
|
|
218
|
+
return datetime.strptime(val, ISO8601_JS_FORMAT).replace(
|
|
219
|
+
tzinfo=pytz.UTC
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if len(val) <= len("2017-06-06 09:40:37.097068"):
|
|
223
|
+
logger.warning(
|
|
224
|
+
"Parsing date time with no timezone: %s Setting TZ to UTC",
|
|
225
|
+
val,
|
|
226
|
+
)
|
|
227
|
+
return datetime.strptime(val, ISO8601_NOTZ).replace(tzinfo=pytz.UTC)
|
|
228
|
+
|
|
229
|
+
return datetime.strptime(val, ISO8601)
|
|
230
|
+
|
|
231
|
+
if typeName == T_BOOL:
|
|
232
|
+
return val == V_TRUE
|
|
233
|
+
|
|
234
|
+
if typeName == T_BYTES:
|
|
235
|
+
return b64decode(val)
|
|
236
|
+
|
|
237
|
+
if typeName in (T_STR):
|
|
238
|
+
return val
|
|
239
|
+
|
|
240
|
+
pyVal = fromRapuiType(typeName)(val)
|
|
241
|
+
|
|
242
|
+
if typeName == T_FLOAT and pyVal.is_integer():
|
|
243
|
+
pyVal = int(pyVal)
|
|
244
|
+
|
|
245
|
+
return pyVal
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def fromRapuiType(typeName: str):
|
|
249
|
+
return TYPE_MAP_RAPUI_TO_PY[typeName]
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def toRapuiType(value):
|
|
253
|
+
if value is None:
|
|
254
|
+
return V_NULL
|
|
255
|
+
|
|
256
|
+
from .Tuple import Tuple
|
|
257
|
+
|
|
258
|
+
if isinstance(value, Tuple):
|
|
259
|
+
return T_RAPUI_TUPLE
|
|
260
|
+
|
|
261
|
+
from .Payload import Payload
|
|
262
|
+
|
|
263
|
+
if isinstance(value, Payload):
|
|
264
|
+
return T_RAPUI_PAYLOAD
|
|
265
|
+
|
|
266
|
+
from .PayloadEnvelope import PayloadEnvelope
|
|
267
|
+
|
|
268
|
+
if isinstance(value, PayloadEnvelope):
|
|
269
|
+
return T_RAPUI_PAYLOAD_ENVELOPE
|
|
270
|
+
|
|
271
|
+
valueClass = value.__class__
|
|
272
|
+
|
|
273
|
+
return TYPE_MAP_PY_TO_RAPUI[valueClass]
|