syndesi 0.4.4__tar.gz → 0.5.1__tar.gz

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 (85) hide show
  1. {syndesi-0.4.4/syndesi.egg-info → syndesi-0.5.1}/PKG-INFO +3 -3
  2. {syndesi-0.4.4 → syndesi-0.5.1}/pyproject.toml +7 -5
  3. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/__init__.py +6 -1
  4. syndesi-0.5.1/syndesi/adapters/adapter.py +478 -0
  5. syndesi-0.5.1/syndesi/adapters/adapter_worker.py +838 -0
  6. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/adapters/auto.py +32 -10
  7. syndesi-0.5.1/syndesi/adapters/descriptors.py +38 -0
  8. syndesi-0.5.1/syndesi/adapters/ip.py +236 -0
  9. syndesi-0.5.1/syndesi/adapters/serialport.py +267 -0
  10. syndesi-0.5.1/syndesi/adapters/stop_conditions.py +347 -0
  11. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/adapters/timeout.py +57 -23
  12. syndesi-0.5.1/syndesi/adapters/tracehub.py +229 -0
  13. syndesi-0.5.1/syndesi/adapters/visa.py +290 -0
  14. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/cli/console.py +50 -15
  15. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/cli/shell.py +97 -48
  16. syndesi-0.5.1/syndesi/cli/terminal_tools.py +14 -0
  17. syndesi-0.5.1/syndesi/component.py +315 -0
  18. syndesi-0.5.1/syndesi/protocols/delimited.py +156 -0
  19. syndesi-0.5.1/syndesi/protocols/modbus.py +3098 -0
  20. syndesi-0.5.1/syndesi/protocols/protocol.py +247 -0
  21. syndesi-0.5.1/syndesi/protocols/raw.py +78 -0
  22. syndesi-0.5.1/syndesi/protocols/scpi.py +107 -0
  23. syndesi-0.5.1/syndesi/remote/remote.py +188 -0
  24. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/scripts/syndesi.py +11 -1
  25. syndesi-0.5.1/syndesi/scripts/syndesi_trace.py +711 -0
  26. syndesi-0.5.1/syndesi/tools/errors.py +68 -0
  27. syndesi-0.5.1/syndesi/tools/log_settings.py +30 -0
  28. syndesi-0.4.4/syndesi/tools/log.py → syndesi-0.5.1/syndesi/tools/logmanager.py +23 -13
  29. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/tools/types.py +8 -7
  30. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/version.py +1 -1
  31. {syndesi-0.4.4 → syndesi-0.5.1/syndesi.egg-info}/PKG-INFO +3 -3
  32. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi.egg-info/SOURCES.txt +7 -25
  33. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi.egg-info/entry_points.txt +1 -1
  34. syndesi-0.4.4/syndesi/adapters/adapter.py +0 -702
  35. syndesi-0.4.4/syndesi/adapters/backend/adapter_backend.py +0 -438
  36. syndesi-0.4.4/syndesi/adapters/backend/adapter_manager.py +0 -48
  37. syndesi-0.4.4/syndesi/adapters/backend/adapter_session.py +0 -345
  38. syndesi-0.4.4/syndesi/adapters/backend/backend.py +0 -443
  39. syndesi-0.4.4/syndesi/adapters/backend/backend_status.py +0 -0
  40. syndesi-0.4.4/syndesi/adapters/backend/backend_tools.py +0 -66
  41. syndesi-0.4.4/syndesi/adapters/backend/descriptors.py +0 -153
  42. syndesi-0.4.4/syndesi/adapters/backend/ip_backend.py +0 -152
  43. syndesi-0.4.4/syndesi/adapters/backend/serialport_backend.py +0 -246
  44. syndesi-0.4.4/syndesi/adapters/backend/stop_condition_backend.py +0 -222
  45. syndesi-0.4.4/syndesi/adapters/backend/timed_queue.py +0 -39
  46. syndesi-0.4.4/syndesi/adapters/backend/timeout.py +0 -252
  47. syndesi-0.4.4/syndesi/adapters/backend/visa_backend.py +0 -197
  48. syndesi-0.4.4/syndesi/adapters/ip.py +0 -116
  49. syndesi-0.4.4/syndesi/adapters/ip_server.py +0 -108
  50. syndesi-0.4.4/syndesi/adapters/serialport.py +0 -93
  51. syndesi-0.4.4/syndesi/adapters/stop_condition.py +0 -114
  52. syndesi-0.4.4/syndesi/adapters/visa.py +0 -52
  53. syndesi-0.4.4/syndesi/cli/backend_console.py +0 -96
  54. syndesi-0.4.4/syndesi/cli/backend_status.py +0 -274
  55. syndesi-0.4.4/syndesi/cli/backend_wrapper.py +0 -61
  56. syndesi-0.4.4/syndesi/cli/terminal_tools.py +0 -14
  57. syndesi-0.4.4/syndesi/component.py +0 -79
  58. syndesi-0.4.4/syndesi/protocols/delimited.py +0 -183
  59. syndesi-0.4.4/syndesi/protocols/modbus.py +0 -1602
  60. syndesi-0.4.4/syndesi/protocols/protocol.py +0 -86
  61. syndesi-0.4.4/syndesi/protocols/raw.py +0 -90
  62. syndesi-0.4.4/syndesi/protocols/scpi.py +0 -144
  63. syndesi-0.4.4/syndesi/scripts/syndesi_backend.py +0 -37
  64. syndesi-0.4.4/syndesi/tools/__init__.py +0 -0
  65. syndesi-0.4.4/syndesi/tools/backend_api.py +0 -182
  66. syndesi-0.4.4/syndesi/tools/backend_logger.py +0 -64
  67. syndesi-0.4.4/syndesi/tools/errors.py +0 -70
  68. syndesi-0.4.4/syndesi/tools/exceptions.py +0 -16
  69. syndesi-0.4.4/syndesi/tools/internal.py +0 -0
  70. syndesi-0.4.4/syndesi/tools/log_settings.py +0 -17
  71. {syndesi-0.4.4 → syndesi-0.5.1}/LICENSE +0 -0
  72. {syndesi-0.4.4 → syndesi-0.5.1}/README.md +0 -0
  73. {syndesi-0.4.4 → syndesi-0.5.1}/setup.cfg +0 -0
  74. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/__main__.py +0 -0
  75. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/adapters/__init__.py +0 -0
  76. {syndesi-0.4.4/syndesi/adapters/backend → syndesi-0.5.1/syndesi/cli}/__init__.py +0 -0
  77. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/cli/shell_tools.py +0 -0
  78. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/cli/terminal.py +0 -0
  79. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi/cli/terminal_apps.py +0 -0
  80. {syndesi-0.4.4/syndesi/cli → syndesi-0.5.1/syndesi/protocols}/__init__.py +0 -0
  81. {syndesi-0.4.4/syndesi/protocols → syndesi-0.5.1/syndesi/scripts}/__init__.py +0 -0
  82. {syndesi-0.4.4/syndesi/scripts → syndesi-0.5.1/syndesi/tools}/__init__.py +0 -0
  83. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi.egg-info/dependency_links.txt +0 -0
  84. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi.egg-info/requires.txt +0 -0
  85. {syndesi-0.4.4 → syndesi-0.5.1}/syndesi.egg-info/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syndesi
3
- Version: 0.4.4
3
+ Version: 0.5.1
4
4
  Summary: Syndesi
5
5
  Author-email: Sébastien Deriaz <sebastien.deriaz1@gmail.com>
6
- License: GPL
7
- Keywords: python,syndesi,interface,ethernet,serial,visa
6
+ License-Expression: GPL-3.0-or-later
7
+ Keywords: python,syndesi,interface,ethernet,serial,visa,protocol,driver,adpater
8
8
  Classifier: Development Status :: 3 - Alpha
9
9
  Classifier: Intended Audience :: Education
10
10
  Classifier: Programming Language :: Python :: 3
@@ -11,9 +11,11 @@ dynamic = ["version"]
11
11
  description = "Syndesi"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
14
- license = { text = "GPL" }
14
+ license = "GPL-3.0-or-later"
15
+ license-files = ["LICENSE"]
15
16
  authors = [{ name = "Sébastien Deriaz", email = "sebastien.deriaz1@gmail.com" }]
16
- keywords = ["python", "syndesi", "interface", "ethernet", "serial", "visa"]
17
+ keywords = ["python", "syndesi", "interface", "ethernet",
18
+ "serial", "visa", "protocol", "driver", "adpater"]
17
19
  classifiers = [
18
20
  "Development Status :: 3 - Alpha",
19
21
  "Intended Audience :: Education",
@@ -29,7 +31,7 @@ dependencies = ['prompt_toolkit', 'pyserial', 'rich', 'platformdirs']
29
31
 
30
32
  [project.scripts]
31
33
  syndesi = "syndesi.scripts.syndesi:main"
32
- syndesi-backend = "syndesi.scripts.syndesi_backend:main"
34
+ syndesi-trace = "syndesi.scripts.syndesi_trace:main"
33
35
 
34
36
  [tool.setuptools.packages.find]
35
37
  where = ["."]
@@ -50,7 +52,7 @@ ignore = ["E501"]
50
52
  [tool.ruff.format]
51
53
 
52
54
  [tool.ruff.lint.pydocstyle]
53
- convention = "numpy" # or "google"
55
+ convention = "numpy"
54
56
 
55
57
  [tool.isort]
56
58
  profile = "black"
@@ -74,4 +76,4 @@ exclude = ["tests"]
74
76
  # All arguments are necessary and it would not make sense to group them / move them
75
77
  # too-many-positional-arguments is kept however because it makes sense
76
78
  # Also disable W1203 to allow f-strings in logging lines
77
- disable = ["R0913", "W1203"]
79
+ disable = ["R0913", "W1203", "R0903"]
@@ -4,13 +4,14 @@ Syndesi module
4
4
 
5
5
  from .adapters.ip import IP
6
6
  from .adapters.serialport import SerialPort
7
+ from .adapters.stop_conditions import Continuation, Length, Termination, Total
7
8
  from .adapters.timeout import Timeout
8
9
  from .adapters.visa import Visa
9
10
  from .protocols.delimited import Delimited
10
11
  from .protocols.modbus import Modbus
11
12
  from .protocols.raw import Raw
12
13
  from .protocols.scpi import SCPI
13
- from .tools.log import log
14
+ from .tools.logmanager import log
14
15
 
15
16
  __all__ = [
16
17
  "IP",
@@ -22,4 +23,8 @@ __all__ = [
22
23
  "SCPI",
23
24
  "log",
24
25
  "Timeout",
26
+ "Continuation",
27
+ "Length",
28
+ "Termination",
29
+ "Total",
25
30
  ]
@@ -0,0 +1,478 @@
1
+ # File : adapter.py
2
+ # Author : Sébastien Deriaz
3
+ # License : GPL
4
+
5
+ """
6
+ Adapters provide a common abstraction for the media layers (physical + data link + network)
7
+
8
+ The user calls methods of the Adapter class synchronously.
9
+
10
+ An adapter is meant to work with bytes objects but it can accept strings.
11
+ Strings will automatically be converted to bytes using utf-8 encoding
12
+
13
+ Each adapter contains a worker thread that monitors the low-level communication layers.
14
+ This approach allows for precise time management (when each fragment is sent/received) and allows
15
+ for asynchronous events (fragment received).
16
+
17
+ Async facade:
18
+ - aopen/awrite/aread/aread_detailed simply await the SAME underlying worker-thread commands
19
+ using asyncio.wrap_future (no extra threads are spawned).
20
+ """
21
+
22
+ # NOTE:
23
+ # This version removes the "worker publishes events into a queue that read_detailed consumes".
24
+ # Instead:
25
+ # - The worker continuously assembles AdapterFrame from fragments (as before).
26
+ # - A read_detailed command registers a "pending read" inside the worker.
27
+ # - When a frame completes, the worker either:
28
+ # * completes the pending read future, OR
29
+ # * buffers the frame for later buffered reads, and optionally calls the callback.
30
+ #
31
+ # This avoids having a sync queue AND an async queue, and makes async wrappers trivial.
32
+
33
+ import asyncio
34
+ import threading
35
+ import weakref
36
+ from abc import abstractmethod
37
+ from collections.abc import Callable
38
+ from enum import Enum
39
+ from types import EllipsisType
40
+
41
+ from syndesi.tools.errors import AdapterError
42
+
43
+ from ..component import AdapterFrame, Component, Descriptor, ReadScope
44
+ from ..tools.log_settings import LoggerAlias
45
+ from ..tools.types import NumberLike, is_number
46
+ from .adapter_worker import (
47
+ AdapterEvent,
48
+ AdapterWorker,
49
+ CloseCommand,
50
+ FlushReadCommand,
51
+ IsOpenCommand,
52
+ OpenCommand,
53
+ ReadCommand,
54
+ SetDescriptorCommand,
55
+ SetEventCallbackCommand,
56
+ SetStopConditionsCommand,
57
+ SetTimeoutCommand,
58
+ StopThreadCommand,
59
+ WriteCommand,
60
+ )
61
+ from .stop_conditions import Fragment, StopCondition
62
+ from .timeout import Timeout, TimeoutAction, any_to_timeout
63
+
64
+ fragments: list[Fragment]
65
+
66
+
67
+ # pylint: disable=too-many-public-methods, too-many-instance-attributes
68
+ class Adapter(Component[bytes], AdapterWorker):
69
+ """
70
+ Adapter class
71
+
72
+ An adapter manages communication with a hardware device.
73
+ """
74
+
75
+ class WorkerTimeout(Enum):
76
+ """Timeout value for each worker command scenario"""
77
+
78
+ OPEN = 2
79
+ STOP = 1
80
+ IMMEDIATE_COMMAND = 0.2
81
+ CLOSE = 0.5
82
+ WRITE = 0.5
83
+ READ = None
84
+
85
+ def __init__(
86
+ self,
87
+ *,
88
+ descriptor: Descriptor,
89
+ stop_conditions: StopCondition | EllipsisType | list[StopCondition],
90
+ timeout: Timeout | EllipsisType | NumberLike | None,
91
+ alias: str,
92
+ encoding: str = "utf-8",
93
+ event_callback: Callable[[AdapterEvent], None] | None = None,
94
+ auto_open: bool = True,
95
+ ) -> None:
96
+ Component.__init__(self, LoggerAlias.ADAPTER)
97
+ AdapterWorker.__init__(self, encoding)
98
+
99
+ self._alias = alias
100
+
101
+ self._descriptor = descriptor
102
+ self.auto_open = auto_open
103
+
104
+ self._initial_event_callback = event_callback
105
+
106
+ # Default stop conditions
107
+ self._initial_stop_conditions: list[StopCondition]
108
+ if stop_conditions is ...:
109
+ self._is_default_stop_condition = True
110
+ self._initial_stop_conditions = self._default_stop_conditions()
111
+ else:
112
+ self._is_default_stop_condition = False
113
+ if isinstance(stop_conditions, StopCondition):
114
+ self._initial_stop_conditions = [stop_conditions]
115
+ elif isinstance(stop_conditions, list):
116
+ self._initial_stop_conditions = stop_conditions
117
+ else:
118
+ raise ValueError("Invalid stop_conditions")
119
+
120
+ # Default timeout
121
+ self.is_default_timeout = timeout is Ellipsis
122
+
123
+ if timeout is Ellipsis:
124
+ self._initial_timeout = self._default_timeout()
125
+ elif isinstance(timeout, Timeout):
126
+ self._initial_timeout = timeout
127
+ elif is_number(timeout):
128
+ self._initial_timeout = Timeout(timeout, action=TimeoutAction.ERROR)
129
+ elif timeout is None:
130
+ self._initial_timeout = Timeout(None)
131
+ else:
132
+ raise ValueError(f"Invalid timeout : {timeout}")
133
+
134
+ # Worker thread
135
+ self._worker_thread = threading.Thread(
136
+ target=self._worker_thread_method, daemon=True
137
+ )
138
+ self._worker_thread.start()
139
+
140
+ # Serialize read/write/query ordering for sync callers.
141
+ self._sync_io_lock = threading.Lock()
142
+ # Serialize read/write/query ordering for async callers.
143
+ self._async_io_lock = asyncio.Lock()
144
+
145
+ self._logger.info(f"Setting up {self._descriptor} adapter ")
146
+ self._update_descriptor()
147
+ self.set_stop_conditions(self._initial_stop_conditions)
148
+ self.set_timeout(self._initial_timeout)
149
+ self.set_event_callback(self._initial_event_callback)
150
+
151
+ if self._descriptor.is_initialized() and auto_open:
152
+ self.open()
153
+
154
+ weakref.finalize(self, self._cleanup)
155
+
156
+ def get_descriptor(self) -> Descriptor:
157
+ """
158
+ Return the adapter's descriptor
159
+
160
+ Returns
161
+ -------
162
+ descriptor : Descriptor
163
+ """
164
+ return self._descriptor
165
+
166
+ # ┌──────────────────────────┐
167
+ # │ Defaults / configuration │
168
+ # └──────────────────────────┘
169
+
170
+ def _stop(self) -> None:
171
+ super()._stop()
172
+ cmd = StopThreadCommand()
173
+ self._worker_send_command(cmd)
174
+ try:
175
+ cmd.result(self.WorkerTimeout.STOP.value)
176
+ except AdapterError:
177
+ pass
178
+
179
+ def _update_descriptor(self) -> None:
180
+ cmd = SetDescriptorCommand(self._descriptor)
181
+ self._worker_send_command(cmd)
182
+ cmd.result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
183
+
184
+ @abstractmethod
185
+ def _default_timeout(self) -> Timeout:
186
+ raise NotImplementedError
187
+
188
+ @abstractmethod
189
+ def _default_stop_conditions(self) -> list[StopCondition]:
190
+ raise NotImplementedError
191
+
192
+ def __str__(self) -> str:
193
+ return str(self._descriptor)
194
+
195
+ def __repr__(self) -> str:
196
+ return self.__str__()
197
+
198
+ def _cleanup(self) -> None:
199
+ try:
200
+ if self.is_open():
201
+ self.close()
202
+ except AdapterError:
203
+ pass
204
+ self._stop()
205
+
206
+ # ┌────────────┐
207
+ # │ Public API │
208
+ # └────────────┘
209
+
210
+ def set_timeout(self, timeout: Timeout | None | float) -> None:
211
+ """
212
+ Set adapter timeout
213
+
214
+ Parameters
215
+ ----------
216
+ timeout : Timeout, float or None
217
+ """
218
+ # This is read by the worker when ReadCommand.timeout is ...
219
+ timeout_instance = any_to_timeout(timeout)
220
+ cmd = SetTimeoutCommand(timeout_instance)
221
+ self._worker_send_command(cmd)
222
+ cmd.result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
223
+
224
+ def set_default_timeout(self, default_timeout: Timeout | None) -> None:
225
+ """
226
+ Configure adapter default timeout. Timeout will only be set if none
227
+ has been configured before
228
+
229
+ Parameters
230
+ ----------
231
+ default_timeout : Timeout or None
232
+ """
233
+ if self.is_default_timeout:
234
+ new_timeout = any_to_timeout(default_timeout)
235
+ self._logger.debug(f"Setting default timeout to {new_timeout}")
236
+ self.set_timeout(new_timeout)
237
+
238
+ def set_stop_conditions(
239
+ self, stop_conditions: StopCondition | None | list[StopCondition]
240
+ ) -> None:
241
+ """
242
+ Set adapter stop-conditions
243
+
244
+ Parameters
245
+ ----------
246
+ stop_conditions : [StopCondition] or None
247
+ """
248
+ if isinstance(stop_conditions, list):
249
+ lst = stop_conditions
250
+ elif isinstance(stop_conditions, StopCondition):
251
+ lst = [stop_conditions]
252
+ elif stop_conditions is None:
253
+ lst = []
254
+ else:
255
+ raise ValueError("Invalid stop_conditions")
256
+
257
+ cmd = SetStopConditionsCommand(lst)
258
+ self._worker_send_command(cmd)
259
+ cmd.result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
260
+
261
+ def set_default_stop_conditions(self, stop_conditions: list[StopCondition]) -> None:
262
+ """
263
+ Configure adapter default stop-condition. Stop-condition will only be set if none
264
+ has been configured before
265
+
266
+ Parameters
267
+ ----------
268
+ stop_conditions : [StopCondition]
269
+ """
270
+ if self._is_default_stop_condition:
271
+ self.set_stop_conditions(stop_conditions)
272
+
273
+ def set_event_callback(
274
+ self, callback: Callable[[AdapterEvent], None] | None
275
+ ) -> None:
276
+ """
277
+ Configure event callback. Event callback is called as such :
278
+
279
+ callback(event : AdapterEvent)
280
+
281
+ Parameters
282
+ ----------
283
+ callback : callable
284
+
285
+ """
286
+ cmd = SetEventCallbackCommand(callback)
287
+ self._worker_send_command(cmd)
288
+ cmd.result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
289
+
290
+ # ==== open ====
291
+
292
+ def _open_future(self) -> OpenCommand:
293
+ cmd = OpenCommand()
294
+ self._worker_send_command(cmd)
295
+ return cmd
296
+
297
+ def open(self) -> None:
298
+ """
299
+ Open adapter communication with the target (blocking)
300
+ """
301
+ return self._open_future().result(self.WorkerTimeout.OPEN.value)
302
+
303
+ async def aopen(self) -> None:
304
+ """
305
+ Open adapter communication with the target (async)
306
+ """
307
+ await asyncio.wrap_future(self._open_future())
308
+
309
+ # ==== close ====
310
+
311
+ def _close_future(self) -> CloseCommand:
312
+ cmd = CloseCommand()
313
+ self._worker_send_command(cmd)
314
+ return cmd
315
+
316
+ def close(self) -> None:
317
+ """
318
+ Close adapter communication with the target (blocking)
319
+ """
320
+ self._close_future().result(self.WorkerTimeout.CLOSE.value)
321
+
322
+ async def aclose(self) -> None:
323
+ """
324
+ Close adapter communication with the target (async)
325
+ """
326
+ await asyncio.wrap_future(self._close_future())
327
+
328
+ # ==== read_detailed ====
329
+
330
+ def _read_detailed_future(
331
+ self,
332
+ timeout: Timeout | EllipsisType | None,
333
+ stop_conditions: StopCondition | EllipsisType | list[StopCondition],
334
+ scope: str,
335
+ ) -> ReadCommand:
336
+ cmd = ReadCommand(
337
+ timeout=timeout,
338
+ stop_conditions=stop_conditions,
339
+ scope=ReadScope(scope),
340
+ )
341
+ self._worker_send_command(cmd)
342
+ return cmd
343
+
344
+ def read_detailed(
345
+ self,
346
+ timeout: Timeout | EllipsisType | None = ...,
347
+ stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
348
+ scope: str = ReadScope.BUFFERED.value,
349
+ ) -> AdapterFrame:
350
+ with self._sync_io_lock:
351
+ result = self._read_detailed_future(
352
+ timeout=timeout, stop_conditions=stop_conditions, scope=scope
353
+ ).result(self.WorkerTimeout.READ.value)
354
+ return result
355
+
356
+ async def aread_detailed(
357
+ self,
358
+ timeout: Timeout | EllipsisType | None = ...,
359
+ stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
360
+ scope: str = ReadScope.BUFFERED.value,
361
+ ) -> AdapterFrame:
362
+ async with self._async_io_lock:
363
+ return await asyncio.wrap_future(
364
+ self._read_detailed_future(
365
+ timeout=timeout, stop_conditions=stop_conditions, scope=scope
366
+ )
367
+ )
368
+
369
+ # ==== read ====
370
+
371
+ def read(
372
+ self,
373
+ timeout: Timeout | EllipsisType | None = ...,
374
+ stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
375
+ scope: str = ReadScope.BUFFERED.value,
376
+ ) -> bytes:
377
+ frame = self.read_detailed(
378
+ timeout=timeout, stop_conditions=stop_conditions, scope=scope
379
+ )
380
+ return frame.get_payload()
381
+
382
+ async def aread(
383
+ self,
384
+ timeout: Timeout | EllipsisType | None = ...,
385
+ stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
386
+ scope: str = ReadScope.BUFFERED.value,
387
+ ) -> bytes:
388
+ frame = await self.aread_detailed(
389
+ timeout=timeout, stop_conditions=stop_conditions, scope=scope
390
+ )
391
+ return frame.get_payload()
392
+
393
+ # ==== flush_read ====
394
+
395
+ def _flush_read_future(self) -> FlushReadCommand:
396
+ cmd = FlushReadCommand()
397
+ self._worker_send_command(cmd)
398
+ return cmd
399
+
400
+ async def aflush_read(self) -> None:
401
+ """
402
+ Clear buffered completed frames and reset current fragment assembly (async)
403
+ """
404
+ async with self._async_io_lock:
405
+ await asyncio.wrap_future(self._flush_read_future())
406
+
407
+ def flush_read(self) -> None:
408
+ """
409
+ Clear buffered completed frames and reset current fragment assembly (blocking)
410
+ """
411
+ with self._sync_io_lock:
412
+ self._flush_read_future().result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
413
+
414
+ # ==== write ====
415
+
416
+ def _write_future(self, data: bytes | str) -> WriteCommand:
417
+ if isinstance(data, str):
418
+ data = data.encode(self.encoding)
419
+ cmd = WriteCommand(data)
420
+ self._worker_send_command(cmd)
421
+ return cmd
422
+
423
+ def write(self, data: bytes | str) -> None:
424
+ with self._sync_io_lock:
425
+ self._write_future(data).result(self.WorkerTimeout.WRITE.value)
426
+
427
+ async def awrite(self, data: bytes | str) -> None:
428
+ async with self._async_io_lock:
429
+ await asyncio.wrap_future(self._write_future(data))
430
+
431
+ # ==== query ====
432
+
433
+ async def aquery_detailed(
434
+ self,
435
+ payload: bytes,
436
+ timeout: Timeout | None | EllipsisType = ...,
437
+ stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
438
+ scope: str = ReadScope.BUFFERED.value,
439
+ ) -> AdapterFrame:
440
+ async with self._async_io_lock:
441
+ await asyncio.wrap_future(self._flush_read_future())
442
+ await asyncio.wrap_future(self._write_future(payload))
443
+ return await asyncio.wrap_future(
444
+ self._read_detailed_future(
445
+ timeout=timeout, stop_conditions=stop_conditions, scope=scope
446
+ )
447
+ )
448
+
449
+ def query_detailed(
450
+ self,
451
+ payload: bytes,
452
+ timeout: Timeout | None | EllipsisType = ...,
453
+ stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
454
+ scope: str = ReadScope.BUFFERED.value,
455
+ ) -> AdapterFrame:
456
+
457
+ with self._sync_io_lock:
458
+ self._flush_read_future().result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
459
+ self._write_future(payload).result(self.WorkerTimeout.WRITE.value)
460
+ output = self._read_detailed_future(
461
+ timeout=timeout, stop_conditions=stop_conditions, scope=scope
462
+ ).result(self.WorkerTimeout.READ.value)
463
+ return output
464
+
465
+ # ==== Other ====
466
+
467
+ def _is_open_future(self) -> IsOpenCommand:
468
+ cmd = IsOpenCommand()
469
+ self._worker_send_command(cmd)
470
+ return cmd
471
+
472
+ def is_open(self) -> bool:
473
+ """Check if the adapter is open"""
474
+ return self._is_open_future().result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
475
+
476
+ async def ais_open(self) -> bool:
477
+ """Asynchronously check if the adapter is open"""
478
+ return await asyncio.wrap_future(self._is_open_future())