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
syndesi/cli/backend_console.py
DELETED
|
@@ -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)
|
syndesi/cli/backend_status.py
DELETED
|
@@ -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")
|
syndesi/cli/backend_wrapper.py
DELETED
|
@@ -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()
|
syndesi/tools/backend_api.py
DELETED
|
@@ -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
|