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
vortex/VortexTest.py ADDED
@@ -0,0 +1,22 @@
1
+ """
2
+ * Created by Synerty Pty Ltd
3
+ *
4
+ * This software is open source, the MIT license applies.
5
+ *
6
+ * Website : http://www.synerty.com
7
+ * Support : support@synerty.com
8
+ """
9
+
10
+ from twisted.trial import unittest
11
+
12
+ from run_test_vortex_server import setupVortexServer
13
+ from vortex.VortexClientHttp import VortexClientHttp
14
+
15
+
16
+ class VortexTest(unittest.TestCase):
17
+ def setUp(self):
18
+ self.port = setupVortexServer()
19
+
20
+ def testVortexConnect(self):
21
+ client = VortexClientHttp("unittest")
22
+ return client.connect("127.0.0.1", self.port)
vortex/VortexUtil.py ADDED
@@ -0,0 +1,314 @@
1
+ import json
2
+ from collections import namedtuple
3
+ from datetime import datetime
4
+ from datetime import timedelta
5
+ from typing import Any
6
+ from typing import Callable
7
+ from typing import Dict
8
+ from typing import Optional
9
+ from typing import Set
10
+ from typing import Union
11
+ from uuid import uuid4
12
+
13
+ import pytz
14
+ from twisted.internet import reactor
15
+ from twisted.internet.defer import Deferred
16
+ from twisted.internet.defer import fail
17
+ from twisted.internet.defer import maybeDeferred
18
+ from twisted.python.failure import Failure
19
+
20
+ from vortex.PayloadEnvelope import PayloadEnvelope
21
+ from vortex.PayloadEnvelope import VortexMsgList
22
+ from vortex.Tuple import Tuple
23
+ from vortex.Tuple import TupleField
24
+ from vortex.Tuple import addTupleType
25
+
26
+ _LimitConcurrencyArgsTuple = namedtuple(
27
+ "_LimitConcurrencyArgsTuple",
28
+ ("deferred", "funcSelf", "args", "kwargs", "queuedTime"),
29
+ )
30
+
31
+
32
+ def _dedupProcessorCallKeys(
33
+ payloadEnvelope: PayloadEnvelope,
34
+ vortexUuid: str,
35
+ *args,
36
+ **kwargs,
37
+ ):
38
+ filt = payloadEnvelope.filt.copy()
39
+ from vortex.PayloadResponse import PayloadResponse
40
+
41
+ filt.pop(PayloadResponse.MESSAGE_ID_KEY, None)
42
+ return vortexUuid, str(filt)
43
+
44
+
45
+ def limitConcurrency(
46
+ decoratorLogger,
47
+ concurrency: int,
48
+ logTaskTimeGreaterThanSeconds=5.0,
49
+ deduplicateBasedOnKwArgsKey: Optional[Callable] = None,
50
+ ):
51
+ """Limit Concurrency to a call, decorator
52
+
53
+ :param decoratorLogger: The logger to log messages for.
54
+ :param concurrency: The number of concurrent calls
55
+ :param logTaskTimeGreaterThanSeconds: If a task takes longer then this
56
+ :param deduplicateBasedOnKwArgsKey: This is a callable that returns a tuple
57
+ of strings. If this argument is provided,
58
+ then duplicate calls with the same values for these arguments will
59
+ be deduplicated. Duplicate calls will use the newest set of arguments.
60
+
61
+ # As of python 3.6, dicts are ordered.
62
+
63
+ >>> x={}
64
+ >>> x[1]=None
65
+ >>> x
66
+ {1: None}
67
+ >>> x[0]=None
68
+ >>> x
69
+ {1: None, 0: None}
70
+ >>> x[123]=None
71
+ >>> x
72
+ {1: None, 0: None, 123: None}
73
+ # Updating a item in the dict retains the same position
74
+ >>> x[0]=None
75
+ >>> x
76
+ {1: None, 0: None, 123: None}
77
+
78
+ # Poping the first off can be done with an iterator.
79
+ >>> next(iter(x.keys()))
80
+ 1
81
+
82
+ """
83
+
84
+ class Wrap:
85
+ def __init__(self, f):
86
+ self._f = f
87
+ self._callQueue: dict[Any, _LimitConcurrencyArgsTuple] = {}
88
+ self._runningCount = 0
89
+ self._processedCount = 0
90
+ self._lastLogTime = None
91
+ self._lastQueueProcessingDelta = None
92
+
93
+ def _callComplete(self, result, callStartTime):
94
+ self._runningCount -= 1
95
+ self._processedCount += 1
96
+ # decoratorLogger.debug("Queued task completed %s", str(self._f))
97
+
98
+ processingTime = datetime.now(pytz.utc) - callStartTime
99
+ if logTaskTimeGreaterThanSeconds < processingTime.total_seconds():
100
+ decoratorLogger.debug(
101
+ "%s call took %s",
102
+ self._f.__name__,
103
+ processingTime,
104
+ )
105
+
106
+ reactor.callLater(0, self._processNext)
107
+ return result
108
+
109
+ def _processNext(self):
110
+ now = datetime.now(pytz.utc)
111
+ while self._runningCount < concurrency and self._callQueue:
112
+ # decoratorLogger.debug("Queueing new task %s", str(self._f))
113
+ self._runningCount += 1
114
+
115
+ nextKey = next(iter(self._callQueue.keys()))
116
+
117
+ args = self._callQueue.pop(nextKey)
118
+
119
+ self._lastQueueProcessingDelta = now - args.queuedTime
120
+
121
+ callWithArgs = lambda: self._f(
122
+ args.funcSelf, *args.args, **args.kwargs
123
+ )
124
+ d = maybeDeferred(callWithArgs)
125
+ d.addBoth(self._callComplete, now)
126
+ d.chainDeferred(args.deferred)
127
+
128
+ self._logQueueSize()
129
+ self._f.running = self._runningCount
130
+ self._f.waiting = len(self._callQueue)
131
+
132
+ def call(self, funcSelf, *args, **kwargs):
133
+ if deduplicateBasedOnKwArgsKey:
134
+ key = deduplicateBasedOnKwArgsKey(*args, **kwargs)
135
+ else:
136
+ key = uuid4()
137
+
138
+ if key in self._callQueue:
139
+ decoratorLogger.debug(
140
+ "%s call was deduplicated for key %s",
141
+ self._f.__name__,
142
+ str(key),
143
+ )
144
+
145
+ d = Deferred()
146
+ self._callQueue[key] = _LimitConcurrencyArgsTuple(
147
+ d, funcSelf, args, kwargs, datetime.now(pytz.utc)
148
+ )
149
+ self._processNext()
150
+ return d
151
+
152
+ def _logQueueSize(self):
153
+ if (
154
+ self._lastLogTime
155
+ and (datetime.now(pytz.utc) - self._lastLogTime).total_seconds()
156
+ < 5.0
157
+ ):
158
+ return
159
+
160
+ deltaTime = self._lastQueueProcessingDelta
161
+ queueSize = len(self._callQueue)
162
+
163
+ def logIt(logFunc):
164
+ self._lastLogTime = datetime.now(pytz.utc)
165
+ logFunc(
166
+ "%s last call was queued for %s, queue size %s,"
167
+ " processed %s since last log message",
168
+ self._f.__name__,
169
+ deltaTime,
170
+ queueSize,
171
+ self._processedCount,
172
+ )
173
+ self._processedCount = 0
174
+
175
+ if 15.0 < deltaTime.total_seconds() or 2000 < queueSize:
176
+ logIt(decoratorLogger.warning)
177
+
178
+ elif 5.0 < deltaTime.total_seconds() or 200 < queueSize:
179
+ logIt(decoratorLogger.debug)
180
+
181
+ def wrap(f):
182
+ wrapC = Wrap(f)
183
+
184
+ def wrapInner(funcSelf, *args, **kwargs):
185
+ return wrapC.call(funcSelf, *args, **kwargs)
186
+
187
+ return wrapInner
188
+
189
+ return wrap
190
+
191
+
192
+ @addTupleType
193
+ class _DebounceArgsTuple(Tuple):
194
+ __tupleType__ = __name__ + "._DebounceArgsTuple"
195
+ args = TupleField()
196
+ kwargs = TupleField()
197
+
198
+
199
+ def debounceCall(debounceSeconds: float):
200
+ """Call a debounced function that delays invoking wrapped function until after wait n seconds
201
+
202
+ :param debounceSeconds: cooling down peroid of n seconds
203
+ :return: the wrapped function
204
+ """
205
+
206
+ class Wrap:
207
+ _lastCleanup = datetime.now(pytz.utc)
208
+ _cleanupDelta = timedelta(minutes=1, seconds=debounceSeconds)
209
+
210
+ _debounceTimeDelta = timedelta(seconds=debounceSeconds)
211
+
212
+ _calls: Dict[str, datetime] = {}
213
+ _callsQueued: Set[tuple] = set()
214
+
215
+ def __init__(self, f):
216
+ self._f = f
217
+
218
+ def __cleanup(self):
219
+ now = datetime.now(pytz.utc)
220
+ # Cleanup the old values
221
+ if self._cleanupDelta < now - self._lastCleanup:
222
+ self._lastCleanup = now
223
+
224
+ for key, val in list(self._calls.items()):
225
+ if (
226
+ self._cleanupDelta < now - val
227
+ and key not in self._callsQueued
228
+ ):
229
+ self._calls.pop(key)
230
+
231
+ def __wrapArgsAndKwargs(self, *args, **kwargs):
232
+ if not args and not kwargs:
233
+ return ""
234
+ hashedArgs = _DebounceArgsTuple(
235
+ args=args, kwargs=kwargs
236
+ ).toJsonDict()
237
+ return json.dumps(hashedArgs)
238
+
239
+ def call(self, funcSelf, *args, **kwargs):
240
+ now = datetime.now(pytz.utc)
241
+
242
+ # Create a hash of the args
243
+ hashedArgs = self.__wrapArgsAndKwargs(args, kwargs)
244
+ self.__cleanup()
245
+
246
+ # If this method has never been called before, call it
247
+ lastCallDate = self._calls.get(hashedArgs)
248
+ if not lastCallDate:
249
+ self._calls[hashedArgs] = now
250
+ return self._f(funcSelf, *args, **kwargs)
251
+
252
+ # Work out how long it's been since this method has been called
253
+ timeSinceLastCall = now - lastCallDate
254
+
255
+ # If this method is overdue for a call, call it
256
+ if timedelta(seconds=debounceSeconds) < timeSinceLastCall:
257
+ # If this call was queued, remove it
258
+ if hashedArgs in self._callsQueued:
259
+ self._callsQueued.remove(hashedArgs)
260
+ self._calls[hashedArgs] = now
261
+ return self._f(funcSelf, *args, **kwargs)
262
+
263
+ timeToNextCall = (
264
+ timedelta(seconds=debounceSeconds) - timeSinceLastCall
265
+ )
266
+
267
+ # If not, schedule this method for call after the debounce
268
+ # but only if there isn't already a call queued
269
+ if hashedArgs not in self._callsQueued:
270
+ self._callsQueued.add(hashedArgs)
271
+ reactor.callLater(
272
+ timeToNextCall.total_seconds() + 0.1,
273
+ self.call,
274
+ funcSelf,
275
+ *args,
276
+ **kwargs,
277
+ )
278
+
279
+ return
280
+
281
+ return
282
+
283
+ def wrap(f):
284
+ wrapC = Wrap(f)
285
+
286
+ def wrapInner(funcSelf, *args, **kwargs):
287
+ return wrapC.call(funcSelf, *args, **kwargs)
288
+
289
+ return wrapInner
290
+
291
+ return wrap
292
+
293
+
294
+ def logLargeMessages(
295
+ logger, vortexMsgs: Union[VortexMsgList, bytes], destVortexUuid: str
296
+ ):
297
+ def logIt(vortexMsg):
298
+ # Log anything larger than 128kb
299
+ length = len(vortexMsg)
300
+ if length < 512 * 1024:
301
+ return
302
+
303
+ logger.debug(
304
+ "Sending vortexMsg of size %s to vortex %s",
305
+ "{:,}kB".format(int(length / 1024)),
306
+ destVortexUuid,
307
+ )
308
+
309
+ if isinstance(vortexMsgs, bytes):
310
+ logIt(vortexMsgs)
311
+
312
+ else:
313
+ for msg in vortexMsgs:
314
+ logIt(msg)
@@ -0,0 +1,321 @@
1
+ """
2
+ * Created by Synerty Pty Ltd
3
+ *
4
+ * This software is open source, the MIT license applies.
5
+ *
6
+ * Website : http://www.synerty.com
7
+ * Support : support@synerty.com
8
+ """
9
+ import logging
10
+ import typing
11
+ import weakref
12
+ from collections import deque, defaultdict
13
+ from typing import Callable, Deque, Dict
14
+
15
+ from twisted.internet.interfaces import IPushProducer
16
+ from zope.interface import implementer
17
+
18
+ from vortex.DeferUtil import nonConcurrentMethod
19
+ from vortex.PayloadPriority import DEFAULT_PRIORITY
20
+
21
+ logger = logging.getLogger(name=__name__)
22
+
23
+
24
+ def _format_size(size):
25
+ for unit in ("B", "KiB", "MiB", "GiB", "TiB"):
26
+ if abs(size) < 100 and unit != "B":
27
+ # 3 digits (xx.x UNIT)
28
+ return "%.1f %s" % (size, unit)
29
+ if abs(size) < 10 * 1024 or unit == "TiB":
30
+ # 4 or 5 digits (xxxx UNIT)
31
+ return "%.0f %s" % (size, unit)
32
+ size /= 1024
33
+
34
+
35
+ @implementer(IPushProducer)
36
+ class VortexWritePushProducer:
37
+ WARNING_DATA_LENGTH = 50 * 1024 * 1024
38
+ ERROR_DATA_LENGTH = 500 * 1024 * 1024
39
+ WRITE_CHUNK_SIZE = 128 * 1024
40
+
41
+ __memoryLoggingRefs = None
42
+ __memoryLoggingEnabled = False
43
+ _DEBUG_LOGGING = False
44
+
45
+ @classmethod
46
+ def setupMemoryLogging(cls) -> None:
47
+ cls.__memoryLoggingRefs = []
48
+ cls.__memoryLoggingEnabled = True
49
+
50
+ @classmethod
51
+ def memoryLoggingDump(
52
+ cls, top=10, msgs=1
53
+ ) -> typing.List[typing.Tuple[str, int, int]]:
54
+ if not cls.__memoryLoggingRefs:
55
+ return []
56
+
57
+ # Filter out expired items
58
+ cls.__memoryLoggingRefs = list(
59
+ filter(lambda o: o(), cls.__memoryLoggingRefs)
60
+ )
61
+
62
+ results = []
63
+ for producerRef in cls.__memoryLoggingRefs:
64
+ producer = producerRef()
65
+ if not producer:
66
+ continue
67
+
68
+ queueCount = sum(
69
+ [len(q) for q in producer._queueByPriority.values()]
70
+ )
71
+
72
+ results.append(
73
+ (
74
+ producer._remoteVortexName,
75
+ queueCount,
76
+ producer._queuedDataLen,
77
+ )
78
+ )
79
+
80
+ data = sorted(results, key=lambda x: x[2], reverse=True)
81
+
82
+ return list(filter(lambda x: x[2] >= msgs, data))[:top]
83
+
84
+ def __init__(
85
+ self,
86
+ transport,
87
+ stopProducingCallback: Callable,
88
+ remoteVortexName: str = "Pending",
89
+ writeWholeFrames=False,
90
+ terminateFrameWithDot=True,
91
+ splitFrames=False,
92
+ ):
93
+ """
94
+
95
+ :param splitFrames: Split frames that come in if they are larger than 128kb.
96
+ This allows better use of ram, as keeping a 1mb to ?mb bytes object around
97
+ is bad for memory management and memory fragmentation.
98
+ """
99
+ if VortexWritePushProducer.__memoryLoggingEnabled:
100
+ VortexWritePushProducer.__memoryLoggingRefs.append(
101
+ weakref.ref(self)
102
+ )
103
+
104
+ self._transport = transport
105
+ self._stopProducingCallback = stopProducingCallback
106
+ self._remoteVortexName = remoteVortexName
107
+ self._writeWholeFrames = writeWholeFrames
108
+ self._terminateFrameWithDot = terminateFrameWithDot
109
+ self._splitFrames = splitFrames
110
+
111
+ self._paused = False
112
+ self._pausedDeferred = None
113
+ self._writingInProgress = False
114
+ self._writingFrameInProgress = False
115
+ self._closed = False
116
+ self._queuedDataLen = 0
117
+ self._queueByPriority: Dict[int, Deque] = defaultdict(deque)
118
+
119
+ self._currentlyWritingData: typing.Optional[bytes] = None
120
+ self._currentlyWritingDataOffset: int = 0
121
+
122
+ def setRemoteVortexName(self, remoteVortexName: str):
123
+ self._remoteVortexName = remoteVortexName
124
+
125
+ @property
126
+ def _canContinue(self):
127
+ return (
128
+ not self._paused
129
+ and not self._startWritingFrame.running
130
+ and [q for q in self._queueByPriority.values() if q]
131
+ )
132
+
133
+ @nonConcurrentMethod
134
+ def _startWriting(self):
135
+ if self._currentlyWritingData is not None:
136
+ self._startWritingFrame()
137
+
138
+ while self._canContinue:
139
+ # Send the messages in order of priority
140
+ for priority in sorted(self._queueByPriority):
141
+ if not self._canContinue:
142
+ return
143
+
144
+ queue = self._queueByPriority[priority]
145
+ if not queue:
146
+ continue
147
+
148
+ data = queue.popleft()
149
+ preLen = self._queuedDataLen
150
+ self._queuedDataLen -= len(data)
151
+
152
+ if self._queuedDataLen < self.WARNING_DATA_LENGTH < preLen:
153
+ logger.info(
154
+ "%s: Data Queue memory high warning - returned to normal : %s",
155
+ self._remoteVortexName,
156
+ _format_size(self._queuedDataLen),
157
+ )
158
+
159
+ if self._queuedDataLen < self.ERROR_DATA_LENGTH < preLen:
160
+ logger.info(
161
+ "%s: Data Queue memory high error - returned to warning : %s",
162
+ self._remoteVortexName,
163
+ _format_size(self._queuedDataLen),
164
+ )
165
+
166
+ if self._writeWholeFrames:
167
+ self._transport.write(data)
168
+ if self._terminateFrameWithDot:
169
+ self._transport.write(b".")
170
+
171
+ else:
172
+ # Use a memoryview for fast slicing.
173
+ self._currentlyWritingData = data
174
+ self._currentlyWritingDataOffset = 0
175
+ self._startWritingFrame()
176
+
177
+ @nonConcurrentMethod
178
+ def _startWritingFrame(self):
179
+ # ---------------
180
+ # Write in progress logic.
181
+ # We should only have one write loop at a time
182
+ if self._writingFrameInProgress:
183
+ return
184
+ self._writingFrameInProgress = True
185
+
186
+ data = self._currentlyWritingData
187
+ offset = self._currentlyWritingDataOffset
188
+ while not self._paused and offset < len(data):
189
+ if self._DEBUG_LOGGING:
190
+ logger.debug(
191
+ "%s: Producer _startWritingFrame called,"
192
+ " wrote %s to %s of %s",
193
+ self._remoteVortexName,
194
+ offset,
195
+ min(offset + self.WRITE_CHUNK_SIZE, len(data)),
196
+ len(data),
197
+ )
198
+ self._transport.write(data[offset : offset + self.WRITE_CHUNK_SIZE])
199
+ offset += self.WRITE_CHUNK_SIZE
200
+
201
+ if len(data) <= offset:
202
+ self._currentlyWritingDataOffset = 0
203
+ self._currentlyWritingData = None
204
+ if self._terminateFrameWithDot:
205
+ self._transport.write(b".")
206
+
207
+ else:
208
+ self._currentlyWritingDataOffset = offset
209
+
210
+ self._writingFrameInProgress = False
211
+
212
+ def pauseProducing(self):
213
+ """
214
+ Pause producing data.
215
+
216
+ Tells a producer that it has produced too much data to process for
217
+ the time being, and to stop until C{resumeProducing()} is called.
218
+ """
219
+ self._paused = True
220
+ if self._DEBUG_LOGGING:
221
+ logger.debug(
222
+ "%s: Producer paused, data len = %s",
223
+ self._remoteVortexName,
224
+ self._queuedDataLen,
225
+ )
226
+
227
+ def resumeProducing(self):
228
+ """
229
+ Resume producing data.
230
+
231
+ This tells a producer to re-add itself to the main loop and produce
232
+ more data for its consumer.
233
+ """
234
+ self._paused = False
235
+ if self._DEBUG_LOGGING:
236
+ logger.debug(
237
+ "%s: Producer resumed, data len = %s",
238
+ self._remoteVortexName,
239
+ self._queuedDataLen,
240
+ )
241
+ self._startWriting()
242
+
243
+ def stopProducing(self):
244
+ """
245
+ Stop producing data.
246
+
247
+ This tells a producer that its consumer has died, so it must stop
248
+ producing data for good.
249
+ """
250
+ if self._stopProducingCallback:
251
+ self._stopProducingCallback()
252
+ self._stopProducingCallback = None
253
+
254
+ def write(self, data: bytes, priority: int = DEFAULT_PRIORITY):
255
+ assert not self._closed
256
+
257
+ # Since write actually queues data and does not write, we will split
258
+ # the data if required so we are storing smaller memory objects.
259
+ if self._splitFrames and self.WRITE_CHUNK_SIZE < len(data):
260
+ for offset in range(0, len(data), self.WRITE_CHUNK_SIZE):
261
+
262
+ if self._DEBUG_LOGGING:
263
+ logger.debug(
264
+ "%s: Producer write, splitting frames,"
265
+ " split %s to %s of %s",
266
+ self._remoteVortexName,
267
+ offset,
268
+ min(offset + self.WRITE_CHUNK_SIZE, len(data)),
269
+ len(data),
270
+ )
271
+ self.write(data[offset : offset + self.WRITE_CHUNK_SIZE])
272
+ return
273
+
274
+ if self._DEBUG_LOGGING:
275
+ logger.debug(
276
+ "%s: Producer write called, data len = %s",
277
+ self._remoteVortexName,
278
+ len(data),
279
+ )
280
+
281
+ preLen = self._queuedDataLen
282
+ self._queuedDataLen += len(data)
283
+
284
+ # Queue the data up in chunks
285
+ self._queueByPriority[priority].append(data)
286
+
287
+ if preLen < self.WARNING_DATA_LENGTH < self._queuedDataLen:
288
+ logger.warning(
289
+ "%s: Data Queue memory high warning : %s",
290
+ self._remoteVortexName,
291
+ _format_size(self._queuedDataLen),
292
+ )
293
+
294
+ if preLen < self.ERROR_DATA_LENGTH < self._queuedDataLen:
295
+ logger.error(
296
+ "%s: Data Queue memory high error : %s",
297
+ self._remoteVortexName,
298
+ _format_size(self._queuedDataLen),
299
+ )
300
+
301
+ self._startWriting()
302
+
303
+ def writeSequence(
304
+ self, data: typing.Iterable[bytes], priority: int = DEFAULT_PRIORITY
305
+ ) -> None:
306
+
307
+ if self._DEBUG_LOGGING:
308
+ logger.debug(
309
+ "%s: Producer writeSequence called",
310
+ self._remoteVortexName,
311
+ )
312
+
313
+ for seq in data:
314
+ self.write(seq, priority=priority)
315
+
316
+ def close(self):
317
+ self._closed = True
318
+ self._queuedDataLen = 0
319
+ self._queueByPriority = defaultdict(deque)
320
+ self._currentlyWritingData = None
321
+ self._currentlyWritingDataOffset = 0
vortex/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """
2
+ * Created by Synerty Pty Ltd
3
+ *
4
+ * This software is open source, the MIT license applies.
5
+ *
6
+ * Website : http://www.synerty.com
7
+ * Support : support@synerty.com
8
+ """
9
+
10
+ __version__ = '4.0.23'