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.
Files changed (87) hide show
  1. vortex/DataWrapTuple.py +27 -0
  2. vortex/DeferUtil.py +224 -0
  3. vortex/Jsonable.py +304 -0
  4. vortex/Payload.py +115 -0
  5. vortex/PayloadEndpoint.py +216 -0
  6. vortex/PayloadEndpointTest.py +164 -0
  7. vortex/PayloadEnvelope.py +166 -0
  8. vortex/PayloadFilterKeys.py +15 -0
  9. vortex/PayloadIO.py +152 -0
  10. vortex/PayloadPriority.py +11 -0
  11. vortex/PayloadResponse.py +173 -0
  12. vortex/PayloadResponseTest.py +39 -0
  13. vortex/PayloadTest.py +136 -0
  14. vortex/PerformTestActionTuple.py +30 -0
  15. vortex/SerialiseUtil.py +273 -0
  16. vortex/TestTuple.py +61 -0
  17. vortex/Tuple.py +968 -0
  18. vortex/TupleAction.py +72 -0
  19. vortex/TupleActionVortex.py +5 -0
  20. vortex/TupleDataForTest.py +25 -0
  21. vortex/TuplePerfTest.py +71 -0
  22. vortex/TuplePolymorphicTupleTypeFieldArgTest.py +74 -0
  23. vortex/TupleRestfulTest.py +140 -0
  24. vortex/TupleSelector.py +105 -0
  25. vortex/TupleTest.py +127 -0
  26. vortex/VortexABC.py +55 -0
  27. vortex/VortexClientHttp.py +308 -0
  28. vortex/VortexClientHttpTest.py +71 -0
  29. vortex/VortexClientTcp.py +355 -0
  30. vortex/VortexClientWebsocketFactory.py +383 -0
  31. vortex/VortexConnectionABC.py +88 -0
  32. vortex/VortexFactory.py +932 -0
  33. vortex/VortexPayloadProtocol.py +180 -0
  34. vortex/VortexServer.py +367 -0
  35. vortex/VortexServerConnection.py +127 -0
  36. vortex/VortexServerHttpResource.py +193 -0
  37. vortex/VortexServerTcp.py +104 -0
  38. vortex/VortexServerWebsocket.py +287 -0
  39. vortex/VortexTcpMemoryLeakPayloadTest.py +611 -0
  40. vortex/VortexTcpMemoryLeakRawTest.py +87 -0
  41. vortex/VortexTest.py +22 -0
  42. vortex/VortexUtil.py +314 -0
  43. vortex/VortexWritePushProducer.py +321 -0
  44. vortex/__init__.py +10 -0
  45. vortex/data_loader/TupleDataLiveLockManager.py +383 -0
  46. vortex/data_loader/TupleDataLoader.py +326 -0
  47. vortex/data_loader/TupleDataLoaderDelegate.py +90 -0
  48. vortex/data_loader/TupleDataLoaderTupleABC.py +10 -0
  49. vortex/data_loader/TupleDataLoaderTuples.py +53 -0
  50. vortex/data_loader/__init__.py +0 -0
  51. vortex/event_bus/TupleChangeEventABC.py +15 -0
  52. vortex/event_bus/TupleChangeEventBus.py +192 -0
  53. vortex/event_bus/TupleChangeEventBusObserverABC.py +11 -0
  54. vortex/event_bus/__init__.py +0 -0
  55. vortex/handler/TupleActionProcessor.py +270 -0
  56. vortex/handler/TupleActionProcessorProxy.py +218 -0
  57. vortex/handler/TupleActionProcessorTestHandler.py +27 -0
  58. vortex/handler/TupleDataActionClient.py +67 -0
  59. vortex/handler/TupleDataObservableCache.py +201 -0
  60. vortex/handler/TupleDataObservableHandler.py +371 -0
  61. vortex/handler/TupleDataObservableProxyHandler.py +340 -0
  62. vortex/handler/TupleDataObservableTestHandler.py +60 -0
  63. vortex/handler/TupleDataObserverClient.py +158 -0
  64. vortex/handler/VortexJSTupleLoaderTestHandler.py +42 -0
  65. vortex/handler/__init__.py +0 -0
  66. vortex/restful/GzippedDataHttpClient.py +170 -0
  67. vortex/restful/GzippedDataHttpClientTest.py +103 -0
  68. vortex/restful/GzippedPayloadHttpClient.py +181 -0
  69. vortex/restful/RestfulHttpClient.py +107 -0
  70. vortex/restful/RestfulHttpClientTest.py +60 -0
  71. vortex/restful/RestfulResource.py +153 -0
  72. vortex/restful/RestfulResourceTest.py +100 -0
  73. vortex/restful/__init__.py +0 -0
  74. vortex/rpc/RPC.py +446 -0
  75. vortex/rpc/RPCTest.py +12 -0
  76. vortex/rpc/__init__.py +0 -0
  77. vortex/sqla_orm/OrmCrudHandler.py +431 -0
  78. vortex/sqla_orm/TupleProviderForDb.py +42 -0
  79. vortex/sqla_orm/VortexTupleJson.py +24 -0
  80. vortex/sqla_orm/__init__.py +0 -0
  81. vortex/storage/TupleStorageSqlite.py +359 -0
  82. vortex/storage/__init__.py +0 -0
  83. vortexpy-4.0.23.dist-info/LICENSE +21 -0
  84. vortexpy-4.0.23.dist-info/METADATA +43 -0
  85. vortexpy-4.0.23.dist-info/RECORD +87 -0
  86. vortexpy-4.0.23.dist-info/WHEEL +5 -0
  87. 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)