0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-13 19:21:50 +02:00

fix(rpc_server): pass cli config to server

This commit is contained in:
2024-07-03 20:29:26 +02:00
parent b9f9a003a2
commit 90178e2f61
4 changed files with 118 additions and 12 deletions

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import importlib import importlib
import importlib.metadata as imd import importlib.metadata as imd
import json
import os import os
import select import select
import subprocess import subprocess
@ -87,7 +88,7 @@ def _get_output(process, logger) -> None:
print(f"Error reading process output: {str(e)}") print(f"Error reading process output: {str(e)}")
def _start_plot_process(gui_id, gui_class, config, logger=None) -> None: def _start_plot_process(gui_id: str, gui_class: type, config: dict | str, logger=None) -> None:
""" """
Start the plot in a new process. Start the plot in a new process.
@ -98,6 +99,8 @@ def _start_plot_process(gui_id, gui_class, config, logger=None) -> None:
# pylint: disable=subprocess-run-check # pylint: disable=subprocess-run-check
command = ["bec-gui-server", "--id", gui_id, "--gui_class", gui_class.__name__] command = ["bec-gui-server", "--id", gui_id, "--gui_class", gui_class.__name__]
if config: if config:
if isinstance(config, dict):
config = json.dumps(config)
command.extend(["--config", config]) command.extend(["--config", config])
env_dict = os.environ.copy() env_dict = os.environ.copy()
@ -190,7 +193,7 @@ class BECGuiClientMixin:
if self._process is None or self._process.poll() is not None: if self._process is None or self._process.poll() is not None:
self._start_update_script() self._start_update_script()
self._process, self._process_output_processing_thread = _start_plot_process( self._process, self._process_output_processing_thread = _start_plot_process(
self._gui_id, self.__class__, self._client._service_config.config_path self._gui_id, self.__class__, self._client._service_config.config
) )
while not self.gui_is_alive(): while not self.gui_is_alive():
print("Waiting for GUI to start...") print("Waiting for GUI to start...")

View File

@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import inspect import inspect
import json
import signal import signal
import sys import sys
from contextlib import redirect_stderr, redirect_stdout from contextlib import redirect_stderr, redirect_stdout
@ -141,10 +142,30 @@ class SimpleFileLikeFromLogOutputFunc:
return return
def _start_server(gui_id: str, gui_class: Union[BECFigure, BECDockArea], config: str | None = None):
if config:
try:
config = json.loads(config)
service_config = ServiceConfig(config=config)
except (json.JSONDecodeError, TypeError):
service_config = ServiceConfig(config_path=config)
else:
# if no config is provided, use the default config
service_config = ServiceConfig()
bec_logger.configure(
service_config.redis,
QtRedisConnector,
service_name="BECWidgetsCLIServer",
service_config=service_config.service_config,
)
server = BECWidgetsCLIServer(gui_id=gui_id, config=service_config, gui_class=gui_class)
return server
def main(): def main():
import argparse import argparse
import os import os
import sys
from qtpy.QtCore import QSize from qtpy.QtCore import QSize
from qtpy.QtGui import QIcon from qtpy.QtGui import QIcon
@ -159,7 +180,7 @@ def main():
type=str, type=str,
help="Name of the gui class to be rendered. Possible values: \n- BECFigure\n- BECDockArea", help="Name of the gui class to be rendered. Possible values: \n- BECFigure\n- BECDockArea",
) )
parser.add_argument("--config", type=str, help="Config file") parser.add_argument("--config", type=str, help="Config file or config string.")
args = parser.parse_args() args = parser.parse_args()
@ -188,14 +209,7 @@ def main():
win = QMainWindow() win = QMainWindow()
win.setWindowTitle("BEC Widgets") win.setWindowTitle("BEC Widgets")
service_config = ServiceConfig(args.config) server = _start_server(args.id, gui_class, args.config)
bec_logger.configure(
service_config.redis,
QtRedisConnector,
service_name="BECWidgetsCLIServer",
service_config=service_config.service_config,
)
server = BECWidgetsCLIServer(gui_id=args.id, config=service_config, gui_class=gui_class)
gui = server.gui gui = server.gui
win.setCentralWidget(gui) win.setCentralWidget(gui)

