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.
- syndesi/__init__.py +22 -2
- syndesi/adapters/adapter.py +332 -489
- syndesi/adapters/adapter_worker.py +820 -0
- syndesi/adapters/auto.py +58 -25
- syndesi/adapters/descriptors.py +38 -0
- syndesi/adapters/ip.py +203 -71
- syndesi/adapters/serialport.py +154 -25
- syndesi/adapters/stop_conditions.py +354 -0
- syndesi/adapters/timeout.py +58 -21
- syndesi/adapters/visa.py +236 -11
- syndesi/cli/console.py +51 -16
- syndesi/cli/shell.py +95 -47
- syndesi/cli/terminal_tools.py +8 -8
- syndesi/component.py +315 -0
- syndesi/protocols/delimited.py +92 -107
- syndesi/protocols/modbus.py +2368 -868
- syndesi/protocols/protocol.py +186 -33
- syndesi/protocols/raw.py +45 -62
- syndesi/protocols/scpi.py +65 -102
- syndesi/remote/remote.py +188 -0
- syndesi/scripts/syndesi.py +12 -2
- syndesi/tools/errors.py +49 -31
- syndesi/tools/log_settings.py +21 -8
- syndesi/tools/{log.py → logmanager.py} +24 -13
- syndesi/tools/types.py +9 -7
- syndesi/version.py +5 -1
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/METADATA +1 -1
- syndesi-0.5.0.dist-info/RECORD +41 -0
- syndesi/adapters/backend/__init__.py +0 -0
- syndesi/adapters/backend/adapter_backend.py +0 -438
- syndesi/adapters/backend/adapter_manager.py +0 -48
- syndesi/adapters/backend/adapter_session.py +0 -346
- syndesi/adapters/backend/backend.py +0 -438
- syndesi/adapters/backend/backend_status.py +0 -0
- syndesi/adapters/backend/backend_tools.py +0 -66
- syndesi/adapters/backend/descriptors.py +0 -153
- syndesi/adapters/backend/ip_backend.py +0 -149
- syndesi/adapters/backend/serialport_backend.py +0 -241
- syndesi/adapters/backend/stop_condition_backend.py +0 -219
- syndesi/adapters/backend/timed_queue.py +0 -39
- syndesi/adapters/backend/timeout.py +0 -252
- syndesi/adapters/backend/visa_backend.py +0 -197
- syndesi/adapters/ip_server.py +0 -102
- syndesi/adapters/stop_condition.py +0 -90
- syndesi/cli/backend_console.py +0 -96
- syndesi/cli/backend_status.py +0 -274
- syndesi/cli/backend_wrapper.py +0 -61
- syndesi/scripts/syndesi_backend.py +0 -37
- syndesi/tools/backend_api.py +0 -175
- syndesi/tools/backend_logger.py +0 -64
- syndesi/tools/exceptions.py +0 -16
- syndesi/tools/internal.py +0 -0
- syndesi-0.4.2.dist-info/RECORD +0 -60
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/WHEEL +0 -0
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/entry_points.txt +0 -0
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
# File : stop_condition.py
|
|
2
|
+
# Author : Sébastien Deriaz
|
|
3
|
+
# License : GPL
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Stop-condition module
|
|
7
|
+
|
|
8
|
+
This is the frontend of the stop-conditions, the part that is imported by the user
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# from abc import abstractmethod
|
|
12
|
+
from abc import abstractmethod
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from enum import Enum
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Fragment:
|
|
19
|
+
"""
|
|
20
|
+
Fragment class, holds a piece of data (bytes) and the time at which it was received
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
data: bytes
|
|
24
|
+
timestamp: float
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
return f"{self.data!r}@{self.timestamp}"
|
|
28
|
+
|
|
29
|
+
def __repr__(self) -> str:
|
|
30
|
+
return f"Fragment({self.data!r}@{self.timestamp})"
|
|
31
|
+
|
|
32
|
+
def __getitem__(self, key: slice) -> "Fragment":
|
|
33
|
+
# if self.data is None:
|
|
34
|
+
# raise IndexError('Cannot index invalid fragment')
|
|
35
|
+
return Fragment(self.data[key], self.timestamp)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class StopConditionType(Enum):
|
|
39
|
+
"""
|
|
40
|
+
Stop-condition type
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
TERMINATION = "termination"
|
|
44
|
+
LENGTH = "length"
|
|
45
|
+
CONTINUATION = "continuation"
|
|
46
|
+
TOTAL = "total"
|
|
47
|
+
FRAGMENT = "fragment"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class StopCondition:
|
|
51
|
+
"""
|
|
52
|
+
Stop-condition base class, cannot be used on its own
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
# @abstractmethod
|
|
56
|
+
# def type(self) -> StopConditionType:
|
|
57
|
+
# pass
|
|
58
|
+
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def initiate_read(self, timestamp: float) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Prepare the stop-condition for the next read
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def evaluate(
|
|
67
|
+
self, raw_fragment: Fragment
|
|
68
|
+
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
69
|
+
"""
|
|
70
|
+
Evaluate incoming fragment and return read information for the next fragment
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def type(self) -> StopConditionType:
|
|
75
|
+
"""
|
|
76
|
+
Helper function to determine the which type of stop-condition generated a stop
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
@abstractmethod
|
|
80
|
+
def flush_read(self) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Reset read operation
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class Termination(StopCondition):
|
|
87
|
+
"""
|
|
88
|
+
Termination stop-condition, used to stop when a specified sequence is received
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
sequence : bytes | str
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, sequence: bytes | str) -> None:
|
|
96
|
+
super().__init__()
|
|
97
|
+
if isinstance(sequence, str):
|
|
98
|
+
self._sequence = sequence.encode("utf-8")
|
|
99
|
+
else:
|
|
100
|
+
self._sequence = sequence
|
|
101
|
+
self._sequence_found_length = 0
|
|
102
|
+
|
|
103
|
+
# TYPE = StopConditionType.TERMINATION
|
|
104
|
+
|
|
105
|
+
# def __init__(self, sequence: bytes | str) -> None:
|
|
106
|
+
# """
|
|
107
|
+
# Instanciate a new Termination class
|
|
108
|
+
# """
|
|
109
|
+
# self.sequence: bytes
|
|
110
|
+
# if isinstance(sequence, str):
|
|
111
|
+
# self.sequence = sequence.encode("utf-8")
|
|
112
|
+
# elif isinstance(sequence, bytes):
|
|
113
|
+
# self.sequence = sequence
|
|
114
|
+
# else:
|
|
115
|
+
# raise ValueError(f"Invalid termination sequence type : {type(sequence)}")
|
|
116
|
+
|
|
117
|
+
def __str__(self) -> str:
|
|
118
|
+
return f"Termination({repr(self._sequence)})"
|
|
119
|
+
|
|
120
|
+
def __repr__(self) -> str:
|
|
121
|
+
return self.__str__()
|
|
122
|
+
|
|
123
|
+
def initiate_read(self, timestamp: float) -> None:
|
|
124
|
+
self._sequence_found_length = 0
|
|
125
|
+
|
|
126
|
+
def flush_read(self) -> None:
|
|
127
|
+
self._sequence_found_length = 0
|
|
128
|
+
|
|
129
|
+
def evaluate(
|
|
130
|
+
self, raw_fragment: Fragment
|
|
131
|
+
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
132
|
+
if raw_fragment.data is None:
|
|
133
|
+
raise RuntimeError("Trying to evaluate an invalid fragment")
|
|
134
|
+
|
|
135
|
+
position, length = termination_in_data(
|
|
136
|
+
self._sequence[self._sequence_found_length :], raw_fragment.data
|
|
137
|
+
)
|
|
138
|
+
stop = False
|
|
139
|
+
deferred = Fragment(b"", raw_fragment.timestamp)
|
|
140
|
+
|
|
141
|
+
if position is None:
|
|
142
|
+
# Nothing was found, keep everything
|
|
143
|
+
kept = raw_fragment
|
|
144
|
+
else:
|
|
145
|
+
self._sequence_found_length += length
|
|
146
|
+
|
|
147
|
+
if self._sequence_found_length == len(self._sequence):
|
|
148
|
+
# The sequence was found entirely
|
|
149
|
+
deferred = raw_fragment[position + length :]
|
|
150
|
+
self._sequence_found_length = 0
|
|
151
|
+
stop = True
|
|
152
|
+
elif position + length == len(raw_fragment.data):
|
|
153
|
+
# Part of the sequence was found at the end
|
|
154
|
+
# Return what's before the sequence
|
|
155
|
+
deferred = Fragment(b"", raw_fragment.timestamp)
|
|
156
|
+
|
|
157
|
+
kept = raw_fragment[: position + length]
|
|
158
|
+
|
|
159
|
+
return stop, kept, deferred, None
|
|
160
|
+
|
|
161
|
+
def type(self) -> StopConditionType:
|
|
162
|
+
return StopConditionType.TERMINATION
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class Length(StopCondition):
|
|
166
|
+
"""
|
|
167
|
+
Length stop-condition, used to stop when the specified number of bytes (or more) have been read
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
n : int
|
|
172
|
+
Number of bytes
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
# TYPE = StopConditionType.LENGTH
|
|
176
|
+
def __init__(self, n: int) -> None:
|
|
177
|
+
super().__init__()
|
|
178
|
+
self._n = n
|
|
179
|
+
self._counter = 0
|
|
180
|
+
|
|
181
|
+
def __str__(self) -> str:
|
|
182
|
+
return f"Length({self._n})"
|
|
183
|
+
|
|
184
|
+
def __repr__(self) -> str:
|
|
185
|
+
return self.__str__()
|
|
186
|
+
|
|
187
|
+
def initiate_read(self, timestamp: float) -> None:
|
|
188
|
+
# Length
|
|
189
|
+
self._counter = 0
|
|
190
|
+
|
|
191
|
+
def type(self) -> StopConditionType:
|
|
192
|
+
return StopConditionType.LENGTH
|
|
193
|
+
|
|
194
|
+
def flush_read(self) -> None:
|
|
195
|
+
self._counter = 0
|
|
196
|
+
|
|
197
|
+
def evaluate(
|
|
198
|
+
self, raw_fragment: Fragment
|
|
199
|
+
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
200
|
+
remaining_bytes = self._n - self._counter
|
|
201
|
+
kept_fragment = raw_fragment[:remaining_bytes]
|
|
202
|
+
deferred_fragment = raw_fragment[remaining_bytes:]
|
|
203
|
+
self._counter += len(kept_fragment.data)
|
|
204
|
+
remaining_bytes = self._n - self._counter
|
|
205
|
+
# TODO : remaining_bytes <= 0 ? Alongside above TODO maybe
|
|
206
|
+
return remaining_bytes == 0, kept_fragment, deferred_fragment, None
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class Continuation(StopCondition):
|
|
210
|
+
"""
|
|
211
|
+
Continuation stop-condition, used to stop reading when data has already been received
|
|
212
|
+
and nothing has been received since then for the specified amount of time
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
continuation : float
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
def __init__(self, continuation: float) -> None:
|
|
220
|
+
super().__init__()
|
|
221
|
+
self.continuation = continuation
|
|
222
|
+
self._last_fragment: float | None = None
|
|
223
|
+
|
|
224
|
+
def __str__(self) -> str:
|
|
225
|
+
return f"Continuation({self.continuation})"
|
|
226
|
+
|
|
227
|
+
def __repr__(self) -> str:
|
|
228
|
+
return self.__str__()
|
|
229
|
+
|
|
230
|
+
def initiate_read(self, timestamp: float) -> None:
|
|
231
|
+
self._last_fragment = timestamp
|
|
232
|
+
|
|
233
|
+
def flush_read(self) -> None:
|
|
234
|
+
self._last_fragment = None
|
|
235
|
+
|
|
236
|
+
def evaluate(
|
|
237
|
+
self, raw_fragment: Fragment
|
|
238
|
+
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
239
|
+
deferred = Fragment(b"", raw_fragment.timestamp)
|
|
240
|
+
kept = raw_fragment
|
|
241
|
+
|
|
242
|
+
# if raw_fragment.timestamp is None:
|
|
243
|
+
# raise RuntimeError("Cannot evaluate fragment with no timestamp")
|
|
244
|
+
# last_fragment can be none if no data was ever received
|
|
245
|
+
if self._last_fragment is not None:
|
|
246
|
+
continuation_timestamp = self._last_fragment + self.continuation
|
|
247
|
+
stop = continuation_timestamp <= raw_fragment.timestamp
|
|
248
|
+
next_event_timeout = continuation_timestamp
|
|
249
|
+
else:
|
|
250
|
+
stop = False
|
|
251
|
+
next_event_timeout = None
|
|
252
|
+
|
|
253
|
+
return stop, kept, deferred, next_event_timeout
|
|
254
|
+
|
|
255
|
+
def type(self) -> StopConditionType:
|
|
256
|
+
return StopConditionType.CONTINUATION
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class Total(StopCondition):
|
|
260
|
+
"""
|
|
261
|
+
Total stop-condition, used to stop reading when data has already been received
|
|
262
|
+
and the total read time exceeds the specified amount
|
|
263
|
+
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
def __init__(self, total: float) -> None:
|
|
267
|
+
super().__init__()
|
|
268
|
+
self.total = total
|
|
269
|
+
self._start_time: float | None = None
|
|
270
|
+
|
|
271
|
+
def __str__(self) -> str:
|
|
272
|
+
return f"Total({self.total})"
|
|
273
|
+
|
|
274
|
+
def __repr__(self) -> str:
|
|
275
|
+
return self.__str__()
|
|
276
|
+
|
|
277
|
+
def initiate_read(self, timestamp: float) -> None:
|
|
278
|
+
self._start_time = timestamp
|
|
279
|
+
|
|
280
|
+
def flush_read(self) -> None:
|
|
281
|
+
self._start_time = None
|
|
282
|
+
|
|
283
|
+
def evaluate(
|
|
284
|
+
self, raw_fragment: Fragment
|
|
285
|
+
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
286
|
+
kept = raw_fragment
|
|
287
|
+
deferred = Fragment(b"", raw_fragment.timestamp)
|
|
288
|
+
|
|
289
|
+
# if raw_fragment.timestamp is None:
|
|
290
|
+
# raise RuntimeError("Cannot evaluate fragment with no timestamp")
|
|
291
|
+
|
|
292
|
+
if self._start_time is None:
|
|
293
|
+
raise RuntimeError("Invalid start time")
|
|
294
|
+
total_timestamp = self._start_time + self.total
|
|
295
|
+
stop = total_timestamp <= raw_fragment.timestamp
|
|
296
|
+
|
|
297
|
+
return stop, kept, deferred, total_timestamp
|
|
298
|
+
|
|
299
|
+
def type(self) -> StopConditionType:
|
|
300
|
+
return StopConditionType.TOTAL
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class FragmentStopCondition(StopCondition):
|
|
304
|
+
"""
|
|
305
|
+
Fragment stop-condition, used to stop on each piece of data received by the
|
|
306
|
+
adapter
|
|
307
|
+
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
def __init__(self) -> None: ...
|
|
311
|
+
|
|
312
|
+
def __str__(self) -> str:
|
|
313
|
+
return "FragmentStopCondition()"
|
|
314
|
+
|
|
315
|
+
def __repr__(self) -> str:
|
|
316
|
+
return self.__str__()
|
|
317
|
+
|
|
318
|
+
def initiate_read(self, timestamp: float) -> None:
|
|
319
|
+
pass
|
|
320
|
+
|
|
321
|
+
def flush_read(self) -> None:
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
def evaluate(
|
|
325
|
+
self, raw_fragment: Fragment
|
|
326
|
+
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
327
|
+
|
|
328
|
+
return True, raw_fragment, Fragment(b"", raw_fragment.timestamp), None
|
|
329
|
+
|
|
330
|
+
def type(self) -> StopConditionType:
|
|
331
|
+
return StopConditionType.FRAGMENT
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def termination_in_data(termination: bytes, data: bytes) -> tuple[int | None, int]:
|
|
335
|
+
"""
|
|
336
|
+
Return the position (if it exists) and length of the termination (or part of it) inside data
|
|
337
|
+
"""
|
|
338
|
+
p = None
|
|
339
|
+
length = len(termination)
|
|
340
|
+
# First check if the full termination is somewhere. If that's the case, data will be split
|
|
341
|
+
try:
|
|
342
|
+
p = data.index(termination)
|
|
343
|
+
# If found, return that
|
|
344
|
+
except ValueError:
|
|
345
|
+
# If not, we'll try to find if part of the sequence is at the end, in that case
|
|
346
|
+
# we'll return the length of the sequence that was found
|
|
347
|
+
length -= 1
|
|
348
|
+
while length > 0:
|
|
349
|
+
if data[-length:] == termination[:length]:
|
|
350
|
+
p = len(data) - length # - 1
|
|
351
|
+
break
|
|
352
|
+
length -= 1
|
|
353
|
+
|
|
354
|
+
return p, length
|
syndesi/adapters/timeout.py
CHANGED
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
# File : timeout.py
|
|
2
2
|
# Author : Sébastien Deriaz
|
|
3
3
|
# License : GPL
|
|
4
|
+
"""
|
|
5
|
+
This module holds the Timeout class, this class is meant for the user as a frontend for the
|
|
6
|
+
backend timeout management
|
|
7
|
+
"""
|
|
4
8
|
|
|
5
9
|
from enum import Enum
|
|
6
10
|
from types import EllipsisType
|
|
7
|
-
from typing import Any
|
|
11
|
+
from typing import Any
|
|
8
12
|
|
|
9
13
|
from ..tools.types import NumberLike, is_number
|
|
10
14
|
|
|
15
|
+
|
|
11
16
|
class TimeoutAction(Enum):
|
|
17
|
+
"""
|
|
18
|
+
Action on timeout expiration
|
|
19
|
+
"""
|
|
20
|
+
|
|
12
21
|
ERROR = "error"
|
|
13
22
|
RETURN_EMPTY = "return_empty"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class IsInitialized(Protocol):
|
|
17
|
-
response: NumberLike | None
|
|
23
|
+
RETURN_NONE = "return_none"
|
|
18
24
|
|
|
19
25
|
|
|
20
26
|
class Timeout:
|
|
27
|
+
"""
|
|
28
|
+
This class holds timeout information
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
response : float
|
|
33
|
+
Time before the device responds
|
|
34
|
+
action : str
|
|
35
|
+
Action performed when a timeout occurs
|
|
36
|
+
* ``error`` : raise a AdapterTimeoutError
|
|
37
|
+
* ``return_empty`` : return b''
|
|
38
|
+
* ``return_none`` : return None
|
|
39
|
+
"""
|
|
40
|
+
|
|
21
41
|
DEFAULT_ACTION = TimeoutAction.ERROR
|
|
22
42
|
|
|
23
43
|
def __init__(
|
|
@@ -25,16 +45,7 @@ class Timeout:
|
|
|
25
45
|
response: NumberLike | None | EllipsisType = ...,
|
|
26
46
|
action: str | EllipsisType | TimeoutAction = ...,
|
|
27
47
|
) -> None:
|
|
28
|
-
"""
|
|
29
|
-
This class holds timeout information
|
|
30
48
|
|
|
31
|
-
Parameters
|
|
32
|
-
----------
|
|
33
|
-
response : float
|
|
34
|
-
Time before the device responds
|
|
35
|
-
action : str
|
|
36
|
-
Action performed when a timeout occurs. 'error' -> raise an error, 'return' -> return b''
|
|
37
|
-
"""
|
|
38
49
|
super().__init__()
|
|
39
50
|
|
|
40
51
|
self._is_default_response = response is ...
|
|
@@ -63,29 +74,55 @@ class Timeout:
|
|
|
63
74
|
return self.__str__()
|
|
64
75
|
|
|
65
76
|
def set_default(self, default_timeout: "Timeout") -> None:
|
|
77
|
+
"""
|
|
78
|
+
Set the default timeout (no effect if timeout is already set)
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
default_timeout : Timeout
|
|
83
|
+
"""
|
|
66
84
|
if self._is_default_response:
|
|
67
85
|
self._response = default_timeout.response()
|
|
68
86
|
if self._is_default_action:
|
|
69
87
|
self.action = default_timeout.action
|
|
70
88
|
|
|
71
89
|
def response(self) -> NumberLike | None:
|
|
90
|
+
"""
|
|
91
|
+
Return timeout response if it has been configured
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
response : NumberLike | None
|
|
96
|
+
"""
|
|
72
97
|
if self._response is ...:
|
|
73
98
|
return None
|
|
74
|
-
|
|
99
|
+
if self._response is None:
|
|
75
100
|
return None
|
|
76
|
-
|
|
77
|
-
return self._response
|
|
101
|
+
return self._response
|
|
78
102
|
|
|
79
103
|
def is_initialized(self) -> bool:
|
|
104
|
+
"""
|
|
105
|
+
Return True if the Timeout has been initialized, False otherwise
|
|
106
|
+
"""
|
|
80
107
|
return self._response is not Ellipsis
|
|
81
108
|
|
|
82
109
|
|
|
83
110
|
def any_to_timeout(value: Any) -> Timeout:
|
|
111
|
+
"""
|
|
112
|
+
Convert any input to a timeout (if possible)
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
value : None | NumberLike | Timeout
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
timeout : Timeout
|
|
121
|
+
"""
|
|
84
122
|
if value is None:
|
|
85
123
|
return Timeout(response=None)
|
|
86
|
-
|
|
124
|
+
if is_number(value):
|
|
87
125
|
return Timeout(response=float(value))
|
|
88
|
-
|
|
126
|
+
if isinstance(value, Timeout):
|
|
89
127
|
return value
|
|
90
|
-
|
|
91
|
-
raise ValueError(f"Could not convert {value} to Timeout")
|
|
128
|
+
raise ValueError(f"Could not convert {value} to Timeout")
|