From c0356b94dbe169bd192d3d6ecaa08a782b947e3a Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Mon, 22 Apr 2024 21:31:12 +0200 Subject: [PATCH] refactor(rpc/server): rpc server can accept BEC container class as a argument; the widget container of BECFigure container property s added to BECFigure; isort applied --- bec_widgets/cli/client.py | 11 +++- bec_widgets/cli/server.py | 46 ++++++++++++---- .../jupyter_console/jupyter_console_window.py | 4 +- bec_widgets/widgets/__init__.py | 1 - bec_widgets/widgets/dock/__init__.py | 1 - bec_widgets/widgets/dock/dock.py | 50 ++++++++++++++++++ bec_widgets/widgets/dock/dock_area.py | 52 +++---------------- bec_widgets/widgets/figure/figure.py | 11 +++- tests/end-2-end/test_bec_figure_rpc.py | 10 ++-- 9 files changed, 117 insertions(+), 69 deletions(-) create mode 100644 bec_widgets/widgets/dock/dock.py diff --git a/bec_widgets/cli/client.py b/bec_widgets/cli/client.py index d714228c..db446b4f 100644 --- a/bec_widgets/cli/client.py +++ b/bec_widgets/cli/client.py @@ -248,11 +248,11 @@ class BECWaveform(RPCBase): """ @rpc_call - def apply_config(self, config: "dict | WidgetConfig", replot_last_scan: "bool" = False): + def apply_config(self, config: "dict | SubplotConfig", replot_last_scan: "bool" = False): """ Apply the configuration to the 1D waveform widget. Args: - config(dict|WidgetConfig): Configuration settings. + config(dict|SubplotConfig): Configuration settings. replot_last_scan(bool, optional): If True, replot the last scan. Defaults to False. """ @@ -614,6 +614,13 @@ class BECFigure(RPCBase, BECFigureClientMixin): Clear all widgets from the figure and reset to default state """ + @property + @rpc_call + def containers(self) -> "dict": + """ + None + """ + class BECCurve(RPCBase): @property diff --git a/bec_widgets/cli/server.py b/bec_widgets/cli/server.py index d31af688..17c7b3b5 100644 --- a/bec_widgets/cli/server.py +++ b/bec_widgets/cli/server.py @@ -1,10 +1,12 @@ import inspect +from typing import Literal from bec_lib import MessageEndpoints, messages from qtpy.QtCore import QTimer from bec_widgets.utils import BECDispatcher from bec_widgets.utils.bec_connector import BECConnector +from bec_widgets.widgets.dock.dock_area import BECDockArea from bec_widgets.widgets.figure import BECFigure from bec_widgets.widgets.plots import BECCurve, BECImageShow, BECWaveform @@ -12,12 +14,18 @@ from bec_widgets.widgets.plots import BECCurve, BECImageShow, BECWaveform class BECWidgetsCLIServer: WIDGETS = [BECWaveform, BECFigure, BECCurve, BECImageShow] - def __init__(self, gui_id: str = None, dispatcher: BECDispatcher = None, client=None) -> None: + def __init__( + self, + gui_id: str = None, + dispatcher: BECDispatcher = None, + client=None, + gui_class: BECFigure | BECDockArea = BECFigure, + ) -> None: self.dispatcher = BECDispatcher() if dispatcher is None else dispatcher self.client = self.dispatcher.client if client is None else client self.client.start() self.gui_id = gui_id - self.fig = BECFigure(gui_id=self.gui_id) + self.gui = gui_class(gui_id=self.gui_id) self.dispatcher.connect_slot( self.on_rpc_update, MessageEndpoints.gui_instructions(self.gui_id) @@ -53,14 +61,14 @@ class BECWidgetsCLIServer: def get_object_from_config(self, config: dict): gui_id = config.get("gui_id") # check if the object is the figure - if gui_id == self.fig.gui_id: - return self.fig + if gui_id == self.gui.gui_id: + return self.gui # check if the object is a widget - if gui_id in self.fig._widgets: - obj = self.fig._widgets[config["gui_id"]] + if gui_id in self.gui.containers: + obj = self.gui.containers[config["gui_id"]] return obj - if self.fig._widgets: - for widget in self.fig._widgets.values(): + if self.gui.containers: + for widget in self.gui.containers.values(): item = widget.find_widget_by_id(gui_id) if item: return item @@ -123,13 +131,29 @@ if __name__ == "__main__": # pragma: no cover parser = argparse.ArgumentParser(description="BEC Widgets CLI Server") parser.add_argument("--id", type=str, help="The id of the server") + parser.add_argument( + "--gui_class", + type=str, + help="Name of the gui class to be rendered. Possible values: \n- BECFigure\n- BECDockArea", + ) args = parser.parse_args() - server = BECWidgetsCLIServer(gui_id=args.id) - # server = BECWidgetsCLIServer(gui_id="test") + if args.gui_class == "BECFigure": + gui_class = BECFigure + elif args.gui_class == "BECDockArea": + gui_class = BECDockArea + else: + print( + "Please specify a valid gui_class to run. Use -h for help." + "\n Starting with default gui_class BECFigure." + ) + gui_class = BECFigure - fig = server.fig + # server = BECWidgetsCLIServer(gui_id=args.id, gui_class=gui_class) + server = BECWidgetsCLIServer(gui_id="test", gui_class=gui_class) + + fig = server.gui win.setCentralWidget(fig) win.show() diff --git a/bec_widgets/examples/jupyter_console/jupyter_console_window.py b/bec_widgets/examples/jupyter_console/jupyter_console_window.py index c64c1e0c..de9f2ea5 100644 --- a/bec_widgets/examples/jupyter_console/jupyter_console_window.py +++ b/bec_widgets/examples/jupyter_console/jupyter_console_window.py @@ -5,13 +5,13 @@ import pyqtgraph as pg from PyQt6.QtCore import QSize from PyQt6.QtGui import QIcon from PyQt6.QtWidgets import QMainWindow -from pyqtgraph.Qt import uic, QtWidgets +from pyqtgraph.Qt import QtWidgets, uic from qtconsole.inprocess import QtInProcessKernelManager from qtconsole.rich_jupyter_widget import RichJupyterWidget from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget from bec_widgets.utils import BECDispatcher -from bec_widgets.widgets import BECFigure, BECDockArea +from bec_widgets.widgets import BECDockArea, BECFigure class JupyterConsoleWidget(RichJupyterWidget): # pragma: no cover: diff --git a/bec_widgets/widgets/__init__.py b/bec_widgets/widgets/__init__.py index ff683d61..e66c1f8c 100644 --- a/bec_widgets/widgets/__init__.py +++ b/bec_widgets/widgets/__init__.py @@ -11,4 +11,3 @@ from .motor_control import ( from .motor_map import MotorMap from .plots import BECCurve, BECMotorMap, BECWaveform from .scan_control import ScanControl -from .dock import BECDockArea, BECDock diff --git a/bec_widgets/widgets/dock/__init__.py b/bec_widgets/widgets/dock/__init__.py index 0579eee6..e69de29b 100644 --- a/bec_widgets/widgets/dock/__init__.py +++ b/bec_widgets/widgets/dock/__init__.py @@ -1 +0,0 @@ -from .dock_area import BECDockArea, BECDock diff --git a/bec_widgets/widgets/dock/dock.py b/bec_widgets/widgets/dock/dock.py new file mode 100644 index 00000000..01506576 --- /dev/null +++ b/bec_widgets/widgets/dock/dock.py @@ -0,0 +1,50 @@ +from typing import Literal, Optional + +from pydantic import Field +from pyqtgraph.dockarea import Dock +from qtpy.QtWidgets import QWidget + +from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils + + +class DockConfig(ConnectionConfig): + widgets: dict[str, ConnectionConfig] = Field({}, description="The widgets in the dock.") + position: Literal["bottom", "top", "left", "right", "above", "below"] = Field( + "bottom", description="The position of the dock." + ) + parent_dock_area: Optional[str] = Field( + None, description="The GUI ID of parent dock area of the dock." + ) + + +class BECDock(BECConnector, Dock): + def __init__( + self, + parent: Optional[QWidget] = None, + parent_dock_area: Optional["BECDockArea"] = None, + config: Optional[ + DockConfig + ] = None, # TODO ATM connection config -> will be changed when I will know what I want to use there + name: Optional[str] = None, + client=None, + gui_id: Optional[str] = None, + **kwargs, + ) -> None: + if config is None: + config = DockConfig( + widget_class=self.__class__.__name__, parent_dock_area=parent_dock_area.gui_id + ) + else: + if isinstance(config, dict): + config = DockConfig(**config) + self.config = config + super().__init__(client=client, config=config, gui_id=gui_id) + Dock.__init__(self, name=name, **kwargs) + + self.parent_dock_area = parent_dock_area + + self.sigClosed.connect(self._remove_from_dock_area) + + def _remove_from_dock_area(self): + """Remove this dock from the DockArea it lives inside.""" + self.parent_dock_area.docks.pop(self.name()) diff --git a/bec_widgets/widgets/dock/dock_area.py b/bec_widgets/widgets/dock/dock_area.py index d5033c39..672f7b2e 100644 --- a/bec_widgets/widgets/dock/dock_area.py +++ b/bec_widgets/widgets/dock/dock_area.py @@ -1,62 +1,22 @@ from collections import defaultdict -from typing import Optional, Literal +from typing import Literal, Optional import pyqtgraph as pg -from qtpy.QtWidgets import QWidget from pydantic import Field -from pyqtgraph.dockarea.DockArea import DockArea, Dock +from pyqtgraph.dockarea.DockArea import Dock, DockArea +from qtpy.QtWidgets import QWidget + from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils -from bec_widgets.widgets import BECWaveform, BECFigure, BECMotorMap +from bec_widgets.widgets import BECFigure, BECMotorMap, BECWaveform from bec_widgets.widgets.plots import BECImageShow - -class DockConfig(ConnectionConfig): - widgets: dict[str, ConnectionConfig] = Field({}, description="The widgets in the dock.") - position: Literal["bottom", "top", "left", "right", "above", "below"] = Field( - "bottom", description="The position of the dock." - ) - parent_dock_area: Optional[str] = Field( - None, description="The GUI ID of parent dock area of the dock." - ) +from .dock import BECDock, DockConfig class DockAreaConfig(ConnectionConfig): docks: dict[str, DockConfig] = Field({}, description="The docks in the dock area.") -class BECDock(BECConnector, Dock): - def __init__( - self, - parent: Optional[QWidget] = None, - parent_dock_area: Optional["BECDockArea"] = None, - config: Optional[ - DockConfig - ] = None, # TODO ATM connection config -> will be changed when I will know what I want to use there - name: Optional[str] = None, - client=None, - gui_id: Optional[str] = None, - **kwargs, - ) -> None: - if config is None: - config = DockConfig( - widget_class=self.__class__.__name__, parent_dock_area=parent_dock_area.gui_id - ) - else: - if isinstance(config, dict): - config = DockConfig(**config) - self.config = config - super().__init__(client=client, config=config, gui_id=gui_id) - Dock.__init__(self, name=name, **kwargs) - - self.parent_dock_area = parent_dock_area - - self.sigClosed.connect(self._remove_from_dock_area) - - def _remove_from_dock_area(self): - """Remove this dock from the DockArea it lives inside.""" - self.parent_dock_area.docks.pop(self.name()) - - class BECDockArea(BECConnector, DockArea): USER_ACCESS = ["figure", "plot", "image", "motor_map", "add_dock", "remove_dock_by_id", "clear"] diff --git a/bec_widgets/widgets/figure/figure.py b/bec_widgets/widgets/figure/figure.py index b480e05a..bba90c69 100644 --- a/bec_widgets/widgets/figure/figure.py +++ b/bec_widgets/widgets/figure/figure.py @@ -20,8 +20,8 @@ from bec_widgets.widgets.plots import ( BECMotorMap, BECPlotBase, BECWaveform, - Waveform1DConfig, SubplotConfig, + Waveform1DConfig, ) from bec_widgets.widgets.plots.image import ImageConfig from bec_widgets.widgets.plots.motor_map import MotorMapConfig @@ -110,6 +110,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget): "change_layout", "change_theme", "clear_all", + "containers", ] clean_signal = pyqtSignal() @@ -160,6 +161,14 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget): def widgets(self, value: dict): self._widgets = value + @property + def containers(self) -> dict: + return self._widgets + + @containers.setter + def containers(self, value: dict): + self._widgets = value + def add_plot( self, x_name: str = None, diff --git a/tests/end-2-end/test_bec_figure_rpc.py b/tests/end-2-end/test_bec_figure_rpc.py index 431b3877..e490ef24 100644 --- a/tests/end-2-end/test_bec_figure_rpc.py +++ b/tests/end-2-end/test_bec_figure_rpc.py @@ -11,8 +11,8 @@ from bec_widgets.utils import BECDispatcher def rpc_server(qtbot, bec_client_lib, threads_check): dispatcher = BECDispatcher(client=bec_client_lib) # Has to init singleton with fixture client server = BECWidgetsCLIServer(gui_id="id_test") - qtbot.addWidget(server.fig) - qtbot.waitExposed(server.fig) + qtbot.addWidget(server.gui) + qtbot.waitExposed(server.gui) qtbot.wait(1000) # 1s long to wait until gui is ready yield server dispatcher.disconnect_all() @@ -23,7 +23,7 @@ def rpc_server(qtbot, bec_client_lib, threads_check): def test_rpc_waveform1d_custom_curve(rpc_server, qtbot): fig = BECFigure(rpc_server.gui_id) - fig_server = rpc_server.fig + fig_server = rpc_server.gui ax = fig.add_plot() curve = ax.add_curve_custom([1, 2, 3], [1, 2, 3]) @@ -37,7 +37,7 @@ def test_rpc_waveform1d_custom_curve(rpc_server, qtbot): def test_rpc_plotting_shortcuts_init_configs(rpc_server, qtbot): fig = BECFigure(rpc_server.gui_id) - fig_server = rpc_server.fig + fig_server = rpc_server.gui plt = fig.plot("samx", "bpm4i") im = fig.image("eiger") @@ -151,7 +151,7 @@ def test_rpc_image(rpc_server, qtbot): def test_rpc_motor_map(rpc_server, qtbot): fig = BECFigure(rpc_server.gui_id) - fig_server = rpc_server.fig + fig_server = rpc_server.gui motor_map = fig.motor_map("samx", "samy")