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,96 +0,0 @@
1
- # backend_status.py
2
- # 13.07.2025
3
- # Sébastien Deriaz
4
-
5
- import argparse
6
- import logging
7
- import time
8
- from collections.abc import Callable
9
- from multiprocessing.connection import Connection
10
- from time import sleep
11
- from typing import Any
12
-
13
- from rich.console import Console
14
- from rich.text import Text
15
-
16
- from ..tools.backend_api import BACKEND_PORT, LOCALHOST
17
- from ..tools.backend_logger import BackendLogger
18
- from ..tools.log_settings import LoggerAlias
19
-
20
- LOGGING_COLORS = {
21
- logging.DEBUG: "grey66",
22
- logging.INFO: "green",
23
- logging.WARNING: "yellow",
24
- logging.ERROR: "red",
25
- logging.CRITICAL: "bold purple",
26
- }
27
-
28
- logging.getLogger().setLevel(logging.CRITICAL + 1)
29
-
30
-
31
- class LogHandler(logging.Handler):
32
- def __init__(
33
- self, callback: Callable[[logging.LogRecord], None] | None = None
34
- ) -> None:
35
- super().__init__()
36
- self.callback = callback
37
-
38
- def emit(self, record: logging.LogRecord) -> None:
39
- if self.callback is not None:
40
- self.callback(record)
41
-
42
-
43
- class BackendConsole:
44
- def __init__(self, input_args: list[str]) -> None:
45
- self.argument_parser = argparse.ArgumentParser()
46
- self.argument_parser.add_argument(
47
- "-a",
48
- "--address",
49
- type=str,
50
- default=LOCALHOST,
51
- help="Listening address, set it to the interface that will be used by the client",
52
- )
53
- self.argument_parser.add_argument(
54
- "-p", "--port", type=int, default=BACKEND_PORT
55
- )
56
- self.argument_parser.add_argument(
57
- "-l", "--log-level", type=str, choices=list(logging._nameToLevel.keys())
58
- ) # pyright: ignore[reportPrivateUsage]
59
-
60
- args = self.argument_parser.parse_args(input_args)
61
-
62
- self.address = args.address
63
- self.port = args.port
64
-
65
- self._console = Console()
66
-
67
- self._log_handler = LogHandler()
68
- self._log_handler.callback = self._add_line
69
-
70
- logging.getLogger().addHandler(self._log_handler)
71
- self._start_time = time.time()
72
- self.conn: Connection[Any, Any] | None = None
73
- self._backend_logger = BackendLogger()
74
-
75
- def run(self) -> None:
76
- self._backend_logger.start()
77
- try:
78
- while True:
79
- sleep(1)
80
- except KeyboardInterrupt:
81
- pass
82
-
83
- def _format_record(self, record: logging.LogRecord) -> str:
84
- color = LOGGING_COLORS.get(record.levelno)
85
- relative_time = record.created - self._start_time
86
-
87
- line = f"[{color}]{relative_time:7.3f} {record.levelname:<8} {record.msg}[/]"
88
- if record.name == LoggerAlias.BACKEND.value:
89
- line = f"[bold]{line}[/bold]"
90
- elif record.name == LoggerAlias.BACKEND_LOGGER.value:
91
- line = f"[italic]{line}[/italic]"
92
- return line
93
-
94
- def _add_line(self, record: logging.LogRecord) -> None:
95
- formated_text = self._format_record(record)
96
- self._console.print(Text.from_markup(formated_text), markup=True)
@@ -1,274 +0,0 @@
1
- # backend_status.py
2
- # 13.07.2025
3
- # Sébastien Deriaz
4
-
5
- import argparse
6
- import logging
7
- import threading
8
- import time
9
- from collections.abc import Callable
10
- from multiprocessing.connection import Client, Pipe, wait
11
- from time import sleep
12
- from typing import cast
13
-
14
- from rich.console import Group
15
- from rich.live import Live
16
- from rich.panel import Panel
17
- from rich.table import Table
18
- from rich.text import Text
19
-
20
- from syndesi.adapters.backend.backend_tools import NamedConnection
21
- from syndesi.tools.log_settings import LoggerAlias
22
-
23
- from ..tools.backend_api import (
24
- BACKEND_PORT,
25
- LOCALHOST,
26
- Action,
27
- backend_request,
28
- )
29
- from ..tools.log import log_manager
30
-
31
- LOGGING_COLORS = {
32
- logging.DEBUG: "grey66",
33
- logging.INFO: "green",
34
- logging.WARNING: "yellow",
35
- logging.ERROR: "red",
36
- logging.CRITICAL: "bold purple",
37
- }
38
-
39
- logging.getLogger().setLevel(logging.CRITICAL + 1)
40
-
41
-
42
- class LogHandler(logging.Handler):
43
- def __init__(
44
- self, callback: Callable[[logging.LogRecord], None] | None = None
45
- ) -> None:
46
- super().__init__()
47
- self.callback = callback
48
-
49
- def emit(self, record: logging.LogRecord) -> None:
50
- if self.callback is not None:
51
- self.callback(record)
52
-
53
-
54
- class BackendStatus:
55
- DEFAULT_REFRESH_RATE = 0.5
56
- DEFAULT_N_CONSOLE_LINES = 20 # Number of lines in the console view
57
- CONNECTIONS_MIN_HEIGHT = 2
58
-
59
- def __init__(self, input_args: list[str]) -> None:
60
- self.argument_parser = argparse.ArgumentParser()
61
- self.argument_parser.add_argument(
62
- "-l",
63
- "--live",
64
- type=float,
65
- default=False,
66
- help="Live mode, update status continuously at set refresh rate",
67
- )
68
- self.argument_parser.add_argument(
69
- "--log-lines",
70
- type=int,
71
- default=self.DEFAULT_N_CONSOLE_LINES,
72
- help="Number of log lines to display",
73
- )
74
- self.argument_parser.add_argument(
75
- "-a",
76
- "--address",
77
- type=str,
78
- default=LOCALHOST,
79
- help="Listening address, set it to the interface that will be used by the client",
80
- )
81
- self.argument_parser.add_argument(
82
- "-p", "--port", type=int, default=BACKEND_PORT
83
- )
84
- self.argument_parser.add_argument(
85
- "--log-level", type=str, choices=list(logging._nameToLevel.keys())
86
- ) # pyright: ignore[reportPrivateUsage]
87
-
88
- args = self.argument_parser.parse_args(input_args)
89
-
90
- self._n_console_lines = args.log_lines
91
- self.host: str = args.address
92
- self.port: int = args.port
93
- self.live_delay: float | None = args.live
94
-
95
- # Buffer for last N_CONSOLE_LINES log messages
96
- self.console_lines: list[str] = []
97
- self.console_lock = threading.Lock() # Lock for thread safety
98
- log_manager.enable_backend_logging()
99
- self._start_time = time.time()
100
- self.conn: NamedConnection | None = None
101
-
102
- self._log_handler = LogHandler()
103
-
104
- self._new_console_line_r, self._new_console_line_w = Pipe()
105
-
106
- def run(self) -> None:
107
- self._log_handler.callback = self._add_line_status_screen
108
- logging.getLogger().addHandler(self._log_handler)
109
- adapter_table = None
110
- main_table = Table(box=None, padding=(0, 1))
111
- main_table.add_column(justify="right")
112
- main_table.add_column(justify="left")
113
- try:
114
- with Live(refresh_per_second=30) as live_display:
115
- while True:
116
- if self.conn is None:
117
- pid = ""
118
- backend_status = "[red3]● Offline"
119
- monitoring_connections = Table(
120
- "",
121
- box=None,
122
- caption_justify="right",
123
- show_header=False,
124
- pad_edge=False,
125
- )
126
- adapter_table = Table("", box=None, caption_justify="right")
127
-
128
- if self.conn is None:
129
- try:
130
- self.conn = NamedConnection(Client((self.host, self.port)))
131
- except (ConnectionRefusedError, OSError):
132
- self.conn = None
133
- sleep(0.1)
134
- else:
135
- backend_request(self.conn.conn, Action.SET_ROLE_MONITORING)
136
-
137
- if self.conn is not None:
138
- backend_status = "[green3]● Online"
139
-
140
- ready = wait([self.conn.conn, self._new_console_line_r])
141
-
142
- if self._new_console_line_r in ready:
143
- self._new_console_line_r.recv()
144
- elif self.conn.conn in ready:
145
- try:
146
- event = self.conn.conn.recv()
147
- except (ConnectionResetError, OSError, EOFError):
148
- self.conn = None
149
- continue
150
- else:
151
- action: Action = Action(event[0])
152
- if action == Action.ENUMERATE_ADAPTER_CONNECTIONS:
153
- adapter_table = Table(
154
- "", box=None, caption_justify="right"
155
- )
156
- snapshot: dict[str, tuple[bool, list[str]]] = event[
157
- 1
158
- ]
159
- unique_clients: set[str] = set()
160
- for _, (_, adapter_clients) in snapshot.items():
161
- unique_clients |= set(adapter_clients)
162
- for client in unique_clients:
163
- adapter_table.add_column(
164
- f"{client}", justify="center"
165
- )
166
-
167
- for adapter, (
168
- status,
169
- adapter_clients,
170
- ) in snapshot.items():
171
- status_indicator = (
172
- "[green3]●" if status else "[red3]●"
173
- )
174
- client_indicators: list[str] = []
175
- for client in unique_clients:
176
- client_indicators.append(
177
- "[green3]●[/]"
178
- if client in adapter_clients
179
- else "[red3]●[/]"
180
- )
181
- adapter_table.add_row(
182
- f"{adapter} {status_indicator}",
183
- *client_indicators,
184
- )
185
- elif action == Action.ENUMERATE_MONITORING_CONNECTIONS:
186
- monitoring_connections = Table(
187
- "",
188
- box=None,
189
- caption_justify="right",
190
- show_header=False,
191
- pad_edge=False,
192
- )
193
- # Update monitoring connections
194
- monitoring_response: list[tuple[str, str]] = event[
195
- 1
196
- ]
197
-
198
- for connection, desc in monitoring_response:
199
- if (
200
- connection
201
- == log_manager.backend_logger_conn_description()
202
- or connection == self.conn.local()
203
- ):
204
- style = ("grey50", "grey23")
205
- else:
206
- style = ("grey", "grey50")
207
-
208
- monitoring_connections.add_row(
209
- f"[{style[0]}]{connection}[/] [{style[1]}]({desc})[/]"
210
- )
211
- for _ in range(
212
- self.CONNECTIONS_MIN_HEIGHT
213
- - len(monitoring_response)
214
- ):
215
- monitoring_connections.add_row("")
216
-
217
- elif action == Action.BACKEND_STATS:
218
- # Update Backend stats
219
- pid_response: int = cast(int, event[1])
220
- pid = str(pid_response)
221
-
222
- main_table = Table(box=None, padding=(0, 1))
223
- main_table.add_column(justify="right")
224
- main_table.add_column(justify="left")
225
- main_table.add_row("Status :", backend_status)
226
- main_table.add_row("PID :", pid)
227
- main_table.add_row("Logger connections :", monitoring_connections)
228
- main_table.add_row(
229
- "Adapter connections :",
230
- (adapter_table if adapter_table is not None else Text("")),
231
- )
232
-
233
- # Build the console panel
234
- with self.console_lock:
235
- console_content = "\n".join(self.console_lines) or " "
236
- console_panel = Panel(
237
- console_content,
238
- title="Console",
239
- height=self._n_console_lines + 2, # +2 for panel borders
240
- border_style="dim",
241
- padding=(0, 1),
242
- )
243
-
244
- # Group the main table and console panel vertically
245
- group = Group(main_table, console_panel)
246
- live_display.update(group)
247
-
248
- if self.live_delay is not None:
249
- sleep(self.live_delay)
250
- else:
251
- break
252
- except KeyboardInterrupt:
253
- pass
254
-
255
- def _format_record(self, record: logging.LogRecord) -> str:
256
- color = LOGGING_COLORS.get(record.levelno)
257
- relative_time = record.created - self._start_time
258
-
259
- line = f"[{color}]{relative_time:7.3f} {record.levelname:<8} {record.msg}[/]"
260
- if record.name == LoggerAlias.BACKEND.value:
261
- line = f"[bold]{line}[/bold]"
262
- return line
263
-
264
- def _add_line_status_screen(self, record: logging.LogRecord) -> None:
265
- """
266
- Add a log message to the console buffer, with color formatting by log level.
267
- Thread-safe.
268
- """
269
- formated_text = self._format_record(record)
270
- with self.console_lock:
271
- self.console_lines.append(formated_text)
272
- if len(self.console_lines) > self._n_console_lines:
273
- self.console_lines = self.console_lines[-self._n_console_lines :]
274
- self._new_console_line_w.send(b"\x00")
@@ -1,61 +0,0 @@
1
- import argparse
2
- from contextlib import nullcontext
3
-
4
- from rich.console import Console
5
-
6
- from syndesi.adapters.backend.backend import Backend
7
- from syndesi.tools.backend_api import BACKEND_PORT, LOCALHOST
8
-
9
-
10
- class BackendWrapper:
11
- def __init__(self, remaining_args: list[str]) -> None:
12
- argument_parser = argparse.ArgumentParser()
13
-
14
- argument_parser.add_argument(
15
- "-a",
16
- "--address",
17
- type=str,
18
- default=LOCALHOST,
19
- help="Listening address, set it to the interface that will be used by the client",
20
- )
21
- argument_parser.add_argument("-p", "--port", type=int, default=BACKEND_PORT)
22
- argument_parser.add_argument(
23
- "-s",
24
- "--shutdown-delay",
25
- type=int,
26
- default=None,
27
- help="Delay before the backend shutdowns automatically",
28
- )
29
- argument_parser.add_argument(
30
- "-q", "--quiet", default=False, action="store_true"
31
- )
32
- argument_parser.add_argument(
33
- "-v", "--verbose", default=False, action="store_true"
34
- )
35
-
36
- args = argument_parser.parse_args(remaining_args)
37
-
38
- self.address = args.address
39
- self.port = args.port
40
- self.shutdown_delay = args.shutdown_delay
41
- self.quiet = args.quiet
42
- self.verbose = args.verbose
43
-
44
- def run(self) -> None:
45
- console = Console()
46
- with (
47
- console.status(
48
- f"[bold green]Syndesi backend running on {self.address}", spinner="dots"
49
- )
50
- if not self.quiet
51
- else nullcontext()
52
- ):
53
- backend = Backend(
54
- host=self.address,
55
- port=self.port,
56
- backend_shutdown_delay=self.shutdown_delay,
57
- )
58
- try:
59
- backend.start()
60
- except KeyboardInterrupt:
61
- pass
@@ -1,37 +0,0 @@
1
- # File : syndesi_backend.py
2
- # Author : Sébastien Deriaz
3
- # License : GPL
4
-
5
- import argparse
6
-
7
- from ..cli.backend_console import BackendConsole
8
- from ..cli.backend_status import BackendStatus
9
- from ..cli.backend_wrapper import BackendWrapper
10
-
11
-
12
- def main() -> None:
13
- argument_parser = argparse.ArgumentParser(prog="syndesi-backend")
14
-
15
- argument_parser.add_argument(
16
- "--status", action="store_true", help="Show backend status"
17
- )
18
-
19
- argument_parser.add_argument(
20
- "--console", action="store_true", help="Run backend console"
21
- )
22
-
23
- args, remaining_args = argument_parser.parse_known_args()
24
-
25
- if args.status:
26
- status = BackendStatus(remaining_args)
27
- status.run()
28
- elif args.console:
29
- console = BackendConsole(remaining_args)
30
- console.run()
31
- else:
32
- backend = BackendWrapper(remaining_args)
33
- backend.run()
34
-
35
-
36
- if __name__ == "__main__":
37
- main()
@@ -1,175 +0,0 @@
1
- # File : backend_api.py
2
- # Author : Sébastien Deriaz
3
- # License : GPL
4
-
5
- from dataclasses import dataclass
6
- from enum import Enum
7
- from multiprocessing.connection import Connection, wait
8
- from typing import Any, Protocol
9
-
10
- from syndesi.tools.errors import BackendCommunicationError, BackendError
11
-
12
- BACKEND_PORT = 59677
13
- LOCALHOST = "127.0.0.1"
14
-
15
- default_host = LOCALHOST
16
- # AUTHKEY = b'syndesi'
17
-
18
- # Backend protocol format
19
- # (action, arguments)
20
- # If the command succeeds, it is returned as :
21
- # (action, other arguments)
22
- # If the command fails, it is returned as :
23
- # (error, error_description)
24
-
25
- # The backend links the client with an adapter when SELECT_ADAPTER is sent along with an adapter descriptor
26
-
27
- EXTRA_BUFFER_RESPONSE_TIME = 1
28
-
29
-
30
- class Action(Enum):
31
- # All adapters
32
- SELECT_ADAPTER = "select"
33
- OPEN = "open" # (descriptor,stop_condition) -> ()
34
- CLOSE = "close" # (descriptor,force) -> ()
35
- # FORCE_CLOSE = "force_close" # (descriptor,) -> ()
36
- WRITE = "write" # (descriptor,data) -> ()
37
- READ = "read" # (descriptor,full_output,temporary_timeout,temporary_stop_condition) -> (data,metrics)
38
- SET_STOP_CONDITIONs = "set_stop_condition" # (descriptor,stop_condition)
39
- FLUSHREAD = "flushread"
40
- START_READ = "start_read" # Start a read (descriptor,response_time)
41
- RESPONSE_TIMEOUT = "response_timeout"
42
-
43
- # Signal
44
- ADAPTER_SIGNAL = "adapter_signal"
45
-
46
- # Other
47
- SET_ROLE_ADAPTER = (
48
- "set_role_adapter" # Define the client as an adapter (exchange of data)
49
- )
50
- SET_ROLE_MONITORING = "set_role_monitoring" # The client queries for backend info
51
- SET_ROLE_LOGGER = "set_role_logger" # The client receives logs
52
- SET_LOG_LEVEL = "set_log_level"
53
- PING = "ping"
54
- STOP = "stop"
55
-
56
- # Backend debugger
57
- ENUMERATE_ADAPTER_CONNECTIONS = "enumerate_adapter_connections"
58
- ENUMERATE_MONITORING_CONNECTIONS = "enumerate_monitoring_connections"
59
- BACKEND_STATS = "backend_stats"
60
-
61
- # Errors
62
- ERROR_GENERIC = "error_generic"
63
- ERROR_UNKNOWN_ACTION = "error_unknown_action"
64
- ERROR_INVALID_REQUEST = "error_invalid_request"
65
- ERROR_ADAPTER_NOT_OPENED = "error_adapter_not_opened"
66
- ERROR_INVALID_ROLE = "error_invalid_role"
67
- ERROR_ADAPTER_DISCONNECTED = "error_adapter_disconnected"
68
- ERROR_BACKEND_DISCONNECTED = "error_backend_disconnected"
69
- ERROR_FAILED_TO_OPEN = "error_failed_to_open"
70
-
71
-
72
- def is_action_error(action: Action) -> bool:
73
- return action.value.startswith("error_")
74
-
75
- class BackendException(Exception):
76
- pass
77
-
78
- class ValidFragment(Protocol):
79
- data: bytes
80
-
81
- @dataclass
82
- class Fragment:
83
- data: bytes
84
- timestamp: float | None
85
-
86
- def __str__(self) -> str:
87
- return f"{self.data!r}"
88
-
89
- def __repr__(self) -> str:
90
- return self.__str__()
91
-
92
- def __getitem__(self, key: slice) -> "Fragment":
93
- # if self.data is None:
94
- # raise IndexError('Cannot index invalid fragment')
95
- return Fragment(self.data[key], self.timestamp)
96
-
97
- BackendResponse = tuple[object, ...]
98
-
99
-
100
- def frontend_send(conn: Connection, action: Action, *args: Any) -> bool:
101
- try:
102
- conn.send((action.value, *args))
103
- except (BrokenPipeError, OSError):
104
- return False
105
- else:
106
- return True
107
-
108
-
109
- def backend_request(
110
- conn: Connection,
111
- action: Action,
112
- *args: Any,
113
- timeout: float = EXTRA_BUFFER_RESPONSE_TIME,
114
- ) -> BackendResponse:
115
- try:
116
- conn.send((action.value, *args))
117
- except (BrokenPipeError, OSError) as err:
118
- raise BackendCommunicationError("Failed to communicate with backend") from err
119
- else:
120
- ready = wait([conn], timeout=timeout)
121
- if conn not in ready:
122
- raise BackendCommunicationError(
123
- "Failed to receive backend response in time"
124
- )
125
-
126
- try:
127
- raw_response: object = conn.recv()
128
- except (EOFError, ConnectionResetError) as err:
129
- raise BackendCommunicationError(
130
- f"Failed to receive backend response to {action.value}"
131
- ) from err
132
-
133
- # Check if the response is correctly formatted
134
- if not (isinstance(raw_response, tuple) and isinstance(raw_response[0], str)):
135
- raise BackendCommunicationError(
136
- f"Invalid response received from backend : {raw_response}"
137
- )
138
-
139
- response_action: Action = Action(raw_response[0])
140
- arguments: tuple[Any, ...] = raw_response[1:]
141
-
142
- if is_action_error(response_action):
143
- if len(arguments) > 0:
144
- if isinstance(arguments[0], str):
145
- error_message: str = arguments[0]
146
- else:
147
- error_message = "failed to read error message"
148
- else:
149
- error_message = "Missing error message"
150
- raise BackendError(f"{response_action} : {error_message}")
151
- return arguments
152
-
153
-
154
- backend_send = frontend_send
155
-
156
-
157
- def raise_if_error(response: BackendResponse) -> None:
158
- action = Action(response[0])
159
- if is_action_error(action):
160
- if len(response) > 1:
161
- description = response[1]
162
- else:
163
- description = f"{action}"
164
- raise BackendException(f"{action.name}/{description}")
165
- return
166
-
167
-
168
- class AdapterBackendStatus(Enum):
169
- DISCONNECTED = 0
170
- CONNECTED = 1
171
-
172
-
173
- class ClientStatus(Enum):
174
- DISCONNECTED = 0
175
- CONNECTED = 1