syndesi 0.4.1__tar.gz → 0.4.2__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 (69) hide show
  1. syndesi-0.4.2/PKG-INFO +96 -0
  2. syndesi-0.4.2/README.md +74 -0
  3. {syndesi-0.4.1 → syndesi-0.4.2}/pyproject.toml +1 -3
  4. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/adapter.py +48 -44
  5. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/adapter_backend.py +54 -36
  6. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/adapter_session.py +24 -25
  7. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/descriptors.py +1 -1
  8. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/ip_backend.py +1 -1
  9. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/serialport_backend.py +6 -7
  10. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/stop_condition_backend.py +47 -26
  11. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/visa_backend.py +2 -2
  12. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/ip.py +3 -3
  13. syndesi-0.4.2/syndesi/adapters/stop_condition.py +90 -0
  14. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/timeout.py +2 -29
  15. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/cli/backend_status.py +7 -9
  16. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/cli/console.py +1 -54
  17. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/cli/shell.py +1 -14
  18. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/cli/shell_tools.py +0 -5
  19. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/protocols/delimited.py +7 -22
  20. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/protocols/modbus.py +3 -3
  21. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/protocols/raw.py +2 -2
  22. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/protocols/scpi.py +5 -4
  23. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/scripts/syndesi.py +1 -3
  24. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/tools/backend_api.py +4 -37
  25. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/tools/backend_logger.py +0 -1
  26. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/tools/errors.py +4 -5
  27. syndesi-0.4.2/syndesi/tools/internal.py +0 -0
  28. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/tools/log.py +0 -88
  29. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/tools/types.py +0 -44
  30. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/version.py +1 -1
  31. syndesi-0.4.2/syndesi.egg-info/PKG-INFO +96 -0
  32. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi.egg-info/SOURCES.txt +1 -0
  33. syndesi-0.4.1/PKG-INFO +0 -123
  34. syndesi-0.4.1/README.md +0 -101
  35. syndesi-0.4.1/syndesi/adapters/stop_condition.py +0 -163
  36. syndesi-0.4.1/syndesi.egg-info/PKG-INFO +0 -123
  37. {syndesi-0.4.1 → syndesi-0.4.2}/LICENSE +0 -0
  38. {syndesi-0.4.1 → syndesi-0.4.2}/setup.cfg +0 -0
  39. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/__init__.py +0 -0
  40. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/__main__.py +0 -0
  41. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/__init__.py +0 -0
  42. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/auto.py +0 -0
  43. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/__init__.py +0 -0
  44. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/adapter_manager.py +0 -0
  45. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/backend.py +0 -0
  46. syndesi-0.4.1/syndesi/cli/__init__.py → syndesi-0.4.2/syndesi/adapters/backend/backend_status.py +0 -0
  47. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/backend_tools.py +1 -1
  48. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/timed_queue.py +0 -0
  49. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/backend/timeout.py +0 -0
  50. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/ip_server.py +0 -0
  51. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/serialport.py +0 -0
  52. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/adapters/visa.py +0 -0
  53. {syndesi-0.4.1/syndesi/protocols → syndesi-0.4.2/syndesi/cli}/__init__.py +0 -0
  54. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/cli/backend_console.py +0 -0
  55. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/cli/backend_wrapper.py +0 -0
  56. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/cli/terminal.py +0 -0
  57. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/cli/terminal_apps.py +0 -0
  58. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/cli/terminal_tools.py +0 -0
  59. {syndesi-0.4.1/syndesi/scripts → syndesi-0.4.2/syndesi/protocols}/__init__.py +0 -0
  60. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/protocols/protocol.py +0 -0
  61. {syndesi-0.4.1/syndesi/tools → syndesi-0.4.2/syndesi/scripts}/__init__.py +0 -0
  62. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/scripts/syndesi_backend.py +0 -0
  63. /syndesi-0.4.1/syndesi/tools/internal.py → /syndesi-0.4.2/syndesi/tools/__init__.py +0 -0
  64. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/tools/exceptions.py +0 -0
  65. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi/tools/log_settings.py +0 -0
  66. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi.egg-info/dependency_links.txt +0 -0
  67. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi.egg-info/entry_points.txt +0 -0
  68. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi.egg-info/requires.txt +0 -0
  69. {syndesi-0.4.1 → syndesi-0.4.2}/syndesi.egg-info/top_level.txt +0 -0