View File

@ -3,6 +3,7 @@ from unittest import mock
import pytest import pytest
from bec_widgets.cli.client import BECFigure from bec_widgets.cli.client import BECFigure
from bec_widgets.cli.client_utils import BECGuiClientMixin, _start_plot_process
from .client_mocks import FakeDevice from .client_mocks import FakeDevice
@ -27,3 +28,49 @@ def test_rpc_call_accepts_device_as_input(cli_figure):
fig, mock_rpc_call = cli_figure fig, mock_rpc_call = cli_figure
fig.plot(x_name=dev1, y_name=dev2) fig.plot(x_name=dev1, y_name=dev2)
mock_rpc_call.assert_called_with("plot", x_name="samx", y_name="bpm4i") mock_rpc_call.assert_called_with("plot", x_name="samx", y_name="bpm4i")
@pytest.mark.parametrize(
"config, call_config",
[
(None, None),
("/path/to/config.yml", "/path/to/config.yml"),
({"key": "value"}, '{"key": "value"}'),
],
)
def test_client_utils_start_plot_process(config, call_config):
with mock.patch("bec_widgets.cli.client_utils.subprocess.Popen") as mock_popen:
_start_plot_process("gui_id", BECFigure, config)
command = ["bec-gui-server", "--id", "gui_id", "--gui_class", "BECFigure"]
if call_config:
command.extend(["--config", call_config])
mock_popen.assert_called_once_with(
command,
text=True,
start_new_session=True,
stdout=mock.ANY,
stderr=mock.ANY,
env=mock.ANY,
)
def test_client_utils_passes_client_config_to_server(bec_dispatcher):
"""
Test that the client config is passed to the server. This ensures that
changes to the client config (either through config files or plugins) are
reflected in the server.
"""
mixin = BECGuiClientMixin()
mixin._client = bec_dispatcher.client
mixin._gui_id = "gui_id"
mixin.gui_is_alive = mock.MagicMock()
mixin.gui_is_alive.side_effect = [True]
with mock.patch("bec_widgets.cli.client_utils._start_plot_process") as mock_start_plot:
with mock.patch.object(mixin, "_start_update_script") as mock_start_update:
mock_start_plot.return_value = [mock.MagicMock(), mock.MagicMock()]
mixin.show()
mock_start_plot.assert_called_once_with(
"gui_id", BECGuiClientMixin, mixin._client._service_config.config
)
mock_start_update.assert_called_once()

View File

@ -0,0 +1,42 @@
from unittest import mock
import pytest
from bec_lib.service_config import ServiceConfig
from bec_widgets.cli.server import _start_server
from bec_widgets.widgets.figure import BECFigure
@pytest.fixture
def mocked_cli_server():
with mock.patch("bec_widgets.cli.server.BECWidgetsCLIServer") as mock_server:
with mock.patch("bec_widgets.cli.server.ServiceConfig") as mock_config:
with mock.patch("bec_lib.logger.bec_logger.configure") as mock_logger:
yield mock_server, mock_config, mock_logger
def test_rpc_server_start_server_without_service_config(mocked_cli_server):
"""
Test that the server is started with the correct arguments.
"""
mock_server, mock_config, _ = mocked_cli_server
_start_server("gui_id", BECFigure, None)
mock_server.assert_called_once_with(gui_id="gui_id", config=mock_config(), gui_class=BECFigure)
@pytest.mark.parametrize(
"config, call_config",
[
("/path/to/config.yml", {"config_path": "/path/to/config.yml"}),
({"key": "value"}, {"config": {"key": "value"}}),
],
)
def test_rpc_server_start_server_with_service_config(mocked_cli_server, config, call_config):
"""
Test that the server is started with the correct arguments.
"""
mock_server, mock_config, _ = mocked_cli_server
config = mock_config(**call_config)
_start_server("gui_id", BECFigure, config)
mock_server.assert_called_once_with(gui_id="gui_id", config=config, gui_class=BECFigure)