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,371 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from abc import ABCMeta
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from collections import namedtuple
|
|
5
|
+
from typing import List
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
from twisted.internet import reactor
|
|
10
|
+
from twisted.internet.defer import Deferred
|
|
11
|
+
from twisted.internet.defer import fail
|
|
12
|
+
from twisted.internet.defer import inlineCallbacks
|
|
13
|
+
from twisted.internet.defer import succeed
|
|
14
|
+
from twisted.python import failure
|
|
15
|
+
|
|
16
|
+
from vortex.DeferUtil import vortexLogFailure
|
|
17
|
+
from vortex.PayloadEndpoint import PayloadEndpoint
|
|
18
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
19
|
+
from vortex.TupleSelector import TupleSelector
|
|
20
|
+
from vortex.VortexABC import SendVortexMsgResponseCallable
|
|
21
|
+
from vortex.VortexFactory import VortexFactory
|
|
22
|
+
from vortex.VortexUtil import limitConcurrency, _dedupProcessorCallKeys
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TupleSelectorUpdateMapperABC(metaclass=ABCMeta):
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def mapTupleSelector(
|
|
30
|
+
self,
|
|
31
|
+
triggerTupleSelector: TupleSelector,
|
|
32
|
+
allTupleSelectors: List[TupleSelector],
|
|
33
|
+
) -> List[TupleSelector]:
|
|
34
|
+
"""Map Tuple Selector
|
|
35
|
+
|
|
36
|
+
Implementing this class provides some intelligence to the Observable
|
|
37
|
+
to trigger a refresh of related data to the triggerTupleSelector.
|
|
38
|
+
|
|
39
|
+
:param triggerTupleSelector: The TupleSelector that has triggered the update.
|
|
40
|
+
:param allTupleSelectors: All TupleSelectors being observed from this observable.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class TuplesProviderABC(metaclass=ABCMeta):
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def makeVortexMsg(
|
|
47
|
+
self, filt: dict, tupleSelector: TupleSelector
|
|
48
|
+
) -> Union[Deferred, bytes]:
|
|
49
|
+
"""Make Vortex Msg
|
|
50
|
+
|
|
51
|
+
The method generates the vortexMsg for the vortex to send.
|
|
52
|
+
|
|
53
|
+
:param filt: The filt for the payload.
|
|
54
|
+
:param tupleSelector: The tuple selector us used to determing which tuples to
|
|
55
|
+
send back,
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
_ObserverDetails = namedtuple(
|
|
60
|
+
"_ObserverDetails", ["vortexUuid", "observerName"]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _ObserverData:
|
|
65
|
+
def __init__(self, tupleSelector: TupleSelector):
|
|
66
|
+
self.tupleSelector = tupleSelector
|
|
67
|
+
self.observers = set()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TupleDataObservableHandler:
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
observableName,
|
|
74
|
+
additionalFilt=None,
|
|
75
|
+
subscriptionsEnabled=True,
|
|
76
|
+
acceptOnlyFromVortex: Optional[Union[str, tuple]] = None,
|
|
77
|
+
):
|
|
78
|
+
"""Constructor
|
|
79
|
+
|
|
80
|
+
:param observableName: The name of this observable
|
|
81
|
+
|
|
82
|
+
:param additionalFilt: Any additional filter keys that are required
|
|
83
|
+
|
|
84
|
+
:param subscriptionsEnabled: Should subscriptions be enabled (default)
|
|
85
|
+
|
|
86
|
+
:param acceptOnlyFromVortex: Accept requests only from this vortex,
|
|
87
|
+
The vortex can be str or tuple of str, or None to accept from any.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
self._observableName = observableName
|
|
91
|
+
self._subscriptionsEnabled = subscriptionsEnabled
|
|
92
|
+
self._filt = dict(name=observableName, key="tupleDataObservable")
|
|
93
|
+
if additionalFilt:
|
|
94
|
+
self._filt.update(additionalFilt)
|
|
95
|
+
|
|
96
|
+
self._endpoint = PayloadEndpoint(
|
|
97
|
+
self._filt,
|
|
98
|
+
self._processLimited,
|
|
99
|
+
acceptOnlyFromVortex=acceptOnlyFromVortex,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
self._observerDataByTupleSelector = {}
|
|
103
|
+
|
|
104
|
+
self._tupleProvidersByTupleName = {}
|
|
105
|
+
self._tupleSelectorUpdateMappers = []
|
|
106
|
+
|
|
107
|
+
def addTupleProvider(self, tupleName, provider: TuplesProviderABC):
|
|
108
|
+
"""Add Tuple Provider"""
|
|
109
|
+
assert (
|
|
110
|
+
not tupleName in self._tupleProvidersByTupleName
|
|
111
|
+
), "Observable:%s, Tuple name %s is already registered" % (
|
|
112
|
+
self._observableName,
|
|
113
|
+
tupleName,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
assert isinstance(provider, TuplesProviderABC), (
|
|
117
|
+
"Observable:%s, provider must be an instance of TuplesProviderABC"
|
|
118
|
+
% self._observableName
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
self._tupleProvidersByTupleName[tupleName] = provider
|
|
122
|
+
|
|
123
|
+
def addTupleSelectorUpdateMapper(
|
|
124
|
+
self, mapper: TupleSelectorUpdateMapperABC
|
|
125
|
+
):
|
|
126
|
+
"""Add Tuple Selector Update Mapper
|
|
127
|
+
|
|
128
|
+
This mapper will be called every time a tuple selector is notified of an update.
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
self._tupleSelectorUpdateMappers.append(mapper)
|
|
132
|
+
|
|
133
|
+
def hasTupleProvider(self, tupleName: str):
|
|
134
|
+
return tupleName in self._tupleProvidersByTupleName
|
|
135
|
+
|
|
136
|
+
def hasTupleSubscribers(self, tupleSelector: TupleSelector):
|
|
137
|
+
return tupleSelector.toJsonStr() in self._observerDataByTupleSelector
|
|
138
|
+
|
|
139
|
+
def tupleSubscribers(self) -> List[TupleSelector]:
|
|
140
|
+
# This is a bit costly
|
|
141
|
+
return [
|
|
142
|
+
TupleSelector.fromJsonStr(jsonStr)
|
|
143
|
+
for jsonStr in self._observerDataByTupleSelector
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
def shutdown(self):
|
|
147
|
+
self._endpoint.shutdown()
|
|
148
|
+
self._tupleSelectorUpdateMappers = []
|
|
149
|
+
self._tupleProvidersByTupleName = {}
|
|
150
|
+
self._observerDataByTupleSelector = {}
|
|
151
|
+
|
|
152
|
+
@inlineCallbacks
|
|
153
|
+
def _createVortexMsg(self, filt, tupleSelector: TupleSelector) -> Deferred:
|
|
154
|
+
tupleProvider = self._tupleProvidersByTupleName.get(tupleSelector.name)
|
|
155
|
+
assert (
|
|
156
|
+
tupleProvider
|
|
157
|
+
), "Observable:%s, No providers registered for tupleName %s" % (
|
|
158
|
+
self._observableName,
|
|
159
|
+
tupleSelector.name,
|
|
160
|
+
)
|
|
161
|
+
try:
|
|
162
|
+
vortexMsg = yield self._customMaybeDeferred(
|
|
163
|
+
tupleProvider.makeVortexMsg, filt, tupleSelector
|
|
164
|
+
)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logger.error(
|
|
167
|
+
f"createVortexMsg"
|
|
168
|
+
f" TupleSelector = {tupleSelector.toJsonStr()},"
|
|
169
|
+
f" ObservableFilt = {self._filt}"
|
|
170
|
+
)
|
|
171
|
+
logger.exception(e)
|
|
172
|
+
raise
|
|
173
|
+
if not vortexMsg:
|
|
174
|
+
msg = f"TupleDataProvider did not return VortexPY message {tupleSelector}"
|
|
175
|
+
logger.error(msg)
|
|
176
|
+
raise Exception(msg)
|
|
177
|
+
return vortexMsg
|
|
178
|
+
|
|
179
|
+
def _processLimited(self, *args, **kwargs):
|
|
180
|
+
d = self._processLimitedDecorated(*args, **kwargs)
|
|
181
|
+
if d:
|
|
182
|
+
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
183
|
+
|
|
184
|
+
@limitConcurrency(
|
|
185
|
+
logger, 25, deduplicateBasedOnKwArgsKey=_dedupProcessorCallKeys
|
|
186
|
+
)
|
|
187
|
+
def _processLimitedDecorated(self, *args, **kwargs):
|
|
188
|
+
return self._process(*args, **kwargs)
|
|
189
|
+
|
|
190
|
+
@inlineCallbacks
|
|
191
|
+
def _process(
|
|
192
|
+
self,
|
|
193
|
+
payloadEnvelope: PayloadEnvelope,
|
|
194
|
+
vortexUuid: str,
|
|
195
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
196
|
+
**kwargs,
|
|
197
|
+
):
|
|
198
|
+
if not VortexFactory.isVortexUuidOnline(vortexUuid):
|
|
199
|
+
logger.debug(
|
|
200
|
+
"Vortex %s is no longer online, skipping reply", vortexUuid
|
|
201
|
+
)
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
# logger.debug(
|
|
205
|
+
# "Vortex %s is STILL online, continuing to reply", vortexUuid
|
|
206
|
+
# )
|
|
207
|
+
|
|
208
|
+
tupleSelector = payloadEnvelope.filt["tupleSelector"]
|
|
209
|
+
tsStr = tupleSelector.toJsonStr()
|
|
210
|
+
|
|
211
|
+
observerDetails = _ObserverDetails(
|
|
212
|
+
vortexUuid, payloadEnvelope.filt.get("observerName")
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Handle unsubscribe
|
|
216
|
+
if payloadEnvelope.filt.get("unsubscribe"):
|
|
217
|
+
if tsStr in self._observerDataByTupleSelector:
|
|
218
|
+
try:
|
|
219
|
+
self._observerDataByTupleSelector[tsStr].observers.remove(
|
|
220
|
+
observerDetails
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
except KeyError:
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
if not self._observerDataByTupleSelector[tsStr].observers:
|
|
227
|
+
del self._observerDataByTupleSelector[tsStr]
|
|
228
|
+
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
# Add support for just getting data, no subscription.
|
|
232
|
+
if self._subscriptionsEnabled and payloadEnvelope.filt.get(
|
|
233
|
+
"subscribe", True
|
|
234
|
+
):
|
|
235
|
+
if tsStr not in self._observerDataByTupleSelector:
|
|
236
|
+
self._observerDataByTupleSelector[tsStr] = _ObserverData(
|
|
237
|
+
tupleSelector
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
self._observerDataByTupleSelector[tsStr].observers.add(
|
|
241
|
+
observerDetails
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
vortexMsg = yield self._createVortexMsg(
|
|
245
|
+
payloadEnvelope.filt, tupleSelector
|
|
246
|
+
)
|
|
247
|
+
try:
|
|
248
|
+
yield sendResponse(vortexMsg)
|
|
249
|
+
except Exception as e:
|
|
250
|
+
logger.error(
|
|
251
|
+
f"_process"
|
|
252
|
+
f" TupleSelector = {tupleSelector.toJsonStr()},"
|
|
253
|
+
f" ObservableFilt = {self._filt}"
|
|
254
|
+
)
|
|
255
|
+
logger.exception(e)
|
|
256
|
+
|
|
257
|
+
def _getMappedTupleSelectors(
|
|
258
|
+
self, tupleSelector: TupleSelector
|
|
259
|
+
) -> List[TupleSelector]:
|
|
260
|
+
# Get all tuple selectors
|
|
261
|
+
allTupleSelectors = [
|
|
262
|
+
data.tupleSelector
|
|
263
|
+
for data in self._observerDataByTupleSelector.values()
|
|
264
|
+
]
|
|
265
|
+
|
|
266
|
+
# Create a dict so we end up with only unique ones
|
|
267
|
+
tupleSelectorByStr = {tupleSelector.toJsonStr(): tupleSelector}
|
|
268
|
+
|
|
269
|
+
# Run through the mappers
|
|
270
|
+
for mapper in self._tupleSelectorUpdateMappers:
|
|
271
|
+
mappedSelectors = mapper.mapTupleSelector(
|
|
272
|
+
tupleSelector, allTupleSelectors
|
|
273
|
+
)
|
|
274
|
+
if mappedSelectors:
|
|
275
|
+
tupleSelectorByStr.update(
|
|
276
|
+
{ts.toJsonStr(): ts for ts in mappedSelectors}
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Return a list of tuple selectors
|
|
280
|
+
return list(tupleSelectorByStr.values())
|
|
281
|
+
|
|
282
|
+
def notifyOfTupleUpdate(self, tupleSelector: TupleSelector) -> None:
|
|
283
|
+
"""Notify Of Tuple Update
|
|
284
|
+
|
|
285
|
+
This method tells the observable that an update has occurred and that it should
|
|
286
|
+
send new data to its observers.
|
|
287
|
+
|
|
288
|
+
Tuple selectors should be identical to the data being observed.
|
|
289
|
+
|
|
290
|
+
:param tupleSelector: A tuple selector that describes the scope of the update.
|
|
291
|
+
:returns: None
|
|
292
|
+
|
|
293
|
+
"""
|
|
294
|
+
tupleSelectors = self._getMappedTupleSelectors(tupleSelector)
|
|
295
|
+
reactor.callLater(0, self._notifyOfTupleUpdateInMain, tupleSelectors)
|
|
296
|
+
|
|
297
|
+
def notifyOfTupleUpdateForTuple(self, tupleName: str) -> None:
|
|
298
|
+
"""Notify of Tuple Update for Tuple
|
|
299
|
+
|
|
300
|
+
Like the above notification method, this method will aquire new data and
|
|
301
|
+
send it to observers.
|
|
302
|
+
|
|
303
|
+
.. note:: Calling this will not trigger the TupleSelector Update Mappers as
|
|
304
|
+
this is an earlier soloution.
|
|
305
|
+
|
|
306
|
+
For all new code, use notifyOfTupleUpdate() and define mappers.
|
|
307
|
+
|
|
308
|
+
"""
|
|
309
|
+
tupleSelectors = []
|
|
310
|
+
for data in self._observerDataByTupleSelector.values():
|
|
311
|
+
if data.tupleSelector.name == tupleName:
|
|
312
|
+
tupleSelectors.append(data.tupleSelector)
|
|
313
|
+
|
|
314
|
+
reactor.callLater(0, self._notifyOfTupleUpdateInMain, tupleSelectors)
|
|
315
|
+
|
|
316
|
+
@inlineCallbacks
|
|
317
|
+
def _notifyOfTupleUpdateInMain(self, tupleSelectors: List[TupleSelector]):
|
|
318
|
+
for tupleSelector in tupleSelectors:
|
|
319
|
+
yield self._notifyOfTupleUpdateInMainOne(tupleSelector)
|
|
320
|
+
|
|
321
|
+
@inlineCallbacks
|
|
322
|
+
def _notifyOfTupleUpdateInMainOne(self, tupleSelector: TupleSelector):
|
|
323
|
+
tsStr = tupleSelector.toJsonStr()
|
|
324
|
+
if tsStr not in self._observerDataByTupleSelector:
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
# Filter out the offline observables
|
|
328
|
+
onlineUuids = set(VortexFactory.getRemoteVortexUuids())
|
|
329
|
+
observers = self._observerDataByTupleSelector[tsStr].observers
|
|
330
|
+
for od in list(observers):
|
|
331
|
+
if od.vortexUuid not in onlineUuids:
|
|
332
|
+
observers.remove(od)
|
|
333
|
+
|
|
334
|
+
# Get / update the list of observing UUIDs
|
|
335
|
+
if not observers:
|
|
336
|
+
del self._observerDataByTupleSelector[tsStr]
|
|
337
|
+
return
|
|
338
|
+
|
|
339
|
+
# Create the vortexMsg
|
|
340
|
+
filt = dict(tupleSelector=tupleSelector)
|
|
341
|
+
filt.update(self._filt)
|
|
342
|
+
vortexMsg = yield self._createVortexMsg(filt, tupleSelector)
|
|
343
|
+
|
|
344
|
+
# We can have multiple Observable clients on the one vortex, so make sure
|
|
345
|
+
# we only send one message for these.
|
|
346
|
+
destVortexUuids = set([od.vortexUuid for od in observers])
|
|
347
|
+
|
|
348
|
+
# Send the vortex messages
|
|
349
|
+
for destVortexUuid in destVortexUuids:
|
|
350
|
+
d = VortexFactory.sendVortexMsg(
|
|
351
|
+
vortexMsgs=vortexMsg, destVortexUuid=destVortexUuid
|
|
352
|
+
)
|
|
353
|
+
d.addErrback(vortexLogFailure, logger)
|
|
354
|
+
|
|
355
|
+
def _customMaybeDeferred(self, f, *args, **kw):
|
|
356
|
+
try:
|
|
357
|
+
result = f(*args, **kw)
|
|
358
|
+
except Exception as e:
|
|
359
|
+
logger.error(
|
|
360
|
+
"TupleDataObservableHandler:%s TupleProvider failed",
|
|
361
|
+
self._observableName,
|
|
362
|
+
)
|
|
363
|
+
logger.exception(e)
|
|
364
|
+
return fail(failure.Failure(e))
|
|
365
|
+
|
|
366
|
+
if isinstance(result, Deferred):
|
|
367
|
+
return result
|
|
368
|
+
elif isinstance(result, failure.Failure):
|
|
369
|
+
return fail(result)
|
|
370
|
+
else:
|
|
371
|
+
return succeed(result)
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from copy import copy
|
|
3
|
+
|
|
4
|
+
from twisted.internet import reactor
|
|
5
|
+
from twisted.internet.defer import TimeoutError
|
|
6
|
+
from twisted.python.failure import Failure
|
|
7
|
+
|
|
8
|
+
from vortex.DeferUtil import deferToThreadWrapWithLogger
|
|
9
|
+
from vortex.DeferUtil import vortexLogFailure
|
|
10
|
+
from vortex.PayloadEndpoint import PayloadEndpoint
|
|
11
|
+
from vortex.PayloadEnvelope import PayloadEnvelope
|
|
12
|
+
from vortex.PayloadResponse import PayloadResponse
|
|
13
|
+
from vortex.TupleSelector import TupleSelector
|
|
14
|
+
from vortex.VortexABC import SendVortexMsgResponseCallable
|
|
15
|
+
from vortex.VortexFactory import NoVortexException
|
|
16
|
+
from vortex.VortexFactory import VortexFactory
|
|
17
|
+
from vortex.VortexUtil import limitConcurrency, _dedupProcessorCallKeys
|
|
18
|
+
from vortex.handler.TupleDataObservableCache import TupleDataObservableCache
|
|
19
|
+
from vortex.handler.TupleDataObservableHandler import TupleDataObservableHandler
|
|
20
|
+
from vortex.handler.TupleDataObservableHandler import TuplesProviderABC
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TupleDataObservableProxyHandler(TupleDataObservableCache):
|
|
26
|
+
__CHECK_PERIOD = 30 # seconds
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
observableName,
|
|
31
|
+
proxyToVortexName: str,
|
|
32
|
+
additionalFilt=None,
|
|
33
|
+
subscriptionsEnabled=True,
|
|
34
|
+
observerName="default",
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Constructor
|
|
37
|
+
|
|
38
|
+
:param observableName: The name of this and the other observable
|
|
39
|
+
:param proxyToVortexName: The vortex dest name to proxy requests to
|
|
40
|
+
:param additionalFilt: Any additional filter keys that are required
|
|
41
|
+
:param subscriptionsEnabled: Should subscriptions be enabled (default)
|
|
42
|
+
:param observerName: We can clash with other observers, so where there are
|
|
43
|
+
multiple observers on the one vortex, they should use different names.
|
|
44
|
+
"""
|
|
45
|
+
TupleDataObservableCache.__init__(self)
|
|
46
|
+
|
|
47
|
+
self._proxyToVortexName = proxyToVortexName
|
|
48
|
+
self._subscriptionsEnabled = subscriptionsEnabled
|
|
49
|
+
self._observerName = observerName
|
|
50
|
+
self._filt = dict(name=observableName, key="tupleDataObservable")
|
|
51
|
+
if additionalFilt:
|
|
52
|
+
self._filt.update(additionalFilt)
|
|
53
|
+
|
|
54
|
+
# Create the local observable, this allows local tuple providers
|
|
55
|
+
# The rest are proxied on to the backend
|
|
56
|
+
self._localObservableHandler = TupleDataObservableHandler(
|
|
57
|
+
observableName,
|
|
58
|
+
additionalFilt=additionalFilt,
|
|
59
|
+
subscriptionsEnabled=subscriptionsEnabled,
|
|
60
|
+
)
|
|
61
|
+
# Shutdown the local observables' endpoint, we don't want it
|
|
62
|
+
# listening its self
|
|
63
|
+
self._localObservableHandler.shutdown()
|
|
64
|
+
|
|
65
|
+
# Finally, Setup our endpoint
|
|
66
|
+
self._endpoint = PayloadEndpoint(self._filt, self._process)
|
|
67
|
+
|
|
68
|
+
TupleDataObservableCache.start(self)
|
|
69
|
+
|
|
70
|
+
def shutdown(self):
|
|
71
|
+
self._endpoint.shutdown()
|
|
72
|
+
TupleDataObservableCache.shutdown(self)
|
|
73
|
+
|
|
74
|
+
## ----- Implement local observable
|
|
75
|
+
|
|
76
|
+
def addTupleProvider(self, tupleName, provider: TuplesProviderABC):
|
|
77
|
+
"""Add Tuple Provider
|
|
78
|
+
|
|
79
|
+
Adds a tuple provider to the local observable.
|
|
80
|
+
|
|
81
|
+
All other requests are proxied on
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
self._localObservableHandler.addTupleProvider(
|
|
85
|
+
tupleName, provider=provider
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def notifyOfTupleUpdate(self, tupleSelector: TupleSelector) -> None:
|
|
89
|
+
"""Notify of Tuple Update
|
|
90
|
+
|
|
91
|
+
Notifies the local observable that tuples have been updated
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
if not self._localObservableHandler.hasTupleProvider(
|
|
95
|
+
tupleSelector.name
|
|
96
|
+
):
|
|
97
|
+
raise Exception(
|
|
98
|
+
"Local observable doesn't have tuple provider for %s"
|
|
99
|
+
" registered, Proxy is : %s" % (tupleSelector.name, self._filt)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
self._localObservableHandler.notifyOfTupleUpdate(tupleSelector)
|
|
103
|
+
|
|
104
|
+
def _process(
|
|
105
|
+
self,
|
|
106
|
+
payloadEnvelope: PayloadEnvelope,
|
|
107
|
+
vortexUuid: str,
|
|
108
|
+
vortexName: str,
|
|
109
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
110
|
+
**kwargs,
|
|
111
|
+
):
|
|
112
|
+
|
|
113
|
+
if vortexName == self._proxyToVortexName:
|
|
114
|
+
return self._processUpdateFromBackend(payloadEnvelope)
|
|
115
|
+
|
|
116
|
+
else:
|
|
117
|
+
d = self._processSubscribeFromFrontend(
|
|
118
|
+
payloadEnvelope, vortexUuid, sendResponse
|
|
119
|
+
)
|
|
120
|
+
if d:
|
|
121
|
+
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
122
|
+
|
|
123
|
+
@limitConcurrency(
|
|
124
|
+
logger, 10, deduplicateBasedOnKwArgsKey=_dedupProcessorCallKeys
|
|
125
|
+
)
|
|
126
|
+
def _processSubscribeFromFrontend(
|
|
127
|
+
self,
|
|
128
|
+
payloadEnvelope: PayloadEnvelope,
|
|
129
|
+
vortexUuid: str,
|
|
130
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
131
|
+
):
|
|
132
|
+
|
|
133
|
+
tupleSelector: TupleSelector = payloadEnvelope.filt["tupleSelector"]
|
|
134
|
+
|
|
135
|
+
# If the local observable provides this tuple, then use that instead
|
|
136
|
+
if self._localObservableHandler.hasTupleProvider(tupleSelector.name):
|
|
137
|
+
return self._localObservableHandler._process(
|
|
138
|
+
payloadEnvelope=payloadEnvelope,
|
|
139
|
+
vortexUuid=vortexUuid,
|
|
140
|
+
sendResponse=sendResponse,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if not VortexFactory.isVortexUuidOnline(vortexUuid):
|
|
144
|
+
logger.debug(
|
|
145
|
+
"Vortex %s is no longer online, skipping reply", vortexUuid
|
|
146
|
+
)
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
# logger.debug(
|
|
150
|
+
# "Vortex %s is STILL online, continuing to reply", vortexUuid
|
|
151
|
+
# )
|
|
152
|
+
|
|
153
|
+
# Add support for just getting data, no subscription.
|
|
154
|
+
if payloadEnvelope.filt.get("unsubscribe", True):
|
|
155
|
+
return self._handleUnsubscribe(tupleSelector, vortexUuid)
|
|
156
|
+
|
|
157
|
+
elif (
|
|
158
|
+
payloadEnvelope.filt.get("subscribe", True)
|
|
159
|
+
and self._subscriptionsEnabled
|
|
160
|
+
):
|
|
161
|
+
return self._handleSubscribe(
|
|
162
|
+
payloadEnvelope, tupleSelector, sendResponse, vortexUuid
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
else:
|
|
166
|
+
return self._handlePoll(
|
|
167
|
+
payloadEnvelope, tupleSelector, sendResponse
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def _handleUnsubscribe(self, tupleSelector: TupleSelector, vortexUuid: str):
|
|
171
|
+
|
|
172
|
+
if not self._hasTupleSelector(tupleSelector):
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
cache = self._getCache(tupleSelector)
|
|
176
|
+
try:
|
|
177
|
+
cache.vortexUuids.remove(vortexUuid)
|
|
178
|
+
except KeyError:
|
|
179
|
+
pass
|
|
180
|
+
|
|
181
|
+
def _handleSubscribe(
|
|
182
|
+
self,
|
|
183
|
+
payloadEnvelope: PayloadEnvelope,
|
|
184
|
+
tupleSelector: TupleSelector,
|
|
185
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
186
|
+
vortexUuid: str,
|
|
187
|
+
):
|
|
188
|
+
|
|
189
|
+
# Add support for just getting data, no subscription.
|
|
190
|
+
cache = self._getCache(tupleSelector)
|
|
191
|
+
if (
|
|
192
|
+
cache
|
|
193
|
+
and cache.lastServerPayloadDate is not None
|
|
194
|
+
and cache.cacheEnabled
|
|
195
|
+
):
|
|
196
|
+
respPayloadEnvelope = PayloadEnvelope(
|
|
197
|
+
filt=payloadEnvelope.filt,
|
|
198
|
+
encodedPayload=cache.encodedPayload,
|
|
199
|
+
date=cache.lastServerPayloadDate,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
d = respPayloadEnvelope.toVortexMsgDefer()
|
|
203
|
+
d.addCallback(sendResponse)
|
|
204
|
+
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
205
|
+
|
|
206
|
+
elif cache:
|
|
207
|
+
self._sendRequestToServer(payloadEnvelope)
|
|
208
|
+
|
|
209
|
+
else:
|
|
210
|
+
cache = self._makeCache(tupleSelector)
|
|
211
|
+
self._sendRequestToServer(payloadEnvelope)
|
|
212
|
+
|
|
213
|
+
cache.vortexUuids.add(vortexUuid)
|
|
214
|
+
# Allow the cache to be disabled
|
|
215
|
+
cache.cacheEnabled = (
|
|
216
|
+
cache.cacheEnabled
|
|
217
|
+
and not payloadEnvelope.filt.get("disableCache", False)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def _sendRequestToServer(self, payloadEnvelope: PayloadEnvelope):
|
|
221
|
+
payloadEnvelope.filt["observerName"] = self._observerName
|
|
222
|
+
d = VortexFactory.sendVortexMsg(
|
|
223
|
+
vortexMsgs=payloadEnvelope.toVortexMsg(),
|
|
224
|
+
destVortexName=self._proxyToVortexName,
|
|
225
|
+
)
|
|
226
|
+
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
227
|
+
|
|
228
|
+
def _sendUnsubscribeToServer(self, tupleSelector: TupleSelector):
|
|
229
|
+
payloadEnvelope = PayloadEnvelope()
|
|
230
|
+
payloadEnvelope.filt["tupleSelector"] = tupleSelector
|
|
231
|
+
payloadEnvelope.filt["unsubscribe"] = True
|
|
232
|
+
self._sendRequestToServer(payloadEnvelope)
|
|
233
|
+
|
|
234
|
+
def _handlePoll(
|
|
235
|
+
self,
|
|
236
|
+
payloadEnvelope: PayloadEnvelope,
|
|
237
|
+
tupleSelector: TupleSelector,
|
|
238
|
+
sendResponse: SendVortexMsgResponseCallable,
|
|
239
|
+
):
|
|
240
|
+
|
|
241
|
+
useCache = payloadEnvelope.filt.get("useCache", True)
|
|
242
|
+
|
|
243
|
+
# Keep a copy of the incoming filt, in case they are using PayloadResponse
|
|
244
|
+
responseFilt = copy(payloadEnvelope.filt)
|
|
245
|
+
|
|
246
|
+
# Restore the original payload filt (PayloadResponse) and send it back
|
|
247
|
+
def reply(payload):
|
|
248
|
+
payload.filt = responseFilt
|
|
249
|
+
d = payload.toVortexMsgDefer()
|
|
250
|
+
d.addCallback(sendResponse)
|
|
251
|
+
d.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
252
|
+
# logger.debug("Received response from observable")
|
|
253
|
+
|
|
254
|
+
if useCache:
|
|
255
|
+
cache = self._getCache(tupleSelector)
|
|
256
|
+
if (
|
|
257
|
+
cache
|
|
258
|
+
and cache.lastServerPayloadDate is not None
|
|
259
|
+
and cache.cacheEnabled
|
|
260
|
+
):
|
|
261
|
+
payloadEnvelope.encodedPayload = cache.encodedPayload
|
|
262
|
+
payloadEnvelope.date = cache.lastServerPayloadDate
|
|
263
|
+
reply(payloadEnvelope)
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
# Track the response, log an error if it fails
|
|
267
|
+
# 5 Seconds is long enough
|
|
268
|
+
pr = PayloadResponse(
|
|
269
|
+
payloadEnvelope,
|
|
270
|
+
timeout=PayloadResponse.TIMEOUT - 5, # 5 seconds less
|
|
271
|
+
logTimeoutError=False,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
pr.addErrback(self._handlePrFailure, tupleSelector)
|
|
275
|
+
pr.addErrback(vortexLogFailure, logger, consumeError=True)
|
|
276
|
+
pr.addCallback(reply)
|
|
277
|
+
|
|
278
|
+
self._sendRequestToServer(payloadEnvelope)
|
|
279
|
+
|
|
280
|
+
def _handlePrFailure(self, f: Failure, tupleSelector):
|
|
281
|
+
if f.check(TimeoutError):
|
|
282
|
+
logger.error(
|
|
283
|
+
"Received no response from\nobservable %s\ntuple selector %s",
|
|
284
|
+
self._filt,
|
|
285
|
+
tupleSelector.toJsonStr(),
|
|
286
|
+
)
|
|
287
|
+
else:
|
|
288
|
+
logger.error(
|
|
289
|
+
"Unexpected error, %s\nobservable %s\ntuple selector %s",
|
|
290
|
+
f,
|
|
291
|
+
self._filt,
|
|
292
|
+
tupleSelector.toJsonStr(),
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
@deferToThreadWrapWithLogger(logger)
|
|
296
|
+
def _processUpdateFromBackend(self, payloadEnvelope: PayloadEnvelope):
|
|
297
|
+
|
|
298
|
+
tupleSelector: TupleSelector = payloadEnvelope.filt["tupleSelector"]
|
|
299
|
+
|
|
300
|
+
if not self._hasTupleSelector(tupleSelector):
|
|
301
|
+
return
|
|
302
|
+
|
|
303
|
+
cache, requiredUpdate = self._updateCache(payloadEnvelope)
|
|
304
|
+
if not requiredUpdate:
|
|
305
|
+
return
|
|
306
|
+
|
|
307
|
+
# Get / update the list of observing UUIDs
|
|
308
|
+
observingUuids = (
|
|
309
|
+
cache.vortexUuids & VortexFactory.getRemoteVortexUuids()
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
if not observingUuids:
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
# Create the vortexMsg
|
|
316
|
+
vortexMsg = payloadEnvelope.toVortexMsg()
|
|
317
|
+
|
|
318
|
+
# Send the vortex messages
|
|
319
|
+
for vortexUuid in observingUuids:
|
|
320
|
+
reactor.callLater(
|
|
321
|
+
0,
|
|
322
|
+
self._sendVortexMsgInReactor,
|
|
323
|
+
vortexMsg=vortexMsg,
|
|
324
|
+
destVortexUuid=vortexUuid,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
def _sendVortexMsgInReactor(self, vortexMsg, destVortexUuid):
|
|
328
|
+
# Send the vortex messages
|
|
329
|
+
d = VortexFactory.sendVortexMsg(
|
|
330
|
+
vortexMsgs=vortexMsg, destVortexUuid=destVortexUuid
|
|
331
|
+
)
|
|
332
|
+
d.addErrback(self._sendErrback)
|
|
333
|
+
|
|
334
|
+
def _sendErrback(self, failure):
|
|
335
|
+
|
|
336
|
+
if failure.check(NoVortexException):
|
|
337
|
+
logger.debug(str(failure.value))
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
vortexLogFailure(failure, logger)
|