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,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