syndesi-0.4.2/PKG-INFO ADDED
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: syndesi
3
+ Version: 0.4.2
4
+ Summary: Syndesi
5
+ Author-email: Sébastien Deriaz <sebastien.deriaz1@gmail.com>
6
+ License: GPL
7
+ Keywords: python,syndesi,interface,ethernet,serial,visa
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Education
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: Unix
12
+ Classifier: Operating System :: MacOS :: MacOS X
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: prompt_toolkit
18
+ Requires-Dist: pyserial
19
+ Requires-Dist: rich
20
+ Requires-Dist: platformdirs
21
+ Dynamic: license-file
22
+
23
+ # Syndesi Python Implementation
24
+
25
+ Syndesi description is available [here](https://github.com/syndesi-project/Syndesi/README.md)
26
+
27
+ Syndesi is a modular Python framework designed to streamline communication and control of a wide range of electronic instruments and devices. By providing a unified abstraction layer for adapters, protocols, and device drivers, Syndesi enables seamless integration with test equipment such as multimeters, oscilloscopes, power supplies, UART/USB devices, and more. Its flexible architecture supports both high-level and low-level operations, making it ideal for automation, data acquisition, and custom device interfacing in laboratory, industrial, and research environments.
28
+
29
+ ## Installation
30
+
31
+ The syndesi Python package can be installed through pip
32
+
33
+ ``pip install syndesi``
34
+
35
+ The package can also be installed locally by cloning this repository
36
+
37
+ ```bash
38
+ git clone https://github.com/syndesi-project/Syndesi
39
+ cd Syndesi
40
+ pip install .
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ The user can work with any of the three following layers :
46
+
47
+ - Adapters : low-level communication (IP, UART, ...)
48
+ - Protocols : Encapsulated protocols (Delimited, Modbus, ...)
49
+ - Drivers : Device or application specific commands
50
+
51
+ ### Adapters
52
+
53
+ The adapter allows the user to read and write raw data through IP, serial and VISA
54
+
55
+ ```python
56
+ from syndesi import IP
57
+
58
+ my_adapter = IP('192.168.1.12', port=5025)
59
+
60
+ my_adapter.write(b'ping\n')
61
+
62
+ my_adapter.read() # -> b'pong'
63
+ ```
64
+
65
+ ```python
66
+ from syndesi import SerialPort
67
+
68
+ arduino = SerialPort('/dev/ttyUSB0', baudrate=115200) # COMx on Windows
69
+ arduino.query(b'get_temperature\n') # -> 20.5
70
+ ```
71
+
72
+ ### Protocols
73
+
74
+ Protocols encapsulate and format data
75
+
76
+ ```python
77
+ from syndesi import IP, Delimited
78
+
79
+ my_server = Delimited(IP('test.server.local', port=1234))
80
+
81
+ my_server.query('Hello world\n') # -> Hello world (\n is removed by Delimited)
82
+
83
+ ```
84
+
85
+ ### Drivers
86
+
87
+ A driver only requires an adapter, the protocol (if used) is instanciated internally
88
+
89
+ ```python
90
+ from syndesi_drivers.instruments.mutlimeters.siglent.SDM3055 import SDM3055
91
+ from syndesi.adapters import IP
92
+
93
+ mm = SDM3055(IP("192.168.1.123"))
94
+
95
+ voltage = mm.measure_dc_voltage()
96
+ ```
@@ -0,0 +1,74 @@
1
+ # Syndesi Python Implementation
2
+
3
+ Syndesi description is available [here](https://github.com/syndesi-project/Syndesi/README.md)
4
+
5
+ Syndesi is a modular Python framework designed to streamline communication and control of a wide range of electronic instruments and devices. By providing a unified abstraction layer for adapters, protocols, and device drivers, Syndesi enables seamless integration with test equipment such as multimeters, oscilloscopes, power supplies, UART/USB devices, and more. Its flexible architecture supports both high-level and low-level operations, making it ideal for automation, data acquisition, and custom device interfacing in laboratory, industrial, and research environments.
6
+
7
+ ## Installation
8
+
9
+ The syndesi Python package can be installed through pip
10
+
11
+ ``pip install syndesi``
12
+
13
+ The package can also be installed locally by cloning this repository
14
+
15
+ ```bash
16
+ git clone https://github.com/syndesi-project/Syndesi
17
+ cd Syndesi
18
+ pip install .
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ The user can work with any of the three following layers :
24
+
25
+ - Adapters : low-level communication (IP, UART, ...)
26
+ - Protocols : Encapsulated protocols (Delimited, Modbus, ...)
27
+ - Drivers : Device or application specific commands
28
+
29
+ ### Adapters
30
+
31
+ The adapter allows the user to read and write raw data through IP, serial and VISA
32
+
33
+ ```python
34
+ from syndesi import IP
35
+
36
+ my_adapter = IP('192.168.1.12', port=5025)
37
+
38
+ my_adapter.write(b'ping\n')
39
+
40
+ my_adapter.read() # -> b'pong'
41
+ ```
42
+
43
+ ```python
44
+ from syndesi import SerialPort
45
+
46
+ arduino = SerialPort('/dev/ttyUSB0', baudrate=115200) # COMx on Windows
47
+ arduino.query(b'get_temperature\n') # -> 20.5
48
+ ```
49
+
50
+ ### Protocols
51
+
52
+ Protocols encapsulate and format data
53
+
54
+ ```python
55
+ from syndesi import IP, Delimited
56
+
57
+ my_server = Delimited(IP('test.server.local', port=1234))
58
+
59
+ my_server.query('Hello world\n') # -> Hello world (\n is removed by Delimited)
60
+
61
+ ```
62
+
63
+ ### Drivers
64
+
65
+ A driver only requires an adapter, the protocol (if used) is instanciated internally
66
+
67
+ ```python
68
+ from syndesi_drivers.instruments.mutlimeters.siglent.SDM3055 import SDM3055
69
+ from syndesi.adapters import IP
70
+
71
+ mm = SDM3055(IP("192.168.1.123"))
72
+
73
+ voltage = mm.measure_dc_voltage()
74
+ ```
@@ -11,7 +11,7 @@ dynamic = ["version"]
11
11
  description = "Syndesi"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
14
- license = { text = "GPL" } # update if different
14
+ license = { text = "GPL" }
15
15
  authors = [{ name = "Sébastien Deriaz", email = "sebastien.deriaz1@gmail.com" }]
16
16
  keywords = ["python", "syndesi", "interface", "ethernet", "serial", "visa"]
17
17
  classifiers = [
@@ -42,8 +42,6 @@ include = ["syndesi*"]
42
42
  [tool.ruff]
43
43
  target-version = "py311"
44
44
  line-length = 100
45
- select = ["D","E","F","I","B"] # include pydocstyle rules ("D")
46
- #ignore = ["D203","D213"] # pick your preferences
47
45
 
48
46
  [tool.ruff.lint]
49
47
  select = ["E", "F", "W", "I", "UP", "B"]
@@ -15,8 +15,8 @@
15
15
  # An adapter is meant to work with bytes objects but it can accept strings.
16
16
  # Strings will automatically be converted to bytes using utf-8 encoding
17
17
 
18
- from enum import Enum
19
18
  import logging
19
+ import os
20
20
  import queue
21
21
  import subprocess
22
22
  import sys
@@ -25,43 +25,44 @@ import time
25
25
  import weakref
26
26
  from abc import ABC, abstractmethod
27
27
  from collections.abc import Callable
28
+ from enum import Enum
28
29
  from multiprocessing.connection import Client, Connection
29
30
  from types import EllipsisType
30
31
  from typing import Any
31
- import os
32
32
 
33
- from .backend.backend_tools import BACKEND_REQUEST_DEFAULT_TIMEOUT
34
33
  from syndesi.tools.types import NumberLike, is_number
35
34
 
36
35
  from ..tools.backend_api import (
37
36
  BACKEND_PORT,
37
+ EXTRA_BUFFER_RESPONSE_TIME,
38
38
  Action,
39
39
  BackendResponse,
40
40
  Fragment,
41
41
  default_host,
42
42
  raise_if_error,
43
- EXTRA_BUFFER_RESPONSE_TIME
44
43
  )
45
44
  from ..tools.log_settings import LoggerAlias
46
45
  from .backend.adapter_backend import (
47
46
  AdapterDisconnected,
48
- AdapterResponseTimeout,
49
47
  AdapterReadPayload,
48
+ AdapterResponseTimeout,
50
49
  AdapterSignal,
51
50
  )
51
+ from .backend.backend_tools import BACKEND_REQUEST_DEFAULT_TIMEOUT
52
52
  from .backend.descriptors import Descriptor
53
- from .stop_condition import StopCondition, Continuation, StopConditionType, Total
53
+ from .stop_condition import Continuation, StopCondition, StopConditionType
54
54
  from .timeout import Timeout, TimeoutAction, any_to_timeout
55
55
 
56
56
  DEFAULT_STOP_CONDITION = Continuation(time=0.1)
57
57
 
58
- DEFAULT_TIMEOUT = Timeout(response=5, action='error')
58
+ DEFAULT_TIMEOUT = Timeout(response=5, action="error")
59
59
 
60
60
  # Maximum time to let the backend start
61
61
  START_TIMEOUT = 2
62
62
  # Time to shutdown the backend
63
63
  SHUTDOWN_DELAY = 2
64
64
 
65
+
65
66
  class SignalQueue(queue.Queue[AdapterSignal]):
66
67
  def __init__(self) -> None:
67
68
  self._read_payload_counter = 0
@@ -70,12 +71,12 @@ class SignalQueue(queue.Queue[AdapterSignal]):
70
71
  def has_read_payload(self) -> bool:
71
72
  return self._read_payload_counter > 0
72
73
 
73
-
74
- def put(self, signal: AdapterSignal, block: bool = True, timeout: float | None = None) -> None:
74
+ def put(
75
+ self, signal: AdapterSignal, block: bool = True, timeout: float | None = None
76
+ ) -> None:
75
77
  if isinstance(signal, AdapterReadPayload):
76
78
  self._read_payload_counter += 1
77
79
  return super().put(signal, block, timeout)
78
-
79
80
 
80
81
  def get(self, block: bool = True, timeout: float | None = None) -> AdapterSignal:
81
82
  signal = super().get(block, timeout)
@@ -94,19 +95,20 @@ def is_backend_running(address: str, port: int) -> bool:
94
95
  conn.close()
95
96
  return True
96
97
 
98
+
97
99
  def start_backend(port: int | None = None) -> None:
98
100
  arguments = [
99
- sys.executable,
100
- "-m",
101
- "syndesi.adapters.backend.backend",
102
- "-s",
103
- str(SHUTDOWN_DELAY),
104
- "-q",
105
- "-p",
106
- str(BACKEND_PORT if port is None else port),
107
- ]
108
-
109
- stdin = subprocess.DEVNULL
101
+ sys.executable,
102
+ "-m",
103
+ "syndesi.adapters.backend.backend",
104
+ "-s",
105
+ str(SHUTDOWN_DELAY),
106
+ "-q",
107
+ "-p",
108
+ str(BACKEND_PORT if port is None else port),
109
+ ]
110
+
111
+ stdin = subprocess.DEVNULL
110
112
  stdout = subprocess.DEVNULL
111
113
  stderr = subprocess.DEVNULL
112
114
 
@@ -122,8 +124,8 @@ def start_backend(port: int | None = None) -> None:
122
124
 
123
125
  else:
124
126
  # Windows: detach from the parent's console so keyboard Ctrl+C won't propagate.
125
- CREATE_NEW_PROCESS_GROUP = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore
126
- DETACHED_PROCESS = 0x00000008 # not exposed by subprocess on all Pythons
127
+ CREATE_NEW_PROCESS_GROUP = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore
128
+ DETACHED_PROCESS = 0x00000008 # not exposed by subprocess on all Pythons
127
129
  # Optional: CREATE_NO_WINDOW (no window even for console apps)
128
130
  CREATE_NO_WINDOW = 0x08000000
129
131
 
@@ -138,9 +140,11 @@ def start_backend(port: int | None = None) -> None:
138
140
  close_fds=True,
139
141
  )
