syndesi 0.4.2__py3-none-any.whl → 0.5.0__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 (57) hide show
  1. syndesi/__init__.py +22 -2
  2. syndesi/adapters/adapter.py +332 -489
  3. syndesi/adapters/adapter_worker.py +820 -0
  4. syndesi/adapters/auto.py +58 -25
  5. syndesi/adapters/descriptors.py +38 -0
  6. syndesi/adapters/ip.py +203 -71
  7. syndesi/adapters/serialport.py +154 -25
  8. syndesi/adapters/stop_conditions.py +354 -0
  9. syndesi/adapters/timeout.py +58 -21
  10. syndesi/adapters/visa.py +236 -11
  11. syndesi/cli/console.py +51 -16
  12. syndesi/cli/shell.py +95 -47
  13. syndesi/cli/terminal_tools.py +8 -8
  14. syndesi/component.py +315 -0
  15. syndesi/protocols/delimited.py +92 -107
  16. syndesi/protocols/modbus.py +2368 -868
  17. syndesi/protocols/protocol.py +186 -33
  18. syndesi/protocols/raw.py +45 -62
  19. syndesi/protocols/scpi.py +65 -102
  20. syndesi/remote/remote.py +188 -0
  21. syndesi/scripts/syndesi.py +12 -2
  22. syndesi/tools/errors.py +49 -31
  23. syndesi/tools/log_settings.py +21 -8
  24. syndesi/tools/{log.py → logmanager.py} +24 -13
  25. syndesi/tools/types.py +9 -7
  26. syndesi/version.py +5 -1
  27. {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/METADATA +1 -1
  28. syndesi-0.5.0.dist-info/RECORD +41 -0
  29. syndesi/adapters/backend/__init__.py +0 -0
  30. syndesi/adapters/backend/adapter_backend.py +0 -438
  31. syndesi/adapters/backend/adapter_manager.py +0 -48
  32. syndesi/adapters/backend/adapter_session.py +0 -346
  33. syndesi/adapters/backend/backend.py +0 -438
  34. syndesi/adapters/backend/backend_status.py +0 -0
  35. syndesi/adapters/backend/backend_tools.py +0 -66
  36. syndesi/adapters/backend/descriptors.py +0 -153
  37. syndesi/adapters/backend/ip_backend.py +0 -149
  38. syndesi/adapters/backend/serialport_backend.py +0 -241
  39. syndesi/adapters/backend/stop_condition_backend.py +0 -219
  40. syndesi/adapters/backend/timed_queue.py +0 -39
  41. syndesi/adapters/backend/timeout.py +0 -252
  42. syndesi/adapters/backend/visa_backend.py +0 -197
  43. syndesi/adapters/ip_server.py +0 -102
  44. syndesi/adapters/stop_condition.py +0 -90
  45. syndesi/cli/backend_console.py +0 -96
  46. syndesi/cli/backend_status.py +0 -274
  47. syndesi/cli/backend_wrapper.py +0 -61
  48. syndesi/scripts/syndesi_backend.py +0 -37
  49. syndesi/tools/backend_api.py +0 -175
  50. syndesi/tools/backend_logger.py +0 -64
  51. syndesi/tools/exceptions.py +0 -16
  52. syndesi/tools/internal.py +0 -0
  53. syndesi-0.4.2.dist-info/RECORD +0 -60
  54. {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/WHEEL +0 -0
  55. {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/entry_points.txt +0 -0
  56. {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/licenses/LICENSE +0 -0
  57. {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/top_level.txt +0 -0
@@ -1,149 +0,0 @@
1
- # File : ip.py
2
- # Author : Sébastien Deriaz
3
- # License : GPL
4
- #
5
- # The IP backend communicates with TCP or UDP capable hosts using the IP layer
6
-
7
- import socket
8
- import time
9
- from typing import cast
10
-
11
- import _socket
12
-
13
- from ...tools.backend_api import AdapterBackendStatus, Fragment
14
- from .adapter_backend import AdapterBackend, HasFileno
15
- from .descriptors import IPDescriptor
16
-
17
-
18
- class IPBackend(AdapterBackend):
19
- BUFFER_SIZE = 65507
20
- # _DEFAULT_BUFFER_SIZE = 1024
21
- # DEFAULT_TIMEOUT = TimeoutBackend(
22
- # response=5,
23
- # action="error"
24
- # )
25
- DEFAULT_STOP_CONDITION = None
26
-
27
- def __init__(self, descriptor: IPDescriptor):
28
- """
29
- IP stack adapter
30
-
31
- Parameters
32
- ----------
33
- address : str
34
- IP description
35
- port : int
36
- IP port
37
- transport : str
38
- 'TCP' or 'UDP'
39
- timeout : Timeout | float
40
- Specify communication timeout
41
- stop_condition : StopCondition
42
- Specify a read stop condition (None by default)
43
- buffer_size : int
44
- Socket buffer size, may be removed in the future
45
- socket : socket.socket
46
- Specify a custom socket, this is reserved for server application
47
- """
48
- super().__init__(descriptor=descriptor)
49
- self.descriptor: IPDescriptor
50
-
51
- self._logger.info(f"Setting up {self.descriptor} adapter ")
52
-
53
- self._socket: _socket.socket | None = None
54
-
55
- def selectable(self) -> HasFileno | None:
56
- return self._socket
57
-
58
- def open(self) -> bool:
59
- output = False
60
- if self._status == AdapterBackendStatus.DISCONNECTED:
61
- if self.descriptor.port is None: # TODO : Check if this is even possible
62
- raise ValueError("Cannot open adapter without specifying a port")
63
-
64
- if self._socket is None:
65
- if self.descriptor.transport == IPDescriptor.Transport.TCP:
66
- self._socket = cast(
67
- _socket.socket,
68
- socket.socket(socket.AF_INET, socket.SOCK_STREAM),
69
- )
70
- elif self.descriptor.transport == IPDescriptor.Transport.UDP:
71
- self._socket = cast(
72
- _socket.socket, socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
73
- )
74
- else:
75
- raise ValueError(
76
- f"Invalid transport protocol : {self.descriptor.transport}"
77
- )
78
- try:
79
- self._socket.settimeout(
80
- 0.5
81
- ) # TODO : Configure this cleanly, it has to be less than the receive timeout of the frontend
82
- self._socket.connect((self.descriptor.address, self.descriptor.port))
83
- except OSError as e: # TODO : Maybe change the exception ?
84
- self._logger.error(f"Failed to open adapter {self.descriptor} : {e}")
85
- else:
86
- self._status = AdapterBackendStatus.CONNECTED
87
- self._logger.info(f"IP Adapter {self.descriptor} opened")
88
- output = True
89
- else:
90
- self._logger.info(f"Adapter {self.descriptor} already openend")
91
- output = True
92
-
93
- return output
94
-
95
- def close(self) -> bool:
96
- super().close()
97
-
98
- self._status = AdapterBackendStatus.DISCONNECTED
99
- if self._socket is not None:
100
- self._socket.close()
101
- self._socket = None
102
- self._logger.info(f"Adapter {self.descriptor} closed")
103
- return True
104
- else:
105
- self._logger.error(f"Failed to close adapter {self.descriptor}")
106
- return False
107
-
108
- def write(self, data: bytes) -> bool:
109
- super().write(data)
110
-
111
- # TODO : Manage adapter reopen
112
- # if self._status == AdapterBackendStatus.DISCONNECTED:
113
- # self._logger.info("Adapter is closed, opening...")
114
- # self.open()
115
-
116
- # Socket send
117
- if self._socket is None:
118
- self._logger.error(f"Cannot write to closed adapter {self.descriptor}")
119
- return False
120
- try:
121
- self._socket.send(data)
122
- except (BrokenPipeError, OSError) as e:
123
- # Socket has been disconnected by the remote peer
124
- self._logger.error(f"Failed to write to adapter {self.descriptor} ({e})")
125
- self.close()
126
- return False
127
- else:
128
- return True
129
-
130
- def _socket_read(self) -> Fragment:
131
- # This function is called only if the socket was ready
132
- t = time.time()
133
- if self._socket is None:
134
- return Fragment(b"", t)
135
- else:
136
- try:
137
- fragment = Fragment(self._socket.recv(self.BUFFER_SIZE), t)
138
- except (ConnectionRefusedError, OSError):
139
- fragment = Fragment(b"", t)
140
-
141
- if fragment.data == b"":
142
- # Socket disconnected
143
- self._logger.debug("## Socket disconnected")
144
- self.close()
145
-
146
- return fragment
147
-
148
- def is_opened(self) -> bool:
149
- return self._status == AdapterBackendStatus.CONNECTED
@@ -1,241 +0,0 @@
1
- # File : serialport.py
2
- # Author : Sébastien Deriaz
3
- # License : GPL
4
- #
5
- # The SerialPort backend communicates with serial devices using the serial package
6
-
7
- import time
8
-
9
- import serial
10
- from serial.serialutil import PortNotOpenError
11
-
12
- from syndesi.tools.backend_api import AdapterBackendStatus, Fragment
13
-
14
- from .adapter_backend import AdapterBackend, HasFileno
15
- from .descriptors import SerialPortDescriptor
16
-
17
-
18
- class SerialPortBackend(AdapterBackend):
19
- def __init__(self, descriptor: SerialPortDescriptor):
20
- """
21
- Serial communication adapter
22
-
23
- Parameters
24
- ----------
25
- port : str
26
- Serial port (COMx or ttyACMx)
27
- """
28
- super().__init__(descriptor=descriptor)
29
- self.descriptor: SerialPortDescriptor
30
- self._logger.info(f"Setting up SerialPort adapter {self.descriptor}")
31
- self._port: serial.Serial | None = None
32
- self._rts_cts = False
33
-
34
- self.open()
35
-
36
- def set_baudrate(self, baudrate: int) -> None:
37
- """
38
- Set baudrate
39
-
40
- Parameters
41
- ----------
42
- baudrate : int
43
- """
44
- self.descriptor.baudrate = baudrate
45
-
46
- if self.is_opened():
47
- self.close()
48
- self.open()
49
-
50
- # TODO : Check if this is still necessary, it's probably more of a frontend thing
51
- def set_default_baudrate(self, baudrate: int) -> None:
52
- """
53
- Sets the default baudrate
54
-
55
- Parameters
56
- ----------
57
- baudrate : int
58
- """
59
- self.descriptor.set_default_baudrate(baudrate)
60
-
61
- def flush_read(self) -> bool:
62
- super().flush_read()
63
- if self._port is None:
64
- return False
65
- else:
66
- self._port.flush()
67
- return True
68
-
69
- def set_rtscts(self, enabled: bool) -> None:
70
- self._rts_cts = enabled
71
- if self.is_opened():
72
- self.close()
73
- self.open()
74
-
75
- def open(self) -> bool:
76
- if self.descriptor.baudrate is None:
77
- raise ValueError("Baudrate must be set, please use set_baudrate")
78
-
79
- if self._port is None:
80
- self._port = serial.Serial(
81
- port=self.descriptor.port,
82
- baudrate=self.descriptor.baudrate,
83
- rtscts=self._rts_cts,
84
- )
85
- elif not self._port.isOpen(): # type: ignore
86
- self._port.open()
87
-
88
- if self._port.isOpen(): # type: ignore
89
- self._logger.info(f"Adapter {self.descriptor} opened")
90
- self._status = AdapterBackendStatus.CONNECTED
91
- return True
92
- else:
93
- self._logger.error(f"Failed to open adapter {self.descriptor}")
94
- self._status = AdapterBackendStatus.DISCONNECTED
95
- return False
96
-
97
- def close(self) -> bool:
98
- super().close()
99
- if hasattr(self, "_port") and self._port is not None:
100
- # Close and the read thread will die by itself
101
- self._port.close()
102
- self._logger.info("Adapter closed")
103
- return True
104
- else:
105
- return False
106
-
107
- def write(self, data: bytes) -> bool:
108
- super().write(data)
109
- if self._port is None:
110
- self._logger.error(f"Cannot write to closed adapter {self.descriptor}")
111
- return False
112
- else:
113
- if self._rts_cts: # Experimental
114
- self._port.setRTS(True) # type: ignore
115
- # TODO : Implement auto open
116
- # if self._status == AdapterBackendStatus.DISCONNECTED:
117
- # self.open()
118
- # write_start = time.time()
119
- try:
120
- self._port.write(data)
121
- except (OSError, PortNotOpenError):
122
- return False
123
- # write_duration = time.time() - write_start
124
- # self._logger.debug(f"Write [{write_duration * 1e3:.3f}ms]: {repr(data)}")
125
-
126
- return True
127
-
128
- def _socket_read(self) -> Fragment:
129
- t = time.time()
130
-
131
- if self._port is None:
132
- self._logger.debug('Port is None -> b""')
133
- return Fragment(b"", t)
134
- else:
135
- try:
136
- data = self._port.read_all()
137
- except (OSError, PortNotOpenError):
138
- self._logger.debug('Port error -> b""')
139
- return Fragment(b"", t)
140
- else:
141
- # This is a test, it seems b"" happens sometimes and disconnects the adapter
142
- if data is not None and data != b"":
143
- return Fragment(data, t)
144
- else:
145
- return Fragment(b"", t)
146
- # self._logger.debug('Data is none -> b""')
147
- # else:
148
- # self._logger.debug(f'{data=}')
149
-
150
- # def _start_thread(self):
151
- # """
152
- # Start the read thread
153
- # """
154
- # #super()._start_thread()
155
- # if self._thread is None or not self._thread.is_alive():
156
- # self._thread = Thread(
157
- # target=self._read_thread,
158
- # daemon=True,
159
- # args=(self._port, self._read_queue, self._thread_commands_read),
160
- # )
161
- # self._thread.start()
162
-
163
- # def _read_thread(
164
- # self,
165
- # port: serial.Serial,
166
- # read_queue: TimedQueue,
167
- # thread_commands: socket.socket,
168
- # ):
169
- # # On linux, it is possivle to use the select.select for both serial port and stop socketpair.
170
- # # On Windows, this is not possible. so the port timeout is used instead.
171
- # if sys.platform == "win32":
172
- # port.timeout = 0.1
173
- # while True:
174
- # # Check how many bytes are available
175
- # if sys.platform == "win32":
176
- # ready, _, _ = select.select([thread_commands], [], [], 0)
177
- # if thread_commands in ready:
178
- # # Stop the read thread
179
- # break
180
- # else:
181
- # # Read data from the serialport with a timeout, if the timeout occurs, read again.
182
- # # This is to avoid having a crazy fast loop
183
- # fragment = port.read()
184
- # if len(fragment) > 0:
185
- # read_queue.put(fragment)
186
- # else:
187
- # ready, _, _ = select.select([self._port.fd, thread_commands], [], [])
188
- # if thread_commands in ready:
189
- # command = self.ThreadCommands(thread_commands.recv(1))
190
- # if command == self.ThreadCommands.STOP:
191
- # # Stop the read thread
192
- # break
193
- # elif self._port.fd in ready:
194
- # try:
195
- # in_waiting = self._port.in_waiting
196
- # except OSError:
197
- # # Input/output error, the port was disconnected
198
- # read_queue.put(AdapterDisconnected)
199
- # else:
200
- # fragment = port.read(in_waiting)
201
- # if fragment:
202
- # read_queue.put(fragment)
203
-
204
- # def read(
205
- # self, timeout=..., stop_condition=..., full_output: bool = False
206
- # ) -> bytes:
207
- # """
208
- # Read data from the device
209
-
210
- # Parameters
211
- # ----------
212
- # timeout : Timeout or None
213
- # Set a custom timeout, if None (default), the adapter timeout is used
214
- # stop_condition : StopCondition or None
215
- # Set a custom stop condition, if None (Default), the adapater stop condition is used
216
- # full_output : bool
217
- # Return a dictionary containing information about the read operation like
218
- # 'read_duration' : float
219
- # 'origin' : 'timeout' or 'stop_condition'
220
- # 'timeout' : Timeout.TimeoutType
221
- # 'stop_condition' : Length or Termination (StopCondition class)
222
- # 'previous_read_buffer_used' : bool
223
- # 'n_fragments' : int
224
- # """
225
- # output = super().read(timeout, stop_condition, full_output)
226
- # if self._rts_cts: # Experimental
227
- # self._port.setRTS(False)
228
- # return output
229
-
230
- def is_opened(self) -> bool:
231
- if self._port is not None:
232
- if self._port.isOpen(): # type: ignore
233
- return True
234
-
235
- return False
236
-
237
- def selectable(self) -> HasFileno | None:
238
- if self._port is not None and self.is_opened():
239
- return self._port
240
- else:
241
- return None
@@ -1,219 +0,0 @@
1
- # File : stop_condition.py
2
- # Author : Sébastien Deriaz
3
- # License : GPL
4
- #
5
- # A stop-condition describes when a communication with a device should
6
- # be stopped based on the data received (length, contents, termination, etc...)
7
- # A stop-condition can also format the data if necessary (remove termination for example)
8
-
9
- import time
10
-
11
- from syndesi.adapters.stop_condition import (
12
- Continuation,
13
- Length,
14
- StopCondition,
15
- StopConditionType,
16
- Termination,
17
- Total,
18
- )
19
-
20
- from ...tools.backend_api import Fragment
21
-
22
-
23
- def termination_in_data(termination: bytes, data: bytes) -> tuple[int | None, int]:
24
- """
25
- Return the position (if it exists) and length of the termination (or part of it) inside data
26
- """
27
- p = None
28
- L = len(termination)
29
- # First check if the full termination is somewhere. If that's the case, data will be split
30
- try:
31
- p = data.index(termination)
32
- # If found, return that
33
- except ValueError:
34
- # If not, we'll try to find if part of the sequence is at the end, in that case
35
- # we'll return the length of the sequence that was found
36
- L -= 1
37
- while L > 0:
38
- if data[-L:] == termination[:L]:
39
- p = len(data) - L # - 1
40
- break
41
- L -= 1
42
-
43
- return p, L
44
-
45
-
46
- class StopConditionBackend:
47
- def __init__(self) -> None:
48
- pass
49
-
50
- def initiate_read(self) -> None:
51
- raise NotImplementedError()
52
-
53
- def evaluate(
54
- self, raw_fragment: Fragment
55
- ) -> tuple[bool, Fragment, Fragment, float | None]:
56
- raise NotImplementedError()
57
-
58
- def type(self) -> StopConditionType:
59
- raise NotImplementedError()
60
-
61
- def flush_read(self) -> None:
62
- raise NotImplementedError()
63
-
64
-
65
- class TerminationBackend(StopConditionBackend):
66
- def __init__(self, sequence: bytes) -> None:
67
- super().__init__()
68
- self._sequence = sequence
69
- self._sequence_found_length = 0
70
-
71
- def initiate_read(self) -> None:
72
- # Termination
73
- self._sequence_found_length = 0
74
-
75
- def flush_read(self) -> None:
76
- self._sequence_found_length = 0
77
-
78
- def evaluate(
79
- self, raw_fragment: Fragment
80
- ) -> tuple[bool, Fragment, Fragment, float | None]:
81
- if raw_fragment.data is None:
82
- raise RuntimeError("Trying to evaluate an invalid fragment")
83
-
84
- position, length = termination_in_data(
85
- self._sequence[self._sequence_found_length :], raw_fragment.data
86
- )
87
- stop = False
88
- deferred = Fragment(b"", None)
89
-
90
- if position is None:
91
- # Nothing was found, keep everything
92
- kept = raw_fragment
93
- else:
94
- self._sequence_found_length += length
95
-
96
- if self._sequence_found_length == len(self._sequence):
97
- # The sequence was found entirely
98
- deferred = raw_fragment[position + length :]
99
- self._sequence_found_length = 0
100
- stop = True
101
- elif position + length == len(raw_fragment.data):
102
- # Part of the sequence was found at the end
103
- # Return what's before the sequence
104
- deferred = Fragment(b"", None)
105
-
106
- kept = raw_fragment[:position]
107
-
108
- return stop, kept, deferred, None
109
-
110
- def type(self) -> StopConditionType:
111
- return StopConditionType.TERMINATION
112
-
113
-
114
- class LengthBackend(StopConditionBackend):
115
- def __init__(self, N: int) -> None:
116
- super().__init__()
117
- self._N = N
118
- self._counter = 0
119
-
120
- def initiate_read(self) -> None:
121
- # Length
122
- self._counter = 0
123
-
124
- def flush_read(self) -> None:
125
- self._counter = 0
126
-
127
- def evaluate(
128
- self, raw_fragment: Fragment
129
- ) -> tuple[bool, Fragment, Fragment, float | None]:
130
- remaining_bytes = self._N - self._counter
131
- kept_fragment = raw_fragment[:remaining_bytes]
132
- deferred_fragment = raw_fragment[remaining_bytes:]
133
- self._counter += len(kept_fragment.data)
134
- remaining_bytes = self._N - self._counter
135
- # TODO : remaining_bytes <= 0 ? Alongside above TODO maybe
136
- return remaining_bytes == 0, kept_fragment, deferred_fragment, None
137
-
138
- def type(self) -> StopConditionType:
139
- return StopConditionType.LENGTH
140
-
141
-
142
- class ContinuationBackend(StopConditionBackend):
143
- def __init__(self, time: float) -> None:
144
- super().__init__()
145
- self._continuation = time
146
- self._last_fragment: float | None = None
147
-
148
- def initiate_read(self) -> None:
149
- self._last_fragment = time.time()
150
-
151
- def flush_read(self) -> None:
152
- self._last_fragment = None
153
-
154
- def evaluate(
155
- self, raw_fragment: Fragment
156
- ) -> tuple[bool, Fragment, Fragment, float | None]:
157
- deferred = Fragment(b"", None)
158
- kept = raw_fragment
159
-
160
- if raw_fragment.timestamp is None:
161
- raise RuntimeError("Cannot evaluate fragment with no timestamp")
162
- # last_fragment can be none if no data was ever received
163
- if self._last_fragment is not None:
164
- continuation_timestamp = self._last_fragment + self._continuation
165
- stop = continuation_timestamp <= raw_fragment.timestamp
166
- next_event_timeout = continuation_timestamp
167
- else:
168
- stop = False
169
- next_event_timeout = None
170
-
171
- return stop, kept, deferred, next_event_timeout
172
-
173
- def type(self) -> StopConditionType:
174
- return StopConditionType.TIMEOUT
175
-
176
-
177
- class TotalBackend(StopConditionBackend):
178
- def __init__(self, time: float) -> None:
179
- super().__init__()
180
- self._total = time
181
- self._start_time: float | None = None
182
-
183
- def initiate_read(self) -> None:
184
- self._start_time = time.time()
185
-
186
- def flush_read(self) -> None:
187
- self._start_time = None
188
-
189
- def evaluate(
190
- self, raw_fragment: Fragment
191
- ) -> tuple[bool, Fragment, Fragment, float | None]:
192
- kept = raw_fragment
193
- deferred = Fragment(b"", None)
194
-
195
- if raw_fragment.timestamp is None:
196
- raise RuntimeError("Cannot evaluate fragment with no timestamp")
197
-
198
- if self._start_time is None:
199
- raise RuntimeError("Invalid start time")
200
- total_timestamp = self._start_time + self._total
201
- stop = total_timestamp <= raw_fragment.timestamp
202
-
203
- return stop, kept, deferred, total_timestamp
204
-
205
- def type(self) -> StopConditionType:
206
- return StopConditionType.TIMEOUT
207
-
208
-
209
- def stop_condition_to_backend(stop_condition: StopCondition) -> StopConditionBackend:
210
- if isinstance(stop_condition, Termination):
211
- return TerminationBackend(stop_condition.sequence)
212
- elif isinstance(stop_condition, Length):
213
- return LengthBackend(stop_condition.N)
214
- elif isinstance(stop_condition, Continuation):
215
- return ContinuationBackend(stop_condition.continuation)
216
- elif isinstance(stop_condition, Total):
217
- return TotalBackend(stop_condition.total)
218
- else:
219
- raise RuntimeError(f"Invalid stop condition : {stop_condition}")
@@ -1,39 +0,0 @@
1
- # File : timed_queue.py
2
- # Author : Sébastien Deriaz
3
- # License : GPL
4
- #
5
- # The timed queue is a special queue that stores time information when elements are stored inside
6
- # This is used to manage timing when reading data from devices
7
-
8
- # import queue
9
- # from time import time
10
-
11
-
12
- # class TimedQueue:
13
- # def __init__(self) -> None:
14
- # self._queue = queue.Queue()
15
-
16
- # def put(self, fragment: bytes) -> None:
17
- # self._queue.put((time(), fragment))
18
-
19
- # def get(self, timeout: float | None) -> tuple[float, bytes]:
20
- # """
21
- # Return an element of the timed queue. Waits at most the amount of time specified by timeout
22
-
23
- # Parameters
24
- # ----------
25
- # timeout : float, None
26
- # """
27
- # try:
28
- # return self._queue.get(block=True, timeout=timeout)
29
- # except queue.Empty: # No item after timeout
30
- # return None, None
31
-
32
- # def is_empty(self):
33
- # return self._queue.empty()
34
-
35
- # def clear(self):
36
- # with self._queue.mutex:
37
- # self._queue.queue.clear()
38
- # self._queue.all_tasks_done.notify_all()
39
- # self._queue.unfinished_tasks = 0