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,218 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from copy import copy
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
from twisted.internet.defer import Deferred
|
|
7
|
+
from twisted.internet.defer import TimeoutError
|
|
8
|
+
from twisted.internet.defer import inlineCallbacks
|
|
9
|
+
from twisted.python.failure import Failure
|
|
10
|
+
|
|
11
|
+
from vortex.DeferUtil import vortexLogFailure
|
|
12
|
+
from vortex.PayloadEndpoint import PayloadEndpoint
|
|
13
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
14
|
+
from vortex.PayloadResponse import PayloadResponse
|
|
15
|
+
from vortex.VortexABC import SendVortexMsgResponseCallable
|
|
16
|
+
from vortex.VortexFactory import VortexFactory
|
|
17
|
+
from vortex.VortexUtil import limitConcurrency
|
|
18
|
+
from vortex.handler.TupleActionProcessor import TupleActionProcessor
|
|
19
|
+
from vortex.handler.TupleActionProcessor import TupleActionProcessorDelegateABC
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TupleActionProcessorProxy:
|
|
25
|
+
"""Tuple Data Observable Proxy Handler
|
|
26
|
+
|
|
27
|
+
This class proxies the TupleActions onto another destination, giving the ability
|
|
28
|
+
to pass through one services into another. EG, from a client facing python service
|
|
29
|
+
to a server backend.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
_DEBUG_LOGGING = False
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
tupleActionProcessorName,
|
|
37
|
+
proxyToVortexName: str,
|
|
38
|
+
additionalFilt=None,
|
|
39
|
+
acceptOnlyFromVortex: Optional[Union[str, tuple]] = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Constructor
|
|
42
|
+
|
|
43
|
+
:param tupleActionProcessorName: The name of this and the other action handler
|
|
44
|
+
:param proxyToVortexName: The vortex dest name to proxy requests to
|
|
45
|
+
:param additionalFilt: Any additional filter keys that are required
|
|
46
|
+
"""
|
|
47
|
+
self._proxyToVortexName = proxyToVortexName
|
|
48
|
+
self._tupleActionProcessorName = tupleActionProcessorName
|
|
49
|
+
self._delegateProcessor = TupleActionProcessor(
|
|
50
|
+
tupleActionProcessorName=tupleActionProcessorName,
|
|
51
|
+
usedForProxy__=True,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self._filt = dict(
|
|
55
|
+
name=tupleActionProcessorName, key="tupleActionProcessorName"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if additionalFilt:
|
|
59
|
+
self._filt.update(additionalFilt)
|
|
60
|
+
|
|
61
|
+
self._endpoint = PayloadEndpoint(
|
|
62
|
+
self._filt,
|
|
63
|
+
self._processLimited,
|
|
64
|
+
acceptOnlyFromVortex=acceptOnlyFromVortex,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def shutdown(self):
|
|
68
|
+
self._endpoint.shutdown()
|
|
69
|
+
|
|
70
|
+
def setDelegate(
|
|
71
|
+
self, tupleName: str, processor: TupleActionProcessorDelegateABC
|
|
72
|
+
):
|
|
73
|
+
"""Add Tuple Action Processor Delegate
|
|
74
|
+
|
|
75
|
+
:param tupleName: The tuple name to process actions for.
|
|
76
|
+
:param processor: The processor to use for processing this tuple name.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
self._delegateProcessor.setDelegate(tupleName, processor)
|
|
80
|
+
|
|
81
|
+
def _processLimited(self, *args, **kwargs):
|
|
82
|
+
d = self._processLimitedDecorated(*args, **kwargs)
|
|
83
|
+
if d:
|
|
84
|
+
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
85
|
+
|
|
86
|
+
@limitConcurrency(logger, 20)
|
|
87
|
+
def _processLimitedDecorated(self, *args, **kwargs):
|
|
88
|
+
return self._process(*args, **kwargs)
|
|
89
|
+
|
|
90
|
+
@inlineCallbacks
|
|
91
|
+
def _process(
|
|
92
|
+
self,
|
|
93
|
+
payloadEnvelope: PayloadEnvelope,
|
|
94
|
+
vortexName: str,
|
|
95
|
+
vortexUuid: str,
|
|
96
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
97
|
+
**kwargs,
|
|
98
|
+
) -> None:
|
|
99
|
+
# Ignore responses from the backend, these are handled by PayloadResponse
|
|
100
|
+
if vortexName == self._proxyToVortexName:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
if not VortexFactory.isVortexUuidOnline(vortexUuid):
|
|
104
|
+
logger.debug(
|
|
105
|
+
"Vortex %s is no longer online, skipping reply", vortexUuid
|
|
106
|
+
)
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
# logger.debug(
|
|
110
|
+
# "Vortex %s is STILL online, continuing to reply", vortexUuid
|
|
111
|
+
# )
|
|
112
|
+
|
|
113
|
+
# Shortcut the logic, so that we don't decode the payload unless we need to.
|
|
114
|
+
if not self._delegateProcessor.delegateCount:
|
|
115
|
+
yield self._processForProxy(
|
|
116
|
+
payloadEnvelope, vortexName, sendResponse
|
|
117
|
+
)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
# If we have local processors, then work out if this tupleAction is meant for
|
|
121
|
+
# the local processor.
|
|
122
|
+
payload = yield payloadEnvelope.decodePayloadDefer()
|
|
123
|
+
|
|
124
|
+
assert (
|
|
125
|
+
len(payload.tuples) == 1
|
|
126
|
+
), "TupleActionProcessor:%s Expected 1 tuples, received %s" % (
|
|
127
|
+
self._tupleActionProcessorName,
|
|
128
|
+
len(payload.tuples),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
tupleAction = payload.tuples[0]
|
|
132
|
+
|
|
133
|
+
if self._delegateProcessor.hasDelegate(tupleAction.tupleName()):
|
|
134
|
+
self._delegateProcessor._processTupleAction(
|
|
135
|
+
payloadEnvelope.filt, sendResponse, tupleAction
|
|
136
|
+
)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
# Else, Just send it on to the delegate we're proxying for (the backend)
|
|
140
|
+
yield self._processForProxy(payloadEnvelope, vortexName, sendResponse)
|
|
141
|
+
|
|
142
|
+
@inlineCallbacks
|
|
143
|
+
def _processForProxy(
|
|
144
|
+
self,
|
|
145
|
+
payloadEnvelope: PayloadEnvelope,
|
|
146
|
+
vortexName: str,
|
|
147
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
148
|
+
**kwargs,
|
|
149
|
+
):
|
|
150
|
+
# Keep a copy of the incoming filt, in case they are using PayloadResponse
|
|
151
|
+
responseFilt = copy(payloadEnvelope.filt)
|
|
152
|
+
|
|
153
|
+
# Track the response, log an error if it fails
|
|
154
|
+
# 5 Seconds is long enough.
|
|
155
|
+
# VortexJS defaults to 10s, so we have some room for round trip time.
|
|
156
|
+
pr = PayloadResponse(
|
|
157
|
+
payloadEnvelope,
|
|
158
|
+
timeout=PayloadResponse.TIMEOUT - 5, # 5 seconds less
|
|
159
|
+
resultCheck=False,
|
|
160
|
+
logTimeoutError=False,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# This is not a lambda, so that it can have a breakpoint
|
|
164
|
+
def reply(payloadEnvelope: PayloadEnvelope):
|
|
165
|
+
payloadEnvelope.filt = responseFilt
|
|
166
|
+
d: Deferred = payloadEnvelope.toVortexMsgDefer()
|
|
167
|
+
d.addCallback(sendResponse)
|
|
168
|
+
return d
|
|
169
|
+
|
|
170
|
+
pr.addCallback(reply)
|
|
171
|
+
|
|
172
|
+
if self._DEBUG_LOGGING:
|
|
173
|
+
pr.addCallback(
|
|
174
|
+
lambda _: logger.debug(
|
|
175
|
+
"Received action response from server for %s",
|
|
176
|
+
payloadEnvelope.filt,
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
pr.addErrback(self.__handlePrFailure, payloadEnvelope, sendResponse)
|
|
180
|
+
|
|
181
|
+
vortexMsg = yield payloadEnvelope.toVortexMsgDefer()
|
|
182
|
+
try:
|
|
183
|
+
yield VortexFactory.sendVortexMsg(
|
|
184
|
+
vortexMsgs=vortexMsg, destVortexName=self._proxyToVortexName
|
|
185
|
+
)
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.exception(e)
|
|
188
|
+
|
|
189
|
+
@inlineCallbacks
|
|
190
|
+
def __handlePrFailure(
|
|
191
|
+
self,
|
|
192
|
+
f: Failure,
|
|
193
|
+
payloadEnvelope: PayloadEnvelope,
|
|
194
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
195
|
+
):
|
|
196
|
+
payload = yield payloadEnvelope.decodePayloadDefer()
|
|
197
|
+
action = payload.tuples[0]
|
|
198
|
+
if f.check(TimeoutError):
|
|
199
|
+
logger.error(
|
|
200
|
+
"Received no response from\nprocessor %s\naction %s",
|
|
201
|
+
self._filt,
|
|
202
|
+
action,
|
|
203
|
+
)
|
|
204
|
+
else:
|
|
205
|
+
logger.error(
|
|
206
|
+
"Unexpected error, %s\nprocessor %s\naction %s",
|
|
207
|
+
f,
|
|
208
|
+
self._filt,
|
|
209
|
+
action,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
vortexLogFailure(f, logger)
|
|
213
|
+
|
|
214
|
+
vortexMsg = yield PayloadEnvelope(
|
|
215
|
+
filt=payloadEnvelope.filt, result=str(f.value)
|
|
216
|
+
).toVortexMsgDefer()
|
|
217
|
+
|
|
218
|
+
sendResponse(vortexMsg)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from twisted.internet import defer
|
|
4
|
+
from twisted.internet.defer import Deferred
|
|
5
|
+
|
|
6
|
+
from vortex.PerformTestActionTuple import PerformTestActionTuple
|
|
7
|
+
from vortex.TupleAction import TupleActionABC, TupleGenericAction
|
|
8
|
+
from vortex.handler.TupleActionProcessor import (
|
|
9
|
+
TupleActionProcessor,
|
|
10
|
+
TupleActionProcessorDelegateABC,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestProcessor(TupleActionProcessorDelegateABC):
|
|
17
|
+
def processTupleAction(self, tupleAction: TupleActionABC) -> Deferred:
|
|
18
|
+
assert isinstance(
|
|
19
|
+
tupleAction, TupleGenericAction
|
|
20
|
+
), "tupleAction is not TupleGenericAction"
|
|
21
|
+
if "FAIL PLEASE" == tupleAction.data:
|
|
22
|
+
raise Exception("Processing failed scenario, exception raised")
|
|
23
|
+
return defer.succeed(True)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
actionProcessor = TupleActionProcessor("vortexTestActions")
|
|
27
|
+
actionProcessor.setDelegate(PerformTestActionTuple.tupleName(), TestProcessor())
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from copy import copy
|
|
3
|
+
|
|
4
|
+
from twisted.internet.defer import Deferred
|
|
5
|
+
|
|
6
|
+
from vortex.Payload import Payload
|
|
7
|
+
from vortex.PayloadResponse import PayloadResponse
|
|
8
|
+
from vortex.TupleAction import TupleActionABC
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TupleDataActionClient:
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
destVortexName: str,
|
|
17
|
+
tupleActionProcessorName: str,
|
|
18
|
+
additionalFilt: dict = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Constructor
|
|
21
|
+
|
|
22
|
+
:param destVortexName: The name of the destination vortex to send to.
|
|
23
|
+
|
|
24
|
+
:param tupleActionProcessorName: The name of this observable
|
|
25
|
+
|
|
26
|
+
:param additionalFilt: Any additional filter keys that are required
|
|
27
|
+
"""
|
|
28
|
+
self._destVortexName = destVortexName
|
|
29
|
+
self._filt = dict(
|
|
30
|
+
name=tupleActionProcessorName, key="tupleActionProcessorName"
|
|
31
|
+
)
|
|
32
|
+
if additionalFilt:
|
|
33
|
+
self._filt.update(additionalFilt)
|
|
34
|
+
|
|
35
|
+
def pushAction(
|
|
36
|
+
self, tupleAction: TupleActionABC, additionalFilt=None
|
|
37
|
+
) -> Deferred:
|
|
38
|
+
"""Push Action
|
|
39
|
+
|
|
40
|
+
This pushes the action, either locally or to the server, depending on the
|
|
41
|
+
implementation.
|
|
42
|
+
|
|
43
|
+
If pushed locally, the promise will resolve when the action has been saved.
|
|
44
|
+
If pushed directly to the server, the promise will resolve when the server has
|
|
45
|
+
responded.
|
|
46
|
+
|
|
47
|
+
:param tupleAction The tuple action to send to the remote end
|
|
48
|
+
:param additionalFilt: Any extra filt that may be required to send with the
|
|
49
|
+
payload.
|
|
50
|
+
"""
|
|
51
|
+
filt = copy(self._filt)
|
|
52
|
+
if additionalFilt:
|
|
53
|
+
filt.update(additionalFilt)
|
|
54
|
+
|
|
55
|
+
d = Payload(filt=filt, tuples=[tupleAction]).makePayloadEnvelopeDefer()
|
|
56
|
+
d.addCallback(
|
|
57
|
+
lambda payloadEnvelope: PayloadResponse(
|
|
58
|
+
payloadEnvelope, destVortexName=self._destVortexName
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Convert the data to TupleAction
|
|
63
|
+
d.addCallback(
|
|
64
|
+
lambda payloadEnvelope: payloadEnvelope.decodePayloadDefer()
|
|
65
|
+
)
|
|
66
|
+
d.addCallback(lambda payload: payload.tuples)
|
|
67
|
+
return d
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import typing
|
|
3
|
+
import weakref
|
|
4
|
+
from abc import abstractmethod, ABCMeta
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from typing import Optional, Dict, Set
|
|
8
|
+
|
|
9
|
+
import pytz
|
|
10
|
+
from reactivex.subject import Subject
|
|
11
|
+
from twisted.internet import task
|
|
12
|
+
|
|
13
|
+
from vortex.DeferUtil import vortexLogFailure
|
|
14
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
15
|
+
from vortex.TupleSelector import TupleSelector
|
|
16
|
+
from vortex.VortexFactory import VortexFactory
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _CachedSubscribedData:
|
|
22
|
+
"""Cached Subscribed Data
|
|
23
|
+
|
|
24
|
+
The client will now cache
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
TEARDOWN_WAIT = 120 # 2 minutes in seconds
|
|
28
|
+
|
|
29
|
+
__memoryLoggingRefs = None
|
|
30
|
+
__memoryLoggingEnabled = False
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def setupMemoryLogging(cls) -> None:
|
|
34
|
+
cls.__memoryLoggingRefs = []
|
|
35
|
+
cls.__memoryLoggingEnabled = True
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def memoryLoggingDump(
|
|
39
|
+
cls, top=10, over=100
|
|
40
|
+
) -> typing.List[typing.Tuple[str, int, int]]:
|
|
41
|
+
if not cls.__memoryLoggingRefs:
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
# Filter out expired items
|
|
45
|
+
cls.__memoryLoggingRefs = list(
|
|
46
|
+
filter(lambda o: o(), cls.__memoryLoggingRefs)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
cachesByTupleType = defaultdict(list)
|
|
50
|
+
for cacheRef in cls.__memoryLoggingRefs:
|
|
51
|
+
cache = cacheRef()
|
|
52
|
+
if not cache:
|
|
53
|
+
continue
|
|
54
|
+
cachesByTupleType[cache.tupleSelector.name].append(
|
|
55
|
+
len(cache.encodedPayload)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
results = []
|
|
59
|
+
for tupleName, lengths in cachesByTupleType.items():
|
|
60
|
+
results.append((tupleName, len(lengths), sum(lengths)))
|
|
61
|
+
|
|
62
|
+
data = sorted(results, key=lambda x: x[2], reverse=True)
|
|
63
|
+
|
|
64
|
+
return list(filter(lambda x: x[2] >= over, data))[:top]
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self, tupleSelector: TupleSelector, cacheEnabled: bool = True
|
|
68
|
+
) -> None:
|
|
69
|
+
if _CachedSubscribedData.__memoryLoggingEnabled:
|
|
70
|
+
_CachedSubscribedData.__memoryLoggingRefs.append(weakref.ref(self))
|
|
71
|
+
|
|
72
|
+
self.tupleSelector: TupleSelector = tupleSelector
|
|
73
|
+
self.vortexUuids: Set[str] = set()
|
|
74
|
+
self.tearDownDate: Optional[datetime] = None
|
|
75
|
+
self.encodedPayload: bytes = None
|
|
76
|
+
|
|
77
|
+
#: Is the cache enabled
|
|
78
|
+
# HINT: Once it's turned off for a tupleSelector, it remains off.
|
|
79
|
+
# Disabling it can also be done from VortexJS
|
|
80
|
+
self.cacheEnabled = cacheEnabled
|
|
81
|
+
|
|
82
|
+
#: Last Server Payload Date
|
|
83
|
+
# If the server has responded with a payload, this is the date in the payload
|
|
84
|
+
# @type {Date | null}
|
|
85
|
+
self.lastServerPayloadDate: Optional[datetime] = None
|
|
86
|
+
|
|
87
|
+
self.subject = Subject()
|
|
88
|
+
|
|
89
|
+
def markForTearDown(self) -> None:
|
|
90
|
+
if self.tearDownDate is not None:
|
|
91
|
+
return
|
|
92
|
+
self.tearDownDate = datetime.now(pytz.utc) + timedelta(
|
|
93
|
+
seconds=self.TEARDOWN_WAIT
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def resetTearDown(self) -> None:
|
|
97
|
+
self.tearDownDate = None
|
|
98
|
+
|
|
99
|
+
def isReadyForTearDown(self) -> bool:
|
|
100
|
+
return (
|
|
101
|
+
self.tearDownDate is not None
|
|
102
|
+
and self.tearDownDate <= datetime.now(pytz.utc)
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class TupleDataObservableCache(metaclass=ABCMeta):
|
|
107
|
+
__CHECK_PERIOD = 30 # seconds
|
|
108
|
+
|
|
109
|
+
def __init__(self) -> None:
|
|
110
|
+
self.__cache: Dict[str, _CachedSubscribedData] = {}
|
|
111
|
+
|
|
112
|
+
def shutdown(self):
|
|
113
|
+
if not self.__pollLoopingCall:
|
|
114
|
+
raise Exception("This has already been shutdown")
|
|
115
|
+
|
|
116
|
+
self.__cache = {}
|
|
117
|
+
self.__pollLoopingCall.stop()
|
|
118
|
+
self.__pollLoopingCall = None
|
|
119
|
+
|
|
120
|
+
for cache in self.__cache.values():
|
|
121
|
+
cache.subject.dispose()
|
|
122
|
+
|
|
123
|
+
self.__cache = {}
|
|
124
|
+
|
|
125
|
+
def start(self):
|
|
126
|
+
|
|
127
|
+
self.__pollLoopingCall = task.LoopingCall(self.__cacheCheck)
|
|
128
|
+
|
|
129
|
+
d = self.__pollLoopingCall.start(self.__CHECK_PERIOD, now=False)
|
|
130
|
+
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
131
|
+
|
|
132
|
+
def _tupleSelectors(self) -> typing.List[TupleSelector]:
|
|
133
|
+
tupleSelectors = []
|
|
134
|
+
for key in self.__cache:
|
|
135
|
+
tupleSelectors.append(TupleSelector.fromJsonStr(key))
|
|
136
|
+
return tupleSelectors
|
|
137
|
+
|
|
138
|
+
## ----- Implement local observable
|
|
139
|
+
|
|
140
|
+
def __cacheCheck(self):
|
|
141
|
+
currentVortexUuids = set(VortexFactory.getRemoteVortexUuids())
|
|
142
|
+
|
|
143
|
+
for ts, cache in list(self.__cache.items()):
|
|
144
|
+
cache.vortexUuids = cache.vortexUuids & currentVortexUuids
|
|
145
|
+
|
|
146
|
+
if cache.vortexUuids or cache.subject.observers:
|
|
147
|
+
cache.resetTearDown()
|
|
148
|
+
|
|
149
|
+
elif cache.isReadyForTearDown():
|
|
150
|
+
logger.debug("Cleaning cache for %s", cache.tupleSelector)
|
|
151
|
+
self._sendUnsubscribeToServer(cache.tupleSelector)
|
|
152
|
+
del self.__cache[ts]
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
cache.markForTearDown()
|
|
156
|
+
|
|
157
|
+
def _getCache(
|
|
158
|
+
self, tupleSelector: TupleSelector
|
|
159
|
+
) -> Optional[_CachedSubscribedData]:
|
|
160
|
+
return self.__cache.get(tupleSelector.toJsonStr())
|
|
161
|
+
|
|
162
|
+
def _hasTupleSelector(self, tupleSelectorAny: TupleSelector) -> bool:
|
|
163
|
+
return tupleSelectorAny.toJsonStr() in self.__cache
|
|
164
|
+
|
|
165
|
+
def _makeCache(self, tupleSelector: TupleSelector) -> _CachedSubscribedData:
|
|
166
|
+
tsStr = tupleSelector.toJsonStr()
|
|
167
|
+
if tsStr in self.__cache:
|
|
168
|
+
return self.__cache[tsStr]
|
|
169
|
+
|
|
170
|
+
cache = _CachedSubscribedData(tupleSelector)
|
|
171
|
+
self.__cache[tsStr] = cache
|
|
172
|
+
return cache
|
|
173
|
+
|
|
174
|
+
def _updateCache(
|
|
175
|
+
self, payloadEnvelope: PayloadEnvelope
|
|
176
|
+
) -> typing.Tuple[_CachedSubscribedData, bool]:
|
|
177
|
+
"""Update Cache
|
|
178
|
+
|
|
179
|
+
Update the cache if it requires it
|
|
180
|
+
|
|
181
|
+
:returns a tuple of (cache, requiredUpdate)
|
|
182
|
+
|
|
183
|
+
"""
|
|
184
|
+
tupleSelector = payloadEnvelope.filt["tupleSelector"]
|
|
185
|
+
|
|
186
|
+
cache = self._getCache(tupleSelector)
|
|
187
|
+
if not cache:
|
|
188
|
+
cache = self._makeCache(tupleSelector)
|
|
189
|
+
|
|
190
|
+
if cache.lastServerPayloadDate is not None:
|
|
191
|
+
if payloadEnvelope.date < cache.lastServerPayloadDate:
|
|
192
|
+
return cache, False
|
|
193
|
+
|
|
194
|
+
cache.lastServerPayloadDate = payloadEnvelope.date
|
|
195
|
+
cache.encodedPayload = payloadEnvelope.encodedPayload
|
|
196
|
+
|
|
197
|
+
return cache, True
|
|
198
|
+
|
|
199
|
+
@abstractmethod
|
|
200
|
+
def _sendUnsubscribeToServer(self, tupleSelector: TupleSelector):
|
|
201
|
+
pass
|