140
142
 
143
+
141
144
  class ReadScope(Enum):
142
- NEXT = 'next'
143
- BUFFERED = 'buffered'
145
+ NEXT = "next"
146
+ BUFFERED = "buffered"
147
+
144
148
 
145
149
  class Adapter(ABC):
146
150
  def __init__(
@@ -215,7 +219,7 @@ class Adapter(ABC):
215
219
  elif isinstance(stop_conditions, list):
216
220
  self._stop_conditions = stop_conditions
217
221
  else:
218
- raise ValueError('Invalid stop_conditions')
222
+ raise ValueError("Invalid stop_conditions")
219
223
 
220
224
  # Set the timeout
221
225
  self.is_default_timeout = False
@@ -335,7 +339,7 @@ class Adapter(ABC):
335
339
  action = Action(response[0])
336
340
 
337
341
  if action == Action.ADAPTER_SIGNAL:
338
- #if is_event(action):
342
+ # if is_event(action):
339
343
  if len(response) <= 1:
340
344
  raise RuntimeError(f"Invalid event response : {response}")
341
345
  signal: AdapterSignal = response[1]
@@ -371,7 +375,9 @@ class Adapter(ABC):
371
375
  self._logger.debug(f"Setting default timeout to {default_timeout}")
372
376
  self._timeout = default_timeout
373
377
 
374
- def set_stop_conditions(self, stop_conditions: StopCondition | None | list[StopCondition]) -> None:
378
+ def set_stop_conditions(
379
+ self, stop_conditions: StopCondition | None | list[StopCondition]
380
+ ) -> None:
375
381
  """
376
382
  Overwrite the stop-condition
377
383
 
@@ -411,7 +417,6 @@ class Adapter(ABC):
411
417
  self._signal_queue.get(block=False)
412
418
  except queue.Empty:
413
419
  break
414
-
415
420
 
416
421
  def previous_read_buffer_empty(self) -> bool:
417
422
  """
@@ -464,7 +469,7 @@ class Adapter(ABC):
464
469
  self,
465
470
  timeout: Timeout | EllipsisType | None = ...,
466
471
  stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
467
- scope : str = ReadScope.BUFFERED.value,
472
+ scope: str = ReadScope.BUFFERED.value,
468
473
  ) -> AdapterReadPayload:
469
474
  """
470
475
  Read data from the device
@@ -490,13 +495,13 @@ class Adapter(ABC):
490
495
  read_timeout = self._timeout
491
496
  else:
492
497
  read_timeout = any_to_timeout(timeout)
493
-
498
+
494
499
  if read_timeout is None:
495
500
  raise RuntimeError("Cannot read without setting a timeout")
496
501
 
497
502
  if stop_conditions is not ...:
498
503
  if isinstance(stop_conditions, StopCondition):
499
- stop_conditions = [stop_conditions]
504
+ stop_conditions = [stop_conditions]
500
505
  self._make_backend_request(Action.SET_STOP_CONDITIONs, stop_conditions)
501
506
 
502
507
  # First, we check if data is in the buffer and if the scope if set to BUFFERED
@@ -525,21 +530,21 @@ class Adapter(ABC):
525
530
  # Wait for the response time + a bit more
526
531
  read_stop_timestamp = read_init_time + _response
527
532
 
528
- # else:
529
- # start_read_id = None
530
- # read_init_time = None
531
-
532
-
533
533
  while True:
534
534
  try:
535
535
  if read_stop_timestamp is None:
536
536
  queue_timeout = None
537
537
  else:
538
- queue_timeout = max(0, read_stop_timestamp - time.time() + EXTRA_BUFFER_RESPONSE_TIME)
538
+ queue_timeout = max(
539
+ 0,
540
+ read_stop_timestamp
541
+ - time.time()
542
+ + EXTRA_BUFFER_RESPONSE_TIME,
543
+ )
539
544
 
540
545
  signal = self._signal_queue.get(timeout=queue_timeout)
541
- except queue.Empty:
542
- raise RuntimeError('Failed to receive response from backend')
546
+ except queue.Empty as e:
547
+ raise RuntimeError("Failed to receive response from backend") from e
543
548
  if isinstance(signal, AdapterReadPayload):
544
549
  output_signal = signal
545
550
  break
@@ -552,17 +557,16 @@ class Adapter(ABC):
552
557
  # Otherwise ignore it
553
558
 
554
559
  if output_signal is None:
555
- # TODO : Make read_timeout always Timeout, never None ?
556
560
  match read_timeout.action:
557
561
  case TimeoutAction.RETURN_EMPTY:
558
562
  t = time.time()
559
563
  return AdapterReadPayload(
560
- fragments=[Fragment(b'', t)],
564
+ fragments=[Fragment(b"", t)],
561
565
  stop_timestamp=t,
562
566
  stop_condition_type=StopConditionType.TIMEOUT,
563
567
  previous_read_buffer_used=False,
564
568
  response_timestamp=None,
565
- response_delay=None
569
+ response_delay=None,
566
570
  )
567
571
  case TimeoutAction.ERROR:
568
572
  raise TimeoutError(
@@ -570,7 +574,7 @@ class Adapter(ABC):
570
574
  )
571
575
  case _:
572
576
  raise NotImplementedError()
573
-
577
+
574
578
  else:
575
579
  return output_signal
576
580
 
@@ -7,19 +7,18 @@
7
7
 
8
8
  import logging
9
9
  import socket
10
+ import time
10
11
  from abc import ABC, abstractmethod
11
12
  from collections.abc import Generator
12
13
  from dataclasses import dataclass
13
14
  from enum import Enum
14
15
  from multiprocessing.connection import Connection
15
16
  from threading import Thread
16
- import time
17
- from typing import Protocol, cast
18
-
19
- from syndesi.tools.types import NumberLike
17
+ from typing import Protocol
20
18
 
21
19
  from ...tools.backend_api import AdapterBackendStatus, Fragment
22
20
  from ...tools.log_settings import LoggerAlias
21
+ from ..stop_condition import StopConditionType
23
22
  from .descriptors import Descriptor
24
23
  from .stop_condition_backend import (
25
24
  ContinuationBackend,
@@ -27,8 +26,6 @@ from .stop_condition_backend import (
27
26
  TotalBackend,
28
27
  )
29
28
 
30
- from ..stop_condition import Continuation, StopConditionType
31
-
32
29
 
33
30
  class HasFileno(Protocol):
34
31
  def fileno(self) -> int:
@@ -82,12 +79,13 @@ class AdapterDisconnected(AdapterSignal):
82
79
  # def __repr__(self) -> str:
83
80
  # return self.__str__()
84
81
 
82
+
85
83
  @dataclass
86
84
  class AdapterResponseTimeout(AdapterSignal):
87
- identifier : int
85
+ identifier: int
88
86
 
89
87
  def __str__(self) -> str:
90
- return f"Response timeout"
88
+ return "Response timeout"
91
89
 
92
90
  def __repr__(self) -> str:
93
91
  return self.__str__()
@@ -99,9 +97,9 @@ class AdapterReadPayload(AdapterSignal):
99
97
  stop_timestamp: float
100
98
  stop_condition_type: StopConditionType
101
99
  previous_read_buffer_used: bool
102
- response_timestamp : float | None
100
+ response_timestamp: float | None
103
101
  # Only used by client and set by frontend
104
- response_delay : float | None = None
102
+ response_delay: float | None = None
105
103
 
106
104
  def data(self) -> bytes:
107
105
  return b"".join([f.data for f in self.fragments])
@@ -118,9 +116,10 @@ class AdapterReadPayload(AdapterSignal):
118
116
  @dataclass
119
117
  class ResponseRequest:
120
118
  timestamp: float
121
- identifier : int
119
+ identifier: int
120
+
122
121
 
123
- def nmin(a : float | None, b : float | None) -> float | None:
122
+ def nmin(a: float | None, b: float | None) -> float | None:
124
123
  if a is None and b is None:
125
124
  return None
126
125
  elif a is None:
@@ -130,6 +129,7 @@ def nmin(a : float | None, b : float | None) -> float | None:
130
129
  else:
131
130
  return min(a, b)
132
131
 
132
+
133
133
  class AdapterBackend(ABC):
134
134
  class ThreadCommands(Enum):
135
135
  STOP = b"0"
@@ -168,7 +168,7 @@ class AdapterBackend(ABC):
168
168
  # None : No ask
169
169
  # float : Ask for a response to happen at the specified value at max
170
170
  self._response_request: ResponseRequest | None = None
171
- self._response_request_start : float | None = None
171
+ self._response_request_start: float | None = None
172
172
 
173
173
  self._first_fragment = True
174
174
 
@@ -246,10 +246,10 @@ class AdapterBackend(ABC):
246
246
  @abstractmethod
247
247
  def _socket_read(self) -> Fragment:
248
248
  raise NotImplementedError
249
-
250
- def _fragments_to_string(self, fragments : list[Fragment]) -> str:
249
+
250
+ def _fragments_to_string(self, fragments: list[Fragment]) -> str:
251
251
  if len(fragments) > 0:
252
- return '+'.join(repr(f.data) for f in fragments)
252
+ return "+".join(repr(f.data) for f in fragments)
253
253
  else:
254
254
  return str([])
255
255
 
@@ -263,7 +263,10 @@ class AdapterBackend(ABC):
263
263
  self.close()
264
264
  yield AdapterDisconnected()
265
265
  else:
266
- self._logger.debug(f"New fragment {fragment_delta_t:+.3f} {fragment}" + (" (first)" if self._first_fragment else ""))
266
+ self._logger.debug(
267
+ f"New fragment {fragment_delta_t:+.3f} {fragment}"
268
+ + (" (first)" if self._first_fragment else "")
269
+ )
267
270
  if self._status == AdapterBackendStatus.CONNECTED:
268
271
  t = time.time()
269
272
 
@@ -272,7 +275,9 @@ class AdapterBackend(ABC):
272
275
 
273
276
  if self._response_request is not None:
274
277
  for stop_condition in self._stop_conditions:
275
- if isinstance(stop_condition, (ContinuationBackend, TotalBackend)):
278
+ if isinstance(
279
+ stop_condition, (ContinuationBackend, TotalBackend)
280
+ ):
276
281
  self._response_request = None
277
282
  break
278
283
 
@@ -287,15 +292,18 @@ class AdapterBackend(ABC):
287
292
  kept = fragment
288
293
 
289
294
  # Run each stop condition one after the other, if a stop is reached, stop evaluating
290
- stop_condition_type : StopConditionType
295
+ stop_condition_type: StopConditionType
291
296
  for stop_condition in self._stop_conditions:
292
- stop, kept, self._previous_buffer, self._next_timeout_timestamp = \
293
- stop_condition.evaluate(kept)
297
+ (
298
+ stop,
299
+ kept,
300
+ self._previous_buffer,
301
+ self._next_timeout_timestamp,
302
+ ) = stop_condition.evaluate(kept)
294
303
  if stop:
295
304
  stop_condition_type = stop_condition.type()
296
305
  break
297
306
 
298
-
299
307
  # if kept.data != b'':
300
308
  # self.fragments.append(kept)
301
309
 
@@ -303,14 +311,22 @@ class AdapterBackend(ABC):
303
311
 
304
312
  if stop:
305
313
  self._first_fragment = True
306
- self._logger.debug(f"Payload {self._fragments_to_string(self.fragments)} ({stop_condition_type.value})")
307
- if self._response_request_start is None or len(self.fragments) == 0:
314
+ self._logger.debug(
315
+ f"Payload {self._fragments_to_string(self.fragments)} ({stop_condition_type.value})"
316
+ )
317
+ if (
318
+ self._response_request_start is None
319
+ or len(self.fragments) == 0
320
+ ):
308
321
  response_delay = None
309
322
  else:
310
323
  if self.fragments[0].timestamp is None:
311
324
  response_delay = None
312
325
  else:
313
- response_delay = self.fragments[0].timestamp - self._response_request_start
326
+ response_delay = (
327
+ self.fragments[0].timestamp
328
+ - self._response_request_start
329
+ )
314
330
  self._response_request_start = None
315
331
  yield AdapterReadPayload(
316
332
  fragments=self.fragments,
@@ -318,9 +334,9 @@ class AdapterBackend(ABC):
318
334
  stop_condition_type=stop_condition_type,
319
335
  previous_read_buffer_used=False,
320
336
  response_timestamp=self.fragments[0].timestamp,
321
- response_delay=response_delay
337
+ response_delay=response_delay,
322
338
  )
323
- self._next_timeout_timestamp = None # Experiment !
339
+ self._next_timeout_timestamp = None # Experiment !
324
340
  self.fragments.clear()
325
341
 
326
342
  if len(self._previous_buffer.data) > 0 and stop:
@@ -341,8 +357,9 @@ class AdapterBackend(ABC):
341
357
  """
342
358
  self._response_request_start = time.time()
343
359
  self._logger.debug(f"Setup read [{identifier}] in {response_time:.3f} s")
344
- self._response_request = ResponseRequest(self._response_request_start + response_time, identifier)
345
-
360
+ self._response_request = ResponseRequest(
361
+ self._response_request_start + response_time, identifier
362
+ )
346
363
 
347
364
  @abstractmethod
348
365
  def is_opened(self) -> bool:
@@ -366,7 +383,9 @@ class AdapterBackend(ABC):
366
383
  response_delay = None
367
384
  else:
368
385
  if self.fragments[0].timestamp is not None:
369
- response_delay = self.fragments[0].timestamp - self._response_request_start
386
+ response_delay = (
387
+ self.fragments[0].timestamp - self._response_request_start
388
+ )
370
389
  else:
371
390
  response_delay = None
372
391
  self._response_request_start = None
@@ -376,8 +395,10 @@ class AdapterBackend(ABC):
376
395
  stop_condition_type=StopConditionType.TIMEOUT,
377
396
  previous_read_buffer_used=False,
378
397
  fragments=self.fragments,
379
- response_timestamp=self.fragments[0].timestamp if len(self.fragments) > 0 else None,
380
- response_delay=response_delay
398
+ response_timestamp=(
399
+ self.fragments[0].timestamp if len(self.fragments) > 0 else None
400
+ ),
401
+ response_delay=response_delay,
381
402
  )
382
403
  # Reset response request
383
404
  if self._response_request is not None:
@@ -388,8 +409,7 @@ class AdapterBackend(ABC):
388
409
  return output
389
410
 
390
411
  elif (
391
- self._next_timeout_origin
392
- == self.AdapterTimeoutEventOrigin.RESPONSE_REQUEST
412
+ self._next_timeout_origin == self.AdapterTimeoutEventOrigin.RESPONSE_REQUEST
393
413
  ):
394
414
  if self._response_request is not None:
395
415
  signal = AdapterResponseTimeout(self._response_request.identifier)
@@ -402,8 +422,6 @@ class AdapterBackend(ABC):
402
422
  min_timestamp = None
403
423
  self._next_timeout_origin = None
404
424
 
405
- t = time.time()
406
-
407
425
  if self._next_timeout_timestamp is not None:
408
426
  min_timestamp = self._next_timeout_timestamp
409
427
  self._next_timeout_origin = self.AdapterTimeoutEventOrigin.TIMEOUT