diff --git a/bec_widgets/cli/__init__.py b/bec_widgets/cli/__init__.py index 421945bc..e69de29b 100644 --- a/bec_widgets/cli/__init__.py +++ b/bec_widgets/cli/__init__.py @@ -1 +0,0 @@ -from .client import * diff --git a/bec_widgets/cli/client.py b/bec_widgets/cli/client.py index c2430f56..60ba28e2 100644 --- a/bec_widgets/cli/client.py +++ b/bec_widgets/cli/client.py @@ -3,9 +3,15 @@ from __future__ import annotations import enum -from typing import Literal, Optional, overload +import inspect +from typing import Literal, Optional + +from bec_lib.logger import bec_logger from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call +from bec_widgets.utils.bec_plugin_helper import get_all_plugin_widgets, get_plugin_client_module + +logger = bec_logger.logger # pylint: skip-file @@ -20,7 +26,6 @@ _Widgets = { "AbortButton": "AbortButton", "BECColorMapWidget": "BECColorMapWidget", "BECDockArea": "BECDockArea", - "BECMultiWaveformWidget": "BECMultiWaveformWidget", "BECProgressBar": "BECProgressBar", "BECQueue": "BECQueue", "BECStatusBox": "BECStatusBox", @@ -34,6 +39,7 @@ _Widgets = { "LogPanel": "LogPanel", "Minesweeper": "Minesweeper", "MotorMap": "MotorMap", + "MultiWaveform": "MultiWaveform", "PositionIndicator": "PositionIndicator", "PositionerBox": "PositionerBox", "PositionerBox2D": "PositionerBox2D", @@ -52,7 +58,31 @@ _Widgets = { "Waveform": "Waveform", "WebsiteWidget": "WebsiteWidget", } -Widgets = _WidgetsEnumType("Widgets", _Widgets) + + +_plugin_widgets = get_all_plugin_widgets() +plugin_client = get_plugin_client_module() +Widgets = _WidgetsEnumType("Widgets", {name: name for name in _plugin_widgets} | _Widgets) + +if (_overlap := _Widgets.keys() & _plugin_widgets.keys()) != set(): + for _widget in _overlap: + logger.warning( + f"Detected duplicate widget {_widget} in plugin repo file: {inspect.getfile(_plugin_widgets[_widget])} !" + ) +for plugin_name, plugin_class in inspect.getmembers(plugin_client, inspect.isclass): + if issubclass(plugin_class, RPCBase) and plugin_class is not RPCBase: + if plugin_name in globals(): + conflicting_file = ( + inspect.getfile(_plugin_widgets[plugin_name]) + if plugin_name in _plugin_widgets + else f"{plugin_client}" + ) + logger.warning( + f"Plugin widget {plugin_name} from {conflicting_file} conflicts with a built-in class!" + ) + continue + if plugin_name not in _overlap: + globals()[plugin_name] = plugin_class class AbortButton(RPCBase): diff --git a/bec_widgets/cli/client_utils.py b/bec_widgets/cli/client_utils.py index 732a0669..81aee9fb 100644 --- a/bec_widgets/cli/client_utils.py +++ b/bec_widgets/cli/client_utils.py @@ -533,7 +533,12 @@ class BECGuiClient(RPCBase): """ name = state["name"] gui_id = state["gui_id"] - widget_class = getattr(client, state["widget_class"]) + try: + widget_class = getattr(client, state["widget_class"]) + except AttributeError as e: + raise AttributeError( + f"Failed to find user widget {state['widget_class']} in the client - did you run bw-generate-cli to generate the plugin files?" + ) from e obj = self._ipython_registry.get(gui_id) if obj is None: widget = widget_class(gui_id=gui_id, name=name, parent=parent) diff --git a/bec_widgets/cli/generate_cli.py b/bec_widgets/cli/generate_cli.py index 7123dc0d..9db4b12b 100644 --- a/bec_widgets/cli/generate_cli.py +++ b/bec_widgets/cli/generate_cli.py @@ -34,13 +34,25 @@ else: class ClientGenerator: - def __init__(self): - self.header = """# This file was automatically generated by generate_cli.py\n + def __init__(self, base=False): + self._base = base + base_imports = ( + """import enum +import inspect +from typing import Literal, Optional +""" + if self._base + else "\n" + ) + self.header = f"""# This file was automatically generated by generate_cli.py\n from __future__ import annotations -import enum -from typing import Literal, Optional, overload +{base_imports} +from bec_lib.logger import bec_logger from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call +{"from bec_widgets.utils.bec_plugin_helper import get_all_plugin_widgets, get_plugin_client_module" if self._base else ""} + +logger = bec_logger.logger # pylint: skip-file""" @@ -67,6 +79,7 @@ from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call self.write_client_enum(rpc_top_level_classes) for cls in connector_classes: + logger.debug(f"generating RPC client class for {cls.__name__}") self.content += "\n\n" self.generate_content_for_class(cls) @@ -74,10 +87,14 @@ from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call """ Write the client enum to the content. """ - self.content += """ + if self._base: + self.content += """ class _WidgetsEnumType(str, enum.Enum): \"\"\" Enum for the available widgets, to be generated programatically \"\"\" ... + """ + + self.content += """ _Widgets = { """ @@ -85,8 +102,33 @@ _Widgets = { self.content += f'"{cls.__name__}": "{cls.__name__}",\n ' self.content += """} -Widgets = _WidgetsEnumType("Widgets", _Widgets) - """ +""" + if self._base: + self.content += """ + +_plugin_widgets = get_all_plugin_widgets() +plugin_client = get_plugin_client_module() +Widgets = _WidgetsEnumType("Widgets", {name: name for name in _plugin_widgets} | _Widgets) + +if (_overlap := _Widgets.keys() & _plugin_widgets.keys()) != set(): + for _widget in _overlap: + logger.warning(f"Detected duplicate widget {_widget} in plugin repo file: {inspect.getfile(_plugin_widgets[_widget])} !") +for plugin_name, plugin_class in inspect.getmembers(plugin_client, inspect.isclass): + if issubclass(plugin_class, RPCBase) and plugin_class is not RPCBase: + if plugin_name in globals(): + conflicting_file = ( + inspect.getfile(_plugin_widgets[plugin_name]) + if plugin_name in _plugin_widgets + else f"{plugin_client}" + ) + logger.warning( + f"Plugin widget {plugin_name} from {conflicting_file} conflicts with a built-in class!" + ) + continue + if plugin_name not in _overlap: + globals()[plugin_name] = plugin_class + +""" def generate_content_for_class(self, cls): """ @@ -199,38 +241,59 @@ def main(): parser = argparse.ArgumentParser(description="Auto-generate the client for RPC widgets") parser.add_argument( - "--module-name", + "--target", action="store", type=str, - default="bec_widgets", - help="Which module to generate plugin files for (default: bec_widgets, example: my_plugin_repo.bec_widgets)", + help="Which package to generate plugin files for. Should be installed in the local environment (example: my_plugin_repo)", ) args = parser.parse_args() + if args.target is None: + logger.error( + "You must provide a target - for safety, the default of running this on bec_widgets core has been removed. To generate the client for bec_widgets, run `bw-generate-cli --target bec_widgets`" + ) + return logger.info(f"BEC Widget code generation tool started with args: {args}") + client_subdir = "cli" if args.target == "bec_widgets" else "widgets" + module_name = "bec_widgets" if args.target == "bec_widgets" else f"{args.target}.bec_widgets" + try: - module = importlib.import_module(args.module_name) + module = importlib.import_module(module_name) assert module.__file__ is not None module_file = Path(module.__file__) module_dir = module_file.parent if module_file.is_file() else module_file except Exception as e: - logger.error(f"Failed to load module {args.module_name} for code generation: {e}") + logger.error(f"Failed to load module {module_name} for code generation: {e}") return - client_path = module_dir / "client.py" + client_path = module_dir / client_subdir / "client.py" - rpc_classes = get_custom_classes(args.module_name) + rpc_classes = get_custom_classes(module_name) logger.info(f"Obtained classes with RPC objects: {rpc_classes!r}") - generator = ClientGenerator(base=args.module_name == "bec_widgets") - logger.info(f"Generating client.py") + generator = ClientGenerator(base=module_name == "bec_widgets") + logger.info(f"Generating client file at {client_path}") generator.generate_client(rpc_classes) generator.write(str(client_path)) + if module_name != "bec_widgets": + non_overwrite_classes = list(clsinfo.name for clsinfo in get_custom_classes("bec_widgets")) + logger.info( + f"Not writing plugins which would conflict with builtin classes: {non_overwrite_classes}" + ) + else: + non_overwrite_classes = [] + for cls in rpc_classes.plugins: - logger.info(f"Writing plugins for: {cls}") + logger.info(f"Writing bec-designer plugin files for {cls.__name__}...") + + if cls.__name__ in non_overwrite_classes: + logger.error( + f"Not writing plugin files for {cls.__name__} because a built-in widget with that name exists" + ) + plugin = DesignerPluginGenerator(cls) if not hasattr(plugin, "info"): continue @@ -239,7 +302,9 @@ def main(): return os.path.exists(os.path.join(plugin.info.base_path, file)) if any(_exists(file) for file in plugin_filenames(plugin.info.plugin_name_snake)): - logger.debug(f"Skipping {plugin.info.plugin_name_snake} - a file already exists.") + logger.debug( + f"Skipping generation of extra plugin files for {plugin.info.plugin_name_snake} - at least one file out of 'plugin.py', 'pyproject', and 'register_{plugin.info.plugin_name_snake}.py' already exists." + ) continue plugin.run() diff --git a/bec_widgets/cli/rpc/rpc_base.py b/bec_widgets/cli/rpc/rpc_base.py index 601a787d..75cc2a5b 100644 --- a/bec_widgets/cli/rpc/rpc_base.py +++ b/bec_widgets/cli/rpc/rpc_base.py @@ -10,14 +10,14 @@ from bec_lib.client import BECClient from bec_lib.endpoints import MessageEndpoints from bec_lib.utils.import_utils import lazy_import, lazy_import_from -import bec_widgets.cli.client as client - if TYPE_CHECKING: # pragma: no cover from bec_lib import messages from bec_lib.connector import MessageObject + + import bec_widgets.cli.client as client else: + client = lazy_import("bec_widgets.cli.client") # avoid circular import messages = lazy_import("bec_lib.messages") - # from bec_lib.connector import MessageObject MessageObject = lazy_import_from("bec_lib.connector", ("MessageObject",)) # pylint: disable=protected-access diff --git a/bec_widgets/cli/rpc/rpc_widget_handler.py b/bec_widgets/cli/rpc/rpc_widget_handler.py index ace92ed5..ed73e2ae 100644 --- a/bec_widgets/cli/rpc/rpc_widget_handler.py +++ b/bec_widgets/cli/rpc/rpc_widget_handler.py @@ -1,9 +1,9 @@ from __future__ import annotations -from typing import Any - from bec_widgets.cli.client_utils import IGNORE_WIDGETS +from bec_widgets.utils.bec_plugin_helper import get_all_plugin_widgets from bec_widgets.utils.bec_widget import BECWidget +from bec_widgets.utils.plugin_utils import get_custom_classes class RPCWidgetHandler: @@ -31,10 +31,8 @@ class RPCWidgetHandler: Returns: None """ - from bec_widgets.utils.plugin_utils import get_custom_classes - clss = get_custom_classes("bec_widgets") - self._widget_classes = { + self._widget_classes = get_all_plugin_widgets() | { cls.__name__: cls for cls in clss.widgets if cls.__name__ not in IGNORE_WIDGETS } diff --git a/bec_widgets/cli/server.py b/bec_widgets/cli/server.py index 1da76351..aadecb43 100644 --- a/bec_widgets/cli/server.py +++ b/bec_widgets/cli/server.py @@ -4,6 +4,7 @@ import functools import json import signal import sys +import traceback import types from contextlib import contextmanager, redirect_stderr, redirect_stdout from typing import Union @@ -95,7 +96,7 @@ class BECWidgetsCLIServer: kwargs = msg["parameter"].get("kwargs", {}) res = self.run_rpc(obj, method, args, kwargs) except Exception as e: - logger.error(f"Error while executing RPC instruction: {e}") + logger.error(f"Error while executing RPC instruction: {traceback.format_exc()}") self.send_response(request_id, False, {"error": str(e)}) else: logger.debug(f"RPC instruction executed successfully: {res}") diff --git a/bec_widgets/client.py b/bec_widgets/client.py deleted file mode 100644 index 3a0bad24..00000000 --- a/bec_widgets/client.py +++ /dev/null @@ -1,3529 +0,0 @@ -# This file was automatically generated by generate_cli.py - -from __future__ import annotations - -import enum -import inspect -from typing import Literal, Optional - -from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call - -# pylint: skip-file - - -class _WidgetsEnumType(str, enum.Enum): - """Enum for the available widgets, to be generated programatically""" - - ... - - -_Widgets = { - "AbortButton": "AbortButton", - "BECColorMapWidget": "BECColorMapWidget", - "BECDockArea": "BECDockArea", - "BECProgressBar": "BECProgressBar", - "BECQueue": "BECQueue", - "BECStatusBox": "BECStatusBox", - "DapComboBox": "DapComboBox", - "DarkModeButton": "DarkModeButton", - "DeviceBrowser": "DeviceBrowser", - "DeviceComboBox": "DeviceComboBox", - "DeviceLineEdit": "DeviceLineEdit", - "Image": "Image", - "LMFitDialog": "LMFitDialog", - "LogPanel": "LogPanel", - "Minesweeper": "Minesweeper", - "MotorMap": "MotorMap", - "MultiWaveform": "MultiWaveform", - "PositionIndicator": "PositionIndicator", - "PositionerBox": "PositionerBox", - "PositionerBox2D": "PositionerBox2D", - "PositionerControlLine": "PositionerControlLine", - "ResetButton": "ResetButton", - "ResumeButton": "ResumeButton", - "RingProgressBar": "RingProgressBar", - "ScanControl": "ScanControl", - "ScatterWaveform": "ScatterWaveform", - "SignalComboBox": "SignalComboBox", - "SignalLineEdit": "SignalLineEdit", - "StopButton": "StopButton", - "TextBox": "TextBox", - "VSCodeEditor": "VSCodeEditor", - "Waveform": "Waveform", - "WebsiteWidget": "WebsiteWidget", -} -Widgets = _WidgetsEnumType("Widgets", _Widgets) - -_plugin_widgets = get_all_plugin_widgets() - -Widgets = _WidgetsEnumType("Widgets", _Widgets | {name: name for name in _plugin_widgets}) -plugin_client = get_plugin_client_module() - -for plugin_name, plugin_class in inspect.getmembers(plugin_client, inspect.isclass): - if issubclass(plugin_class, RPCBase): - globals()[plugin_name] = plugin_class - - -class AbortButton(RPCBase): - """A button that abort the scan.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class BECColorMapWidget(RPCBase): - @property - @rpc_call - def colormap(self): - """ - Get the current colormap name. - """ - - -class BECDock(RPCBase): - @property - @rpc_call - def _config_dict(self) -> "dict": - """ - Get the configuration of the widget. - - Returns: - dict: The configuration of the widget. - """ - - @property - @rpc_call - def element_list(self) -> "list[BECWidget]": - """ - Get the widgets in the dock. - - Returns: - widgets(list): The widgets in the dock. - """ - - @property - @rpc_call - def elements(self) -> "dict[str, BECWidget]": - """ - Get the widgets in the dock. - - Returns: - widgets(dict): The widgets in the dock. - """ - - @rpc_call - def new( - self, - widget: "BECWidget | str", - name: "str | None" = None, - row: "int | None" = None, - col: "int" = 0, - rowspan: "int" = 1, - colspan: "int" = 1, - shift: "Literal['down', 'up', 'left', 'right']" = "down", - ) -> "BECWidget": - """ - Add a widget to the dock. - - Args: - widget(QWidget): The widget to add. It can not be BECDock or BECDockArea. - name(str): The name of the widget. - row(int): The row to add the widget to. If None, the widget will be added to the next available row. - col(int): The column to add the widget to. - rowspan(int): The number of rows the widget should span. - colspan(int): The number of columns the widget should span. - shift(Literal["down", "up", "left", "right"]): The direction to shift the widgets if the position is occupied. - """ - - @rpc_call - def show(self): - """ - Show the dock. - """ - - @rpc_call - def hide(self): - """ - Hide the dock. - """ - - @rpc_call - def show_title_bar(self): - """ - Hide the title bar of the dock. - """ - - @rpc_call - def set_title(self, title: "str"): - """ - Set the title of the dock. - - Args: - title(str): The title of the dock. - """ - - @rpc_call - def hide_title_bar(self): - """ - Hide the title bar of the dock. - """ - - @rpc_call - def available_widgets(self) -> "list": - """ - List all widgets that can be added to the dock. - - Returns: - list: The list of eligible widgets. - """ - - @rpc_call - def delete(self, widget_name: "str") -> "None": - """ - Remove a widget from the dock. - - Args: - widget_name(str): Delete the widget with the given name. - """ - - @rpc_call - def delete_all(self): - """ - Remove all widgets from the dock. - """ - - @rpc_call - def remove(self): - """ - Remove the dock from the parent dock area. - """ - - @rpc_call - def attach(self): - """ - Attach the dock to the parent dock area. - """ - - @rpc_call - def detach(self): - """ - Detach the dock from the parent dock area. - """ - - -class BECDockArea(RPCBase): - @property - @rpc_call - def _rpc_id(self) -> "str": - """ - Get the RPC ID of the widget. - """ - - @property - @rpc_call - def _config_dict(self) -> "dict": - """ - Get the configuration of the widget. - - Returns: - dict: The configuration of the widget. - """ - - @rpc_call - def _get_all_rpc(self) -> "dict": - """ - Get all registered RPC objects. - """ - - @rpc_call - def new( - self, - name: "str | None" = None, - widget: "str | QWidget | None" = None, - widget_name: "str | None" = None, - position: "Literal['bottom', 'top', 'left', 'right', 'above', 'below']" = "bottom", - relative_to: "BECDock | None" = None, - closable: "bool" = True, - floating: "bool" = False, - row: "int | None" = None, - col: "int" = 0, - rowspan: "int" = 1, - colspan: "int" = 1, - ) -> "BECDock": - """ - Add a dock to the dock area. Dock has QGridLayout as layout manager by default. - - Args: - name(str): The name of the dock to be displayed and for further references. Has to be unique. - widget(str|QWidget|None): The widget to be added to the dock. While using RPC, only BEC RPC widgets from RPCWidgetHandler are allowed. - position(Literal["bottom", "top", "left", "right", "above", "below"]): The position of the dock. - relative_to(BECDock): The dock to which the new dock should be added relative to. - closable(bool): Whether the dock is closable. - floating(bool): Whether the dock is detached after creating. - row(int): The row of the added widget. - col(int): The column of the added widget. - rowspan(int): The rowspan of the added widget. - colspan(int): The colspan of the added widget. - - Returns: - BECDock: The created dock. - """ - - @rpc_call - def show(self): - """b - Show all windows including floating docks. - """ - - @rpc_call - def hide(self): - """ - Hide all windows including floating docks. - """ - - @property - @rpc_call - def panels(self) -> "dict[str, BECDock]": - """ - Get the docks in the dock area. - Returns: - dock_dict(dict): The docks in the dock area. - """ - - @property - @rpc_call - def panel_list(self) -> "list[BECDock]": - """ - Get the docks in the dock area. - - Returns: - list: The docks in the dock area. - """ - - @rpc_call - def delete(self, dock_name: "str"): - """ - Delete a dock by name. - - Args: - dock_name(str): The name of the dock to delete. - """ - - @rpc_call - def delete_all(self) -> "None": - """ - Delete all docks. - """ - - @rpc_call - def remove(self) -> "None": - """ - Remove the dock area. - """ - - @rpc_call - def detach_dock(self, dock_name: "str") -> "BECDock": - """ - Undock a dock from the dock area. - - Args: - dock_name(str): The dock to undock. - - Returns: - BECDock: The undocked dock. - """ - - @rpc_call - def attach_all(self): - """ - Return all floating docks to the dock area. - """ - - @property - @rpc_call - def selected_device(self) -> "str": - """ - None - """ - - @rpc_call - def save_state(self) -> "dict": - """ - Save the state of the dock area. - - Returns: - dict: The state of the dock area. - """ - - @rpc_call - def restore_state( - self, state: "dict" = None, missing: "Literal['ignore', 'error']" = "ignore", extra="bottom" - ): - """ - Restore the state of the dock area. If no state is provided, the last state is restored. - - Args: - state(dict): The state to restore. - missing(Literal['ignore','error']): What to do if a dock is missing. - extra(str): Extra docks that are in the dockarea but that are not mentioned in state will be added to the bottom of the dockarea, unless otherwise specified by the extra argument. - """ - - -class BECMainWindow(RPCBase): - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class BECProgressBar(RPCBase): - """A custom progress bar with smooth transitions. The displayed text can be customized using a template.""" - - @rpc_call - def set_value(self, value): - """ - Set the value of the progress bar. - - Args: - value (float): The value to set. - """ - - @rpc_call - def set_maximum(self, maximum: float): - """ - Set the maximum value of the progress bar. - - Args: - maximum (float): The maximum value. - """ - - @rpc_call - def set_minimum(self, minimum: float): - """ - Set the minimum value of the progress bar. - - Args: - minimum (float): The minimum value. - """ - - @property - @rpc_call - def label_template(self): - """ - The template for the center label. Use $value, $maximum, and $percentage to insert the values. - - Examples: - >>> progressbar.label_template = "$value / $maximum - $percentage %" - >>> progressbar.label_template = "$value / $percentage %" - """ - - @label_template.setter - @rpc_call - def label_template(self): - """ - The template for the center label. Use $value, $maximum, and $percentage to insert the values. - - Examples: - >>> progressbar.label_template = "$value / $maximum - $percentage %" - >>> progressbar.label_template = "$value / $percentage %" - """ - - -class BECQueue(RPCBase): - """Widget to display the BEC queue.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class BECStatusBox(RPCBase): - """An autonomous widget to display the status of BEC services.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class Curve(RPCBase): - @rpc_call - def remove(self): - """ - Remove the curve from the plot. - """ - - @property - @rpc_call - def _rpc_id(self) -> "str": - """ - Get the RPC ID of the widget. - """ - - @property - @rpc_call - def _config_dict(self) -> "dict": - """ - Get the configuration of the widget. - - Returns: - dict: The configuration of the widget. - """ - - @rpc_call - def set(self, **kwargs): - """ - Set the properties of the curve. - - Args: - **kwargs: Keyword arguments for the properties to be set. - - Possible properties: - - color: str - - symbol: str - - symbol_color: str - - symbol_size: int - - pen_width: int - - pen_style: Literal["solid", "dash", "dot", "dashdot"] - """ - - @rpc_call - def set_data(self, x: "list | np.ndarray", y: "list | np.ndarray"): - """ - Set the data of the curve. - - Args: - x(list|np.ndarray): The x data. - y(list|np.ndarray): The y data. - - Raises: - ValueError: If the source is not custom. - """ - - @rpc_call - def set_color(self, color: "str", symbol_color: "str | None" = None): - """ - Change the color of the curve. - - Args: - color(str): Color of the curve. - symbol_color(str, optional): Color of the symbol. Defaults to None. - """ - - @rpc_call - def set_color_map_z(self, colormap: "str"): - """ - Set the colormap for the scatter plot z gradient. - - Args: - colormap(str): Colormap for the scatter plot. - """ - - @rpc_call - def set_symbol(self, symbol: "str"): - """ - Change the symbol of the curve. - - Args: - symbol(str): Symbol of the curve. - """ - - @rpc_call - def set_symbol_color(self, symbol_color: "str"): - """ - Change the symbol color of the curve. - - Args: - symbol_color(str): Color of the symbol. - """ - - @rpc_call - def set_symbol_size(self, symbol_size: "int"): - """ - Change the symbol size of the curve. - - Args: - symbol_size(int): Size of the symbol. - """ - - @rpc_call - def set_pen_width(self, pen_width: "int"): - """ - Change the pen width of the curve. - - Args: - pen_width(int): Width of the pen. - """ - - @rpc_call - def set_pen_style(self, pen_style: "Literal['solid', 'dash', 'dot', 'dashdot']"): - """ - Change the pen style of the curve. - - Args: - pen_style(Literal["solid", "dash", "dot", "dashdot"]): Style of the pen. - """ - - @rpc_call - def get_data(self) -> "tuple[np.ndarray, np.ndarray]": - """ - Get the data of the curve. - Returns: - tuple[np.ndarray,np.ndarray]: X and Y data of the curve. - """ - - @property - @rpc_call - def dap_params(self): - """ - Get the dap parameters. - """ - - @property - @rpc_call - def dap_summary(self): - """ - Get the dap summary. - """ - - @property - @rpc_call - def dap_oversample(self): - """ - Get the dap oversample. - """ - - @dap_oversample.setter - @rpc_call - def dap_oversample(self): - """ - Get the dap oversample. - """ - - -class DapComboBox(RPCBase): - """The DAPComboBox widget is an extension to the QComboBox with all avaialble DAP model from BEC.""" - - @rpc_call - def select_y_axis(self, y_axis: str): - """ - Slot to update the y axis. - - Args: - y_axis(str): Y axis. - """ - - @rpc_call - def select_x_axis(self, x_axis: str): - """ - Slot to update the x axis. - - Args: - x_axis(str): X axis. - """ - - @rpc_call - def select_fit_model(self, fit_name: str | None): - """ - Slot to update the fit model. - - Args: - default_device(str): Default device name. - """ - - -class DarkModeButton(RPCBase): - @rpc_call - def toggle_dark_mode(self) -> "None": - """ - Toggle the dark mode state. This will change the theme of the entire - application to dark or light mode. - """ - - -class DeviceBrowser(RPCBase): - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class DeviceComboBox(RPCBase): - """Combobox widget for device input with autocomplete for device names.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class DeviceInputBase(RPCBase): - """Mixin base class for device input widgets.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class DeviceLineEdit(RPCBase): - """Line edit widget for device input with autocomplete for device names.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class DeviceSignalInputBase(RPCBase): - """Mixin base class for device signal input widgets.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class Image(RPCBase): - @property - @rpc_call - def enable_toolbar(self) -> "bool": - """ - Show Toolbar. - """ - - @enable_toolbar.setter - @rpc_call - def enable_toolbar(self) -> "bool": - """ - Show Toolbar. - """ - - @property - @rpc_call - def enable_side_panel(self) -> "bool": - """ - Show Side Panel - """ - - @enable_side_panel.setter - @rpc_call - def enable_side_panel(self) -> "bool": - """ - Show Side Panel - """ - - @property - @rpc_call - def enable_fps_monitor(self) -> "bool": - """ - Enable the FPS monitor. - """ - - @enable_fps_monitor.setter - @rpc_call - def enable_fps_monitor(self) -> "bool": - """ - Enable the FPS monitor. - """ - - @rpc_call - def set(self, **kwargs): - """ - Set the properties of the plot widget. - - Args: - **kwargs: Keyword arguments for the properties to be set. - - Possible properties: - - title: str - - x_label: str - - y_label: str - - x_scale: Literal["linear", "log"] - - y_scale: Literal["linear", "log"] - - x_lim: tuple - - y_lim: tuple - - legend_label_size: int - """ - - @property - @rpc_call - def title(self) -> "str": - """ - Set title of the plot. - """ - - @title.setter - @rpc_call - def title(self) -> "str": - """ - Set title of the plot. - """ - - @property - @rpc_call - def x_label(self) -> "str": - """ - The set label for the x-axis. - """ - - @x_label.setter - @rpc_call - def x_label(self) -> "str": - """ - The set label for the x-axis. - """ - - @property - @rpc_call - def y_label(self) -> "str": - """ - The set label for the y-axis. - """ - - @y_label.setter - @rpc_call - def y_label(self) -> "str": - """ - The set label for the y-axis. - """ - - @property - @rpc_call - def x_limits(self) -> "QPointF": - """ - Get the x limits of the plot. - """ - - @x_limits.setter - @rpc_call - def x_limits(self) -> "QPointF": - """ - Get the x limits of the plot. - """ - - @property - @rpc_call - def y_limits(self) -> "QPointF": - """ - Get the y limits of the plot. - """ - - @y_limits.setter - @rpc_call - def y_limits(self) -> "QPointF": - """ - Get the y limits of the plot. - """ - - @property - @rpc_call - def x_grid(self) -> "bool": - """ - Show grid on the x-axis. - """ - - @x_grid.setter - @rpc_call - def x_grid(self) -> "bool": - """ - Show grid on the x-axis. - """ - - @property - @rpc_call - def y_grid(self) -> "bool": - """ - Show grid on the y-axis. - """ - - @y_grid.setter - @rpc_call - def y_grid(self) -> "bool": - """ - Show grid on the y-axis. - """ - - @property - @rpc_call - def inner_axes(self) -> "bool": - """ - Show inner axes of the plot widget. - """ - - @inner_axes.setter - @rpc_call - def inner_axes(self) -> "bool": - """ - Show inner axes of the plot widget. - """ - - @property - @rpc_call - def outer_axes(self) -> "bool": - """ - Show the outer axes of the plot widget. - """ - - @outer_axes.setter - @rpc_call - def outer_axes(self) -> "bool": - """ - Show the outer axes of the plot widget. - """ - - @property - @rpc_call - def auto_range_x(self) -> "bool": - """ - Set auto range for the x-axis. - """ - - @auto_range_x.setter - @rpc_call - def auto_range_x(self) -> "bool": - """ - Set auto range for the x-axis. - """ - - @property - @rpc_call - def auto_range_y(self) -> "bool": - """ - Set auto range for the y-axis. - """ - - @auto_range_y.setter - @rpc_call - def auto_range_y(self) -> "bool": - """ - Set auto range for the y-axis. - """ - - @property - @rpc_call - def x_log(self) -> "bool": - """ - Set X-axis to log scale if True, linear if False. - """ - - @x_log.setter - @rpc_call - def x_log(self) -> "bool": - """ - Set X-axis to log scale if True, linear if False. - """ - - @property - @rpc_call - def y_log(self) -> "bool": - """ - Set Y-axis to log scale if True, linear if False. - """ - - @y_log.setter - @rpc_call - def y_log(self) -> "bool": - """ - Set Y-axis to log scale if True, linear if False. - """ - - @property - @rpc_call - def legend_label_size(self) -> "int": - """ - The font size of the legend font. - """ - - @legend_label_size.setter - @rpc_call - def legend_label_size(self) -> "int": - """ - The font size of the legend font. - """ - - @property - @rpc_call - def color_map(self) -> "str": - """ - Set the color map of the image. - """ - - @color_map.setter - @rpc_call - def color_map(self) -> "str": - """ - Set the color map of the image. - """ - - @property - @rpc_call - def vrange(self) -> "tuple": - """ - Get the vrange of the image. - """ - - @vrange.setter - @rpc_call - def vrange(self) -> "tuple": - """ - Get the vrange of the image. - """ - - @property - @rpc_call - def v_min(self) -> "float": - """ - Get the minimum value of the v_range. - """ - - @v_min.setter - @rpc_call - def v_min(self) -> "float": - """ - Get the minimum value of the v_range. - """ - - @property - @rpc_call - def v_max(self) -> "float": - """ - Get the maximum value of the v_range. - """ - - @v_max.setter - @rpc_call - def v_max(self) -> "float": - """ - Get the maximum value of the v_range. - """ - - @property - @rpc_call - def lock_aspect_ratio(self) -> "bool": - """ - Whether the aspect ratio is locked. - """ - - @lock_aspect_ratio.setter - @rpc_call - def lock_aspect_ratio(self) -> "bool": - """ - Whether the aspect ratio is locked. - """ - - @property - @rpc_call - def autorange(self) -> "bool": - """ - Whether autorange is enabled. - """ - - @autorange.setter - @rpc_call - def autorange(self) -> "bool": - """ - Whether autorange is enabled. - """ - - @property - @rpc_call - def autorange_mode(self) -> "str": - """ - Autorange mode. - - Options: - - "max": Use the maximum value of the image for autoranging. - - "mean": Use the mean value of the image for autoranging. - """ - - @autorange_mode.setter - @rpc_call - def autorange_mode(self) -> "str": - """ - Autorange mode. - - Options: - - "max": Use the maximum value of the image for autoranging. - - "mean": Use the mean value of the image for autoranging. - """ - - @property - @rpc_call - def monitor(self) -> "str": - """ - The name of the monitor to use for the image. - """ - - @monitor.setter - @rpc_call - def monitor(self) -> "str": - """ - The name of the monitor to use for the image. - """ - - @rpc_call - def enable_colorbar( - self, - enabled: "bool", - style: "Literal['full', 'simple']" = "full", - vrange: "tuple[int, int] | None" = None, - ): - """ - Enable the colorbar and switch types of colorbars. - - Args: - enabled(bool): Whether to enable the colorbar. - style(Literal["full", "simple"]): The type of colorbar to enable. - vrange(tuple): The range of values to use for the colorbar. - """ - - @property - @rpc_call - def enable_simple_colorbar(self) -> "bool": - """ - Enable the simple colorbar. - """ - - @enable_simple_colorbar.setter - @rpc_call - def enable_simple_colorbar(self) -> "bool": - """ - Enable the simple colorbar. - """ - - @property - @rpc_call - def enable_full_colorbar(self) -> "bool": - """ - Enable the full colorbar. - """ - - @enable_full_colorbar.setter - @rpc_call - def enable_full_colorbar(self) -> "bool": - """ - Enable the full colorbar. - """ - - @property - @rpc_call - def fft(self) -> "bool": - """ - Whether FFT postprocessing is enabled. - """ - - @fft.setter - @rpc_call - def fft(self) -> "bool": - """ - Whether FFT postprocessing is enabled. - """ - - @property - @rpc_call - def log(self) -> "bool": - """ - Whether logarithmic scaling is applied. - """ - - @log.setter - @rpc_call - def log(self) -> "bool": - """ - Whether logarithmic scaling is applied. - """ - - @property - @rpc_call - def rotation(self) -> "int": - """ - The number of 90° rotations to apply. - """ - - @rotation.setter - @rpc_call - def rotation(self) -> "int": - """ - The number of 90° rotations to apply. - """ - - @property - @rpc_call - def transpose(self) -> "bool": - """ - Whether the image is transposed. - """ - - @transpose.setter - @rpc_call - def transpose(self) -> "bool": - """ - Whether the image is transposed. - """ - - @rpc_call - def image( - self, - monitor: "str | None" = None, - monitor_type: "Literal['auto', '1d', '2d']" = "auto", - color_map: "str | None" = None, - color_bar: "Literal['simple', 'full'] | None" = None, - vrange: "tuple[int, int] | None" = None, - ) -> "ImageItem": - """ - Set the image source and update the image. - - Args: - monitor(str): The name of the monitor to use for the image. - monitor_type(str): The type of monitor to use. Options are "1d", "2d", or "auto". - color_map(str): The color map to use for the image. - color_bar(str): The type of color bar to use. Options are "simple" or "full". - vrange(tuple): The range of values to use for the color map. - - Returns: - ImageItem: The image object. - """ - - @property - @rpc_call - def main_image(self) -> "ImageItem": - """ - Access the main image item. - """ - - -class ImageItem(RPCBase): - @property - @rpc_call - def color_map(self) -> "str": - """ - Get the current color map. - """ - - @color_map.setter - @rpc_call - def color_map(self) -> "str": - """ - Get the current color map. - """ - - @property - @rpc_call - def v_range(self) -> "tuple[float, float]": - """ - Get the color intensity range of the image. - """ - - @v_range.setter - @rpc_call - def v_range(self) -> "tuple[float, float]": - """ - Get the color intensity range of the image. - """ - - @property - @rpc_call - def v_min(self) -> "float": - """ - None - """ - - @v_min.setter - @rpc_call - def v_min(self) -> "float": - """ - None - """ - - @property - @rpc_call - def v_max(self) -> "float": - """ - None - """ - - @v_max.setter - @rpc_call - def v_max(self) -> "float": - """ - None - """ - - @property - @rpc_call - def autorange(self) -> "bool": - """ - None - """ - - @autorange.setter - @rpc_call - def autorange(self) -> "bool": - """ - None - """ - - @property - @rpc_call - def autorange_mode(self) -> "str": - """ - None - """ - - @autorange_mode.setter - @rpc_call - def autorange_mode(self) -> "str": - """ - None - """ - - @property - @rpc_call - def fft(self) -> "bool": - """ - Get or set whether FFT postprocessing is enabled. - """ - - @fft.setter - @rpc_call - def fft(self) -> "bool": - """ - Get or set whether FFT postprocessing is enabled. - """ - - @property - @rpc_call - def log(self) -> "bool": - """ - Get or set whether logarithmic scaling is applied. - """ - - @log.setter - @rpc_call - def log(self) -> "bool": - """ - Get or set whether logarithmic scaling is applied. - """ - - @property - @rpc_call - def rotation(self) -> "Optional[int]": - """ - Get or set the number of 90° rotations to apply. - """ - - @rotation.setter - @rpc_call - def rotation(self) -> "Optional[int]": - """ - Get or set the number of 90° rotations to apply. - """ - - @property - @rpc_call - def transpose(self) -> "bool": - """ - Get or set whether the image is transposed. - """ - - @transpose.setter - @rpc_call - def transpose(self) -> "bool": - """ - Get or set whether the image is transposed. - """ - - @rpc_call - def get_data(self) -> "np.ndarray": - """ - Get the data of the image. - Returns: - np.ndarray: The data of the image. - """ - - -class LMFitDialog(RPCBase): - """Dialog for displaying the fit summary and params for LMFit DAP processes""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class LogPanel(RPCBase): - """Displays a log panel""" - - @rpc_call - def set_plain_text(self, text: str) -> None: - """ - Set the plain text of the widget. - - Args: - text (str): The text to set. - """ - - @rpc_call - def set_html_text(self, text: str) -> None: - """ - Set the HTML text of the widget. - - Args: - text (str): The text to set. - """ - - -class Minesweeper(RPCBase): ... - - -class MotorMap(RPCBase): - @property - @rpc_call - def enable_toolbar(self) -> "bool": - """ - Show Toolbar. - """ - - @enable_toolbar.setter - @rpc_call - def enable_toolbar(self) -> "bool": - """ - Show Toolbar. - """ - - @property - @rpc_call - def enable_side_panel(self) -> "bool": - """ - Show Side Panel - """ - - @enable_side_panel.setter - @rpc_call - def enable_side_panel(self) -> "bool": - """ - Show Side Panel - """ - - @property - @rpc_call - def enable_fps_monitor(self) -> "bool": - """ - Enable the FPS monitor. - """ - - @enable_fps_monitor.setter - @rpc_call - def enable_fps_monitor(self) -> "bool": - """ - Enable the FPS monitor. - """ - - @rpc_call - def set(self, **kwargs): - """ - Set the properties of the plot widget. - - Args: - **kwargs: Keyword arguments for the properties to be set. - - Possible properties: - - title: str - - x_label: str - - y_label: str - - x_scale: Literal["linear", "log"] - - y_scale: Literal["linear", "log"] - - x_lim: tuple - - y_lim: tuple - - legend_label_size: int - """ - - @property - @rpc_call - def title(self) -> "str": - """ - Set title of the plot. - """ - - @title.setter - @rpc_call - def title(self) -> "str": - """ - Set title of the plot. - """ - - @property - @rpc_call - def x_label(self) -> "str": - """ - The set label for the x-axis. - """ - - @x_label.setter - @rpc_call - def x_label(self) -> "str": - """ - The set label for the x-axis. - """ - - @property - @rpc_call - def y_label(self) -> "str": - """ - The set label for the y-axis. - """ - - @y_label.setter - @rpc_call - def y_label(self) -> "str": - """ - The set label for the y-axis. - """ - - @property - @rpc_call - def x_limits(self) -> "QPointF": - """ - Get the x limits of the plot. - """ - - @x_limits.setter - @rpc_call - def x_limits(self) -> "QPointF": - """ - Get the x limits of the plot. - """ - - @property - @rpc_call - def y_limits(self) -> "QPointF": - """ - Get the y limits of the plot. - """ - - @y_limits.setter - @rpc_call - def y_limits(self) -> "QPointF": - """ - Get the y limits of the plot. - """ - - @property - @rpc_call - def x_grid(self) -> "bool": - """ - Show grid on the x-axis. - """ - - @x_grid.setter - @rpc_call - def x_grid(self) -> "bool": - """ - Show grid on the x-axis. - """ - - @property - @rpc_call - def y_grid(self) -> "bool": - """ - Show grid on the y-axis. - """ - - @y_grid.setter - @rpc_call - def y_grid(self) -> "bool": - """ - Show grid on the y-axis. - """ - - @property - @rpc_call - def inner_axes(self) -> "bool": - """ - Show inner axes of the plot widget. - """ - - @inner_axes.setter - @rpc_call - def inner_axes(self) -> "bool": - """ - Show inner axes of the plot widget. - """ - - @property - @rpc_call - def outer_axes(self) -> "bool": - """ - Show the outer axes of the plot widget. - """ - - @outer_axes.setter - @rpc_call - def outer_axes(self) -> "bool": - """ - Show the outer axes of the plot widget. - """ - - @property - @rpc_call - def lock_aspect_ratio(self) -> "bool": - """ - Lock aspect ratio of the plot widget. - """ - - @lock_aspect_ratio.setter - @rpc_call - def lock_aspect_ratio(self) -> "bool": - """ - Lock aspect ratio of the plot widget. - """ - - @property - @rpc_call - def auto_range_x(self) -> "bool": - """ - Set auto range for the x-axis. - """ - - @auto_range_x.setter - @rpc_call - def auto_range_x(self) -> "bool": - """ - Set auto range for the x-axis. - """ - - @property - @rpc_call - def auto_range_y(self) -> "bool": - """ - Set auto range for the y-axis. - """ - - @auto_range_y.setter - @rpc_call - def auto_range_y(self) -> "bool": - """ - Set auto range for the y-axis. - """ - - @property - @rpc_call - def x_log(self) -> "bool": - """ - Set X-axis to log scale if True, linear if False. - """ - - @x_log.setter - @rpc_call - def x_log(self) -> "bool": - """ - Set X-axis to log scale if True, linear if False. - """ - - @property - @rpc_call - def y_log(self) -> "bool": - """ - Set Y-axis to log scale if True, linear if False. - """ - - @y_log.setter - @rpc_call - def y_log(self) -> "bool": - """ - Set Y-axis to log scale if True, linear if False. - """ - - @property - @rpc_call - def legend_label_size(self) -> "int": - """ - The font size of the legend font. - """ - - @legend_label_size.setter - @rpc_call - def legend_label_size(self) -> "int": - """ - The font size of the legend font. - """ - - @property - @rpc_call - def color(self) -> "tuple": - """ - Get the color of the motor trace. - - Returns: - tuple: Color of the motor trace. - """ - - @color.setter - @rpc_call - def color(self) -> "tuple": - """ - Get the color of the motor trace. - - Returns: - tuple: Color of the motor trace. - """ - - @property - @rpc_call - def max_points(self) -> "int": - """ - Get the maximum number of points to display. - """ - - @max_points.setter - @rpc_call - def max_points(self) -> "int": - """ - Get the maximum number of points to display. - """ - - @property - @rpc_call - def precision(self) -> "int": - """ - Set the decimal precision of the motor position. - """ - - @precision.setter - @rpc_call - def precision(self) -> "int": - """ - Set the decimal precision of the motor position. - """ - - @property - @rpc_call - def num_dim_points(self) -> "int": - """ - Get the number of dim points for the motor map. - """ - - @num_dim_points.setter - @rpc_call - def num_dim_points(self) -> "int": - """ - Get the number of dim points for the motor map. - """ - - @property - @rpc_call - def background_value(self) -> "int": - """ - Get the background value of the motor map. - """ - - @background_value.setter - @rpc_call - def background_value(self) -> "int": - """ - Get the background value of the motor map. - """ - - @property - @rpc_call - def scatter_size(self) -> "int": - """ - Get the scatter size of the motor map plot. - """ - - @scatter_size.setter - @rpc_call - def scatter_size(self) -> "int": - """ - Get the scatter size of the motor map plot. - """ - - @rpc_call - def map(self, x_name: "str", y_name: "str", validate_bec: "bool" = True) -> "None": - """ - Set the x and y motor names. - - Args: - x_name(str): The name of the x motor. - y_name(str): The name of the y motor. - validate_bec(bool, optional): If True, validate the signal with BEC. Defaults to True. - """ - - @rpc_call - def reset_history(self): - """ - Reset the history of the motor map. - """ - - @rpc_call - def get_data(self) -> "dict": - """ - Get the data of the motor map. - - Returns: - dict: Data of the motor map. - """ - - -class MultiWaveform(RPCBase): - @property - @rpc_call - def enable_toolbar(self) -> "bool": - """ - Show Toolbar. - """ - - @enable_toolbar.setter - @rpc_call - def enable_toolbar(self) -> "bool": - """ - Show Toolbar. - """ - - @property - @rpc_call - def enable_side_panel(self) -> "bool": - """ - Show Side Panel - """ - - @enable_side_panel.setter - @rpc_call - def enable_side_panel(self) -> "bool": - """ - Show Side Panel - """ - - @property - @rpc_call - def enable_fps_monitor(self) -> "bool": - """ - Enable the FPS monitor. - """ - - @enable_fps_monitor.setter - @rpc_call - def enable_fps_monitor(self) -> "bool": - """ - Enable the FPS monitor. - """ - - @rpc_call - def set(self, **kwargs): - """ - Set the properties of the plot widget. - - Args: - **kwargs: Keyword arguments for the properties to be set. - - Possible properties: - - title: str - - x_label: str - - y_label: str - - x_scale: Literal["linear", "log"] - - y_scale: Literal["linear", "log"] - - x_lim: tuple - - y_lim: tuple - - legend_label_size: int - """ - - @property - @rpc_call - def title(self) -> "str": - """ - Set title of the plot. - """ - - @title.setter - @rpc_call - def title(self) -> "str": - """ - Set title of the plot. - """ - - @property - @rpc_call - def x_label(self) -> "str": - """ - The set label for the x-axis. - """ - - @x_label.setter - @rpc_call - def x_label(self) -> "str": - """ - The set label for the x-axis. - """ - - @property - @rpc_call - def y_label(self) -> "str": - """ - The set label for the y-axis. - """ - - @y_label.setter - @rpc_call - def y_label(self) -> "str": - """ - The set label for the y-axis. - """ - - @property - @rpc_call - def x_limits(self) -> "QPointF": - """ - Get the x limits of the plot. - """ - - @x_limits.setter - @rpc_call - def x_limits(self) -> "QPointF": - """ - Get the x limits of the plot. - """ - - @property - @rpc_call - def y_limits(self) -> "QPointF": - """ - Get the y limits of the plot. - """ - - @y_limits.setter - @rpc_call - def y_limits(self) -> "QPointF": - """ - Get the y limits of the plot. - """ - - @property - @rpc_call - def x_grid(self) -> "bool": - """ - Show grid on the x-axis. - """ - - @x_grid.setter - @rpc_call - def x_grid(self) -> "bool": - """ - Show grid on the x-axis. - """ - - @property - @rpc_call - def y_grid(self) -> "bool": - """ - Show grid on the y-axis. - """ - - @y_grid.setter - @rpc_call - def y_grid(self) -> "bool": - """ - Show grid on the y-axis. - """ - - @property - @rpc_call - def inner_axes(self) -> "bool": - """ - Show inner axes of the plot widget. - """ - - @inner_axes.setter - @rpc_call - def inner_axes(self) -> "bool": - """ - Show inner axes of the plot widget. - """ - - @property - @rpc_call - def outer_axes(self) -> "bool": - """ - Show the outer axes of the plot widget. - """ - - @outer_axes.setter - @rpc_call - def outer_axes(self) -> "bool": - """ - Show the outer axes of the plot widget. - """ - - @property - @rpc_call - def lock_aspect_ratio(self) -> "bool": - """ - Lock aspect ratio of the plot widget. - """ - - @lock_aspect_ratio.setter - @rpc_call - def lock_aspect_ratio(self) -> "bool": - """ - Lock aspect ratio of the plot widget. - """ - - @property - @rpc_call - def auto_range_x(self) -> "bool": - """ - Set auto range for the x-axis. - """ - - @auto_range_x.setter - @rpc_call - def auto_range_x(self) -> "bool": - """ - Set auto range for the x-axis. - """ - - @property - @rpc_call - def auto_range_y(self) -> "bool": - """ - Set auto range for the y-axis. - """ - - @auto_range_y.setter - @rpc_call - def auto_range_y(self) -> "bool": - """ - Set auto range for the y-axis. - """ - - @property - @rpc_call - def x_log(self) -> "bool": - """ - Set X-axis to log scale if True, linear if False. - """ - - @x_log.setter - @rpc_call - def x_log(self) -> "bool": - """ - Set X-axis to log scale if True, linear if False. - """ - - @property - @rpc_call - def y_log(self) -> "bool": - """ - Set Y-axis to log scale if True, linear if False. - """ - - @y_log.setter - @rpc_call - def y_log(self) -> "bool": - """ - Set Y-axis to log scale if True, linear if False. - """ - - @property - @rpc_call - def legend_label_size(self) -> "int": - """ - The font size of the legend font. - """ - - @legend_label_size.setter - @rpc_call - def legend_label_size(self) -> "int": - """ - The font size of the legend font. - """ - - @property - @rpc_call - def highlighted_index(self): - """ - None - """ - - @highlighted_index.setter - @rpc_call - def highlighted_index(self): - """ - None - """ - - @property - @rpc_call - def highlight_last_curve(self) -> "bool": - """ - Get the highlight_last_curve property. - Returns: - bool: The highlight_last_curve property. - """ - - @highlight_last_curve.setter - @rpc_call - def highlight_last_curve(self) -> "bool": - """ - Get the highlight_last_curve property. - Returns: - bool: The highlight_last_curve property. - """ - - @property - @rpc_call - def color_palette(self) -> "str": - """ - The color palette of the figure widget. - """ - - @color_palette.setter - @rpc_call - def color_palette(self) -> "str": - """ - The color palette of the figure widget. - """ - - @property - @rpc_call - def opacity(self) -> "int": - """ - The opacity of the figure widget. - """ - - @opacity.setter - @rpc_call - def opacity(self) -> "int": - """ - The opacity of the figure widget. - """ - - @property - @rpc_call - def flush_buffer(self) -> "bool": - """ - The flush_buffer property. - """ - - @flush_buffer.setter - @rpc_call - def flush_buffer(self) -> "bool": - """ - The flush_buffer property. - """ - - @property - @rpc_call - def max_trace(self) -> "int": - """ - The maximum number of traces to display on the plot. - """ - - @max_trace.setter - @rpc_call - def max_trace(self) -> "int": - """ - The maximum number of traces to display on the plot. - """ - - @property - @rpc_call - def monitor(self) -> "str": - """ - The monitor of the figure widget. - """ - - @monitor.setter - @rpc_call - def monitor(self) -> "str": - """ - The monitor of the figure widget. - """ - - @rpc_call - def set_curve_limit(self, max_trace: "int", flush_buffer: "bool"): - """ - Set the maximum number of traces to display on the plot. - - Args: - max_trace (int): The maximum number of traces to display. - flush_buffer (bool): Flush the buffer. - """ - - @rpc_call - def plot(self, monitor: "str", color_palette: "str | None" = "magma"): - """ - Create a plot for the given monitor. - Args: - monitor (str): The monitor to set. - color_palette (str|None): The color palette to use for the plot. - """ - - @rpc_call - def set_curve_highlight(self, index: "int"): - """ - Set the curve highlight based on visible curves. - - Args: - index (int): The index of the curve to highlight among visible curves. - """ - - @rpc_call - def clear_curves(self): - """ - Remove all curves from the plot, excluding crosshair items. - """ - - -class PositionIndicator(RPCBase): - @rpc_call - def set_value(self, position: float): - """ - None - """ - - @rpc_call - def set_range(self, min_value: float, max_value: float): - """ - Set the range of the position indicator - - Args: - min_value(float): Minimum value of the range - max_value(float): Maximum value of the range - """ - - @property - @rpc_call - def vertical(self): - """ - Property to determine the orientation of the position indicator - """ - - @property - @rpc_call - def indicator_width(self): - """ - Property to get the width of the indicator - """ - - @property - @rpc_call - def rounded_corners(self): - """ - Property to get the rounded corners of the position indicator - """ - - -class PositionerBox(RPCBase): - """Simple Widget to control a positioner in box form""" - - @rpc_call - def set_positioner(self, positioner: "str | Positioner"): - """ - Set the device - - Args: - positioner (Positioner | str) : Positioner to set, accepts str or the device - """ - - -class PositionerBox2D(RPCBase): - """Simple Widget to control two positioners in box form""" - - @rpc_call - def set_positioner_hor(self, positioner: "str | Positioner"): - """ - Set the device - - Args: - positioner (Positioner | str) : Positioner to set, accepts str or the device - """ - - @rpc_call - def set_positioner_ver(self, positioner: "str | Positioner"): - """ - Set the device - - Args: - positioner (Positioner | str) : Positioner to set, accepts str or the device - """ - - -class PositionerBoxBase(RPCBase): - """Contains some core logic for positioner box widgets""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class PositionerControlLine(RPCBase): - """A widget that controls a single device.""" - - @rpc_call - def set_positioner(self, positioner: "str | Positioner"): - """ - Set the device - - Args: - positioner (Positioner | str) : Positioner to set, accepts str or the device - """ - - -class PositionerGroup(RPCBase): - """Simple Widget to control a positioner in box form""" - - @rpc_call - def set_positioners(self, device_names: "str"): - """ - Redraw grid with positioners from device_names string - - Device names must be separated by space - """ - - -class ResetButton(RPCBase): - """A button that resets the scan queue.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class ResumeButton(RPCBase): - """A button that continue scan queue.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class Ring(RPCBase): - @rpc_call - def _get_all_rpc(self) -> "dict": - """ - Get all registered RPC objects. - """ - - @property - @rpc_call - def _rpc_id(self) -> "str": - """ - Get the RPC ID of the widget. - """ - - @property - @rpc_call - def _config_dict(self) -> "dict": - """ - Get the configuration of the widget. - - Returns: - dict: The configuration of the widget. - """ - - @rpc_call - def set_value(self, value: "int | float"): - """ - Set the value for the ring widget - - Args: - value(int | float): Value for the ring widget - """ - - @rpc_call - def set_color(self, color: "str | tuple"): - """ - Set the color for the ring widget - - Args: - color(str | tuple): Color for the ring widget. Can be HEX code or tuple (R, G, B, A). - """ - - @rpc_call - def set_background(self, color: "str | tuple"): - """ - Set the background color for the ring widget - - Args: - color(str | tuple): Background color for the ring widget. Can be HEX code or tuple (R, G, B, A). - """ - - @rpc_call - def set_line_width(self, width: "int"): - """ - Set the line width for the ring widget - - Args: - width(int): Line width for the ring widget - """ - - @rpc_call - def set_min_max_values(self, min_value: "int | float", max_value: "int | float"): - """ - Set the min and max values for the ring widget. - - Args: - min_value(int | float): Minimum value for the ring widget - max_value(int | float): Maximum value for the ring widget - """ - - @rpc_call - def set_start_angle(self, start_angle: "int"): - """ - Set the start angle for the ring widget - - Args: - start_angle(int): Start angle for the ring widget in degrees - """ - - @rpc_call - def set_update(self, mode: "Literal['manual', 'scan', 'device']", device: "str" = None): - """ - Set the update mode for the ring widget. - Modes: - - "manual": Manual update mode, the value is set by the user. - - "scan": Update mode for the scan progress. The value is updated by the current scan progress. - - "device": Update mode for the device readback. The value is updated by the device readback. Take into account that user has to set the device name and limits. - - Args: - mode(str): Update mode for the ring widget. Can be "manual", "scan" or "device" - device(str): Device name for the device readback mode, only used when mode is "device" - """ - - @rpc_call - def reset_connection(self): - """ - Reset the connections for the ring widget. Disconnect the current slot and endpoint. - """ - - -class RingProgressBar(RPCBase): - @rpc_call - def _get_all_rpc(self) -> "dict": - """ - Get all registered RPC objects. - """ - - @property - @rpc_call - def _rpc_id(self) -> "str": - """ - Get the RPC ID of the widget. - """ - - @property - @rpc_call - def _config_dict(self) -> "dict": - """ - Get the configuration of the widget. - - Returns: - dict: The configuration of the widget. - """ - - @property - @rpc_call - def rings(self): - """ - None - """ - - @rpc_call - def update_config(self, config: "RingProgressBarConfig | dict"): - """ - Update the configuration of the widget. - - Args: - config(SpiralProgressBarConfig|dict): Configuration to update. - """ - - @rpc_call - def add_ring(self, **kwargs) -> "Ring": - """ - Add a new progress bar. - - Args: - **kwargs: Keyword arguments for the new progress bar. - - Returns: - Ring: Ring object. - """ - - @rpc_call - def remove_ring(self, index: "int"): - """ - Remove a progress bar by index. - - Args: - index(int): Index of the progress bar to remove. - """ - - @rpc_call - def set_precision(self, precision: "int", bar_index: "int" = None): - """ - Set the precision for the progress bars. If bar_index is not provide, the precision will be set for all progress bars. - - Args: - precision(int): Precision for the progress bars. - bar_index(int): Index of the progress bar to set the precision for. If provided, only a single precision can be set. - """ - - @rpc_call - def set_min_max_values( - self, - min_values: "int | float | list[int | float]", - max_values: "int | float | list[int | float]", - ): - """ - Set the minimum and maximum values for the progress bars. - - Args: - min_values(int|float | list[float]): Minimum value(s) for the progress bars. If multiple progress bars are displayed, provide a list of minimum values for each progress bar. - max_values(int|float | list[float]): Maximum value(s) for the progress bars. If multiple progress bars are displayed, provide a list of maximum values for each progress bar. - """ - - @rpc_call - def set_number_of_bars(self, num_bars: "int"): - """ - Set the number of progress bars to display. - - Args: - num_bars(int): Number of progress bars to display. - """ - - @rpc_call - def set_value(self, values: "int | list", ring_index: "int" = None): - """ - Set the values for the progress bars. - - Args: - values(int | tuple): Value(s) for the progress bars. If multiple progress bars are displayed, provide a tuple of values for each progress bar. - ring_index(int): Index of the progress bar to set the value for. If provided, only a single value can be set. - - Examples: - >>> SpiralProgressBar.set_value(50) - >>> SpiralProgressBar.set_value([30, 40, 50]) # (outer, middle, inner) - >>> SpiralProgressBar.set_value(60, bar_index=1) # Set the value for the middle progress bar. - """ - - @rpc_call - def set_colors_from_map(self, colormap, color_format: "Literal['RGB', 'HEX']" = "RGB"): - """ - Set the colors for the progress bars from a colormap. - - Args: - colormap(str): Name of the colormap. - color_format(Literal["RGB","HEX"]): Format of the returned colors ('RGB', 'HEX'). - """ - - @rpc_call - def set_colors_directly( - self, colors: "list[str | tuple] | str | tuple", bar_index: "int" = None - ): - """ - Set the colors for the progress bars directly. - - Args: - colors(list[str | tuple] | str | tuple): Color(s) for the progress bars. If multiple progress bars are displayed, provide a list of colors for each progress bar. - bar_index(int): Index of the progress bar to set the color for. If provided, only a single color can be set. - """ - - @rpc_call - def set_line_widths(self, widths: "int | list[int]", bar_index: "int" = None): - """ - Set the line widths for the progress bars. - - Args: - widths(int | list[int]): Line width(s) for the progress bars. If multiple progress bars are displayed, provide a list of line widths for each progress bar. - bar_index(int): Index of the progress bar to set the line width for. If provided, only a single line width can be set. - """ - - @rpc_call - def set_gap(self, gap: "int"): - """ - Set the gap between the progress bars. - - Args: - gap(int): Gap between the progress bars. - """ - - @rpc_call - def set_diameter(self, diameter: "int"): - """ - Set the diameter of the widget. - - Args: - diameter(int): Diameter of the widget. - """ - - @rpc_call - def reset_diameter(self): - """ - Reset the fixed size of the widget. - """ - - @rpc_call - def enable_auto_updates(self, enable: "bool" = True): - """ - Enable or disable updates based on scan status. Overrides manual updates. - The behaviour of the whole progress bar widget will be driven by the scan queue status. - - Args: - enable(bool): True or False. - - Returns: - bool: True if scan segment updates are enabled. - """ - - -class ScanControl(RPCBase): - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class ScanMetadata(RPCBase): - """Dynamically generates a form for inclusion of metadata for a scan. Uses the""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class ScatterCurve(RPCBase): - """Scatter curve item for the scatter waveform widget.""" - - @property - @rpc_call - def color_map(self) -> "str": - """ - The color map of the scatter curve. - """ - - -class ScatterWaveform(RPCBase): - @property - @rpc_call - def enable_toolbar(self) -> "bool": - """ - Show Toolbar. - """ - - @enable_toolbar.setter - @rpc_call - def enable_toolbar(self) -> "bool": - """ - Show Toolbar. - """ - - @property - @rpc_call - def enable_side_panel(self) -> "bool": - """ - Show Side Panel - """ - - @enable_side_panel.setter - @rpc_call - def enable_side_panel(self) -> "bool": - """ - Show Side Panel - """ - - @property - @rpc_call - def enable_fps_monitor(self) -> "bool": - """ - Enable the FPS monitor. - """ - - @enable_fps_monitor.setter - @rpc_call - def enable_fps_monitor(self) -> "bool": - """ - Enable the FPS monitor. - """ - - @rpc_call - def set(self, **kwargs): - """ - Set the properties of the plot widget. - - Args: - **kwargs: Keyword arguments for the properties to be set. - - Possible properties: - - title: str - - x_label: str - - y_label: str - - x_scale: Literal["linear", "log"] - - y_scale: Literal["linear", "log"] - - x_lim: tuple - - y_lim: tuple - - legend_label_size: int - """ - - @property - @rpc_call - def title(self) -> "str": - """ - Set title of the plot. - """ - - @title.setter - @rpc_call - def title(self) -> "str": - """ - Set title of the plot. - """ - - @property - @rpc_call - def x_label(self) -> "str": - """ - The set label for the x-axis. - """ - - @x_label.setter - @rpc_call - def x_label(self) -> "str": - """ - The set label for the x-axis. - """ - - @property - @rpc_call - def y_label(self) -> "str": - """ - The set label for the y-axis. - """ - - @y_label.setter - @rpc_call - def y_label(self) -> "str": - """ - The set label for the y-axis. - """ - - @property - @rpc_call - def x_limits(self) -> "QPointF": - """ - Get the x limits of the plot. - """ - - @x_limits.setter - @rpc_call - def x_limits(self) -> "QPointF": - """ - Get the x limits of the plot. - """ - - @property - @rpc_call - def y_limits(self) -> "QPointF": - """ - Get the y limits of the plot. - """ - - @y_limits.setter - @rpc_call - def y_limits(self) -> "QPointF": - """ - Get the y limits of the plot. - """ - - @property - @rpc_call - def x_grid(self) -> "bool": - """ - Show grid on the x-axis. - """ - - @x_grid.setter - @rpc_call - def x_grid(self) -> "bool": - """ - Show grid on the x-axis. - """ - - @property - @rpc_call - def y_grid(self) -> "bool": - """ - Show grid on the y-axis. - """ - - @y_grid.setter - @rpc_call - def y_grid(self) -> "bool": - """ - Show grid on the y-axis. - """ - - @property - @rpc_call - def inner_axes(self) -> "bool": - """ - Show inner axes of the plot widget. - """ - - @inner_axes.setter - @rpc_call - def inner_axes(self) -> "bool": - """ - Show inner axes of the plot widget. - """ - - @property - @rpc_call - def outer_axes(self) -> "bool": - """ - Show the outer axes of the plot widget. - """ - - @outer_axes.setter - @rpc_call - def outer_axes(self) -> "bool": - """ - Show the outer axes of the plot widget. - """ - - @property - @rpc_call - def lock_aspect_ratio(self) -> "bool": - """ - Lock aspect ratio of the plot widget. - """ - - @lock_aspect_ratio.setter - @rpc_call - def lock_aspect_ratio(self) -> "bool": - """ - Lock aspect ratio of the plot widget. - """ - - @property - @rpc_call - def auto_range_x(self) -> "bool": - """ - Set auto range for the x-axis. - """ - - @auto_range_x.setter - @rpc_call - def auto_range_x(self) -> "bool": - """ - Set auto range for the x-axis. - """ - - @property - @rpc_call - def auto_range_y(self) -> "bool": - """ - Set auto range for the y-axis. - """ - - @auto_range_y.setter - @rpc_call - def auto_range_y(self) -> "bool": - """ - Set auto range for the y-axis. - """ - - @property - @rpc_call - def x_log(self) -> "bool": - """ - Set X-axis to log scale if True, linear if False. - """ - - @x_log.setter - @rpc_call - def x_log(self) -> "bool": - """ - Set X-axis to log scale if True, linear if False. - """ - - @property - @rpc_call - def y_log(self) -> "bool": - """ - Set Y-axis to log scale if True, linear if False. - """ - - @y_log.setter - @rpc_call - def y_log(self) -> "bool": - """ - Set Y-axis to log scale if True, linear if False. - """ - - @property - @rpc_call - def legend_label_size(self) -> "int": - """ - The font size of the legend font. - """ - - @legend_label_size.setter - @rpc_call - def legend_label_size(self) -> "int": - """ - The font size of the legend font. - """ - - @property - @rpc_call - def main_curve(self) -> "ScatterCurve": - """ - The main scatter curve item. - """ - - @property - @rpc_call - def color_map(self) -> "str": - """ - The color map of the scatter waveform. - """ - - @color_map.setter - @rpc_call - def color_map(self) -> "str": - """ - The color map of the scatter waveform. - """ - - @rpc_call - def plot( - self, - x_name: "str", - y_name: "str", - z_name: "str", - x_entry: "None | str" = None, - y_entry: "None | str" = None, - z_entry: "None | str" = None, - color_map: "str | None" = "magma", - label: "str | None" = None, - validate_bec: "bool" = True, - ) -> "ScatterCurve": - """ - Plot the data from the device signals. - - Args: - x_name (str): The name of the x device signal. - y_name (str): The name of the y device signal. - z_name (str): The name of the z device signal. - x_entry (None | str): The x entry of the device signal. - y_entry (None | str): The y entry of the device signal. - z_entry (None | str): The z entry of the device signal. - color_map (str | None): The color map of the scatter waveform. - label (str | None): The label of the curve. - validate_bec (bool): Whether to validate the device signals with current BEC instance. - - Returns: - ScatterCurve: The scatter curve object. - """ - - @rpc_call - def update_with_scan_history(self, scan_index: "int" = None, scan_id: "str" = None): - """ - Update the scan curves with the data from the scan storage. - Provide only one of scan_id or scan_index. - - Args: - scan_id(str, optional): ScanID of the scan to be updated. Defaults to None. - scan_index(int, optional): Index of the scan to be updated. Defaults to None. - """ - - @rpc_call - def clear_all(self): - """ - Clear all the curves from the plot. - """ - - -class SignalComboBox(RPCBase): - """Line edit widget for device input with autocomplete for device names.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class SignalLineEdit(RPCBase): - """Line edit widget for device input with autocomplete for device names.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class StopButton(RPCBase): - """A button that stops the current scan.""" - - @rpc_call - def remove(self): - """ - Cleanup the BECConnector - """ - - -class TextBox(RPCBase): - """A widget that displays text in plain and HTML format""" - - @rpc_call - def set_plain_text(self, text: str) -> None: - """ - Set the plain text of the widget. - - Args: - text (str): The text to set. - """ - - @rpc_call - def set_html_text(self, text: str) -> None: - """ - Set the HTML text of the widget. - - Args: - text (str): The text to set. - """ - - -class VSCodeEditor(RPCBase): - """A widget to display the VSCode editor.""" - - ... - - -class Waveform(RPCBase): - @property - @rpc_call - def _config_dict(self) -> "dict": - """ - Get the configuration of the widget. - - Returns: - dict: The configuration of the widget. - """ - - @property - @rpc_call - def enable_toolbar(self) -> "bool": - """ - Show Toolbar. - """ - - @enable_toolbar.setter - @rpc_call - def enable_toolbar(self) -> "bool": - """ - Show Toolbar. - """ - - @property - @rpc_call - def enable_side_panel(self) -> "bool": - """ - Show Side Panel - """ - - @enable_side_panel.setter - @rpc_call - def enable_side_panel(self) -> "bool": - """ - Show Side Panel - """ - - @property - @rpc_call - def enable_fps_monitor(self) -> "bool": - """ - Enable the FPS monitor. - """ - - @enable_fps_monitor.setter - @rpc_call - def enable_fps_monitor(self) -> "bool": - """ - Enable the FPS monitor. - """ - - @rpc_call - def set(self, **kwargs): - """ - Set the properties of the plot widget. - - Args: - **kwargs: Keyword arguments for the properties to be set. - - Possible properties: - - title: str - - x_label: str - - y_label: str - - x_scale: Literal["linear", "log"] - - y_scale: Literal["linear", "log"] - - x_lim: tuple - - y_lim: tuple - - legend_label_size: int - """ - - @property - @rpc_call - def title(self) -> "str": - """ - Set title of the plot. - """ - - @title.setter - @rpc_call - def title(self) -> "str": - """ - Set title of the plot. - """ - - @property - @rpc_call - def x_label(self) -> "str": - """ - The set label for the x-axis. - """ - - @x_label.setter - @rpc_call - def x_label(self) -> "str": - """ - The set label for the x-axis. - """ - - @property - @rpc_call - def y_label(self) -> "str": - """ - The set label for the y-axis. - """ - - @y_label.setter - @rpc_call - def y_label(self) -> "str": - """ - The set label for the y-axis. - """ - - @property - @rpc_call - def x_limits(self) -> "QPointF": - """ - Get the x limits of the plot. - """ - - @x_limits.setter - @rpc_call - def x_limits(self) -> "QPointF": - """ - Get the x limits of the plot. - """ - - @property - @rpc_call - def y_limits(self) -> "QPointF": - """ - Get the y limits of the plot. - """ - - @y_limits.setter - @rpc_call - def y_limits(self) -> "QPointF": - """ - Get the y limits of the plot. - """ - - @property - @rpc_call - def x_grid(self) -> "bool": - """ - Show grid on the x-axis. - """ - - @x_grid.setter - @rpc_call - def x_grid(self) -> "bool": - """ - Show grid on the x-axis. - """ - - @property - @rpc_call - def y_grid(self) -> "bool": - """ - Show grid on the y-axis. - """ - - @y_grid.setter - @rpc_call - def y_grid(self) -> "bool": - """ - Show grid on the y-axis. - """ - - @property - @rpc_call - def inner_axes(self) -> "bool": - """ - Show inner axes of the plot widget. - """ - - @inner_axes.setter - @rpc_call - def inner_axes(self) -> "bool": - """ - Show inner axes of the plot widget. - """ - - @property - @rpc_call - def outer_axes(self) -> "bool": - """ - Show the outer axes of the plot widget. - """ - - @outer_axes.setter - @rpc_call - def outer_axes(self) -> "bool": - """ - Show the outer axes of the plot widget. - """ - - @property - @rpc_call - def lock_aspect_ratio(self) -> "bool": - """ - Lock aspect ratio of the plot widget. - """ - - @lock_aspect_ratio.setter - @rpc_call - def lock_aspect_ratio(self) -> "bool": - """ - Lock aspect ratio of the plot widget. - """ - - @property - @rpc_call - def auto_range_x(self) -> "bool": - """ - Set auto range for the x-axis. - """ - - @auto_range_x.setter - @rpc_call - def auto_range_x(self) -> "bool": - """ - Set auto range for the x-axis. - """ - - @property - @rpc_call - def auto_range_y(self) -> "bool": - """ - Set auto range for the y-axis. - """ - - @auto_range_y.setter - @rpc_call - def auto_range_y(self) -> "bool": - """ - Set auto range for the y-axis. - """ - - @property - @rpc_call - def x_log(self) -> "bool": - """ - Set X-axis to log scale if True, linear if False. - """ - - @x_log.setter - @rpc_call - def x_log(self) -> "bool": - """ - Set X-axis to log scale if True, linear if False. - """ - - @property - @rpc_call - def y_log(self) -> "bool": - """ - Set Y-axis to log scale if True, linear if False. - """ - - @y_log.setter - @rpc_call - def y_log(self) -> "bool": - """ - Set Y-axis to log scale if True, linear if False. - """ - - @property - @rpc_call - def legend_label_size(self) -> "int": - """ - The font size of the legend font. - """ - - @legend_label_size.setter - @rpc_call - def legend_label_size(self) -> "int": - """ - The font size of the legend font. - """ - - @rpc_call - def __getitem__(self, key: "int | str"): - """ - None - """ - - @property - @rpc_call - def curves(self) -> "list[Curve]": - """ - Get the curves of the plot widget as a list. - - Returns: - list: List of curves. - """ - - @property - @rpc_call - def x_mode(self) -> "str": - """ - None - """ - - @x_mode.setter - @rpc_call - def x_mode(self) -> "str": - """ - None - """ - - @property - @rpc_call - def color_palette(self) -> "str": - """ - The color palette of the figure widget. - """ - - @color_palette.setter - @rpc_call - def color_palette(self) -> "str": - """ - The color palette of the figure widget. - """ - - @rpc_call - def plot( - self, - arg1: "list | np.ndarray | str | None" = None, - y: "list | np.ndarray | None" = None, - x: "list | np.ndarray | None" = None, - x_name: "str | None" = None, - y_name: "str | None" = None, - x_entry: "str | None" = None, - y_entry: "str | None" = None, - color: "str | None" = None, - label: "str | None" = None, - dap: "str | None" = None, - **kwargs, - ) -> "Curve": - """ - Plot a curve to the plot widget. - - Args: - arg1(list | np.ndarray | str | None): First argument, which can be x data, y data, or y_name. - y(list | np.ndarray): Custom y data to plot. - x(list | np.ndarray): Custom y data to plot. - x_name(str): Name of the x signal. - - "auto": Use the best effort signal. - - "timestamp": Use the timestamp signal. - - "index": Use the index signal. - - Custom signal name of a device from BEC. - y_name(str): The name of the device for the y-axis. - x_entry(str): The name of the entry for the x-axis. - y_entry(str): The name of the entry for the y-axis. - color(str): The color of the curve. - label(str): The label of the curve. - dap(str): The dap model to use for the curve, only available for sync devices. - If not specified, none will be added. - Use the same string as is the name of the LMFit model. - - Returns: - Curve: The curve object. - """ - - @rpc_call - def add_dap_curve( - self, - device_label: "str", - dap_name: "str", - color: "str | None" = None, - dap_oversample: "int" = 1, - **kwargs, - ) -> "Curve": - """ - Create a new DAP curve referencing the existing device curve `device_label`, - with the data processing model `dap_name`. - - Args: - device_label(str): The label of the device curve to add DAP to. - dap_name(str): The name of the DAP model to use. - color(str): The color of the curve. - dap_oversample(int): The oversampling factor for the DAP curve. - **kwargs - - Returns: - Curve: The new DAP curve. - """ - - @rpc_call - def remove_curve(self, curve: "int | str"): - """ - Remove a curve from the plot widget. - - Args: - curve(int|str): The curve to remove. It Can be the order of the curve or the name of the curve. - """ - - @rpc_call - def update_with_scan_history(self, scan_index: "int" = None, scan_id: "str" = None): - """ - Update the scan curves with the data from the scan storage. - Provide only one of scan_id or scan_index. - - Args: - scan_id(str, optional): ScanID of the scan to be updated. Defaults to None. - scan_index(int, optional): Index of the scan to be updated. Defaults to None. - """ - - @rpc_call - def get_dap_params(self) -> "dict[str, dict]": - """ - Get the DAP parameters of all DAP curves. - - Returns: - dict[str, dict]: DAP parameters of all DAP curves. - """ - - @rpc_call - def get_dap_summary(self) -> "dict[str, dict]": - """ - Get the DAP summary of all DAP curves. - - Returns: - dict[str, dict]: DAP summary of all DAP curves. - """ - - @rpc_call - def get_all_data(self, output: "Literal['dict', 'pandas']" = "dict") -> "dict": - """ - Extract all curve data into a dictionary or a pandas DataFrame. - - Args: - output (Literal["dict", "pandas"]): Format of the output data. - - Returns: - dict | pd.DataFrame: Data of all curves in the specified format. - """ - - @rpc_call - def get_curve(self, curve: "int | str") -> "Curve | None": - """ - Get a curve from the plot widget. - - Args: - curve(int|str): The curve to get. It Can be the order of the curve or the name of the curve. - - Return(Curve|None): The curve object if found, None otherwise. - """ - - @rpc_call - def select_roi(self, region: "tuple[float, float]"): - """ - Public method if you want the old `select_roi` style. - """ - - @rpc_call - def clear_all(self): - """ - Clear all curves from the plot widget. - """ - - -class WebsiteWidget(RPCBase): - """A simple widget to display a website""" - - @rpc_call - def set_url(self, url: str) -> None: - """ - Set the url of the website widget - - Args: - url (str): The url to set - """ - - @rpc_call - def get_url(self) -> str: - """ - Get the current url of the website widget - - Returns: - str: The current url - """ - - @rpc_call - def reload(self): - """ - Reload the website - """ - - @rpc_call - def back(self): - """ - Go back in the history - """ - - @rpc_call - def forward(self): - """ - Go forward in the history - """ diff --git a/bec_widgets/utils/bec_designer.py b/bec_widgets/utils/bec_designer.py index cb536cbd..a1353a3a 100644 --- a/bec_widgets/utils/bec_designer.py +++ b/bec_widgets/utils/bec_designer.py @@ -10,6 +10,8 @@ from bec_qthemes import material_icon from qtpy import PYSIDE6 from qtpy.QtGui import QIcon +from bec_widgets.utils.bec_plugin_helper import user_widget_plugin + if PYSIDE6: from PySide6.scripts.pyside_tool import ( _extend_path_var, @@ -150,7 +152,12 @@ def main(): # pragma: no cover print("PYSIDE6 is not available in the environment. Exiting...") return base_dir = Path(os.path.dirname(bec_widgets.__file__)).resolve() + plugin_paths = find_plugin_paths(base_dir) + if (plugin_repo := user_widget_plugin()) and isinstance(plugin_repo.__file__, str): + plugin_repo_dir = Path(os.path.dirname(plugin_repo.__file__)).resolve() + plugin_paths.extend(find_plugin_paths(plugin_repo_dir)) + set_plugin_environment_variable(plugin_paths) patch_designer() diff --git a/bec_widgets/utils/bec_plugin_helper.py b/bec_widgets/utils/bec_plugin_helper.py new file mode 100644 index 00000000..5d50fdcf --- /dev/null +++ b/bec_widgets/utils/bec_plugin_helper.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import importlib.metadata +import inspect +import pkgutil +from importlib import util as importlib_util +from importlib.machinery import FileFinder, ModuleSpec, SourceFileLoader +from types import ModuleType +from typing import Generator + +from bec_widgets.utils.bec_widget import BECWidget + + +def _submodule_specs(module: ModuleType) -> tuple[ModuleSpec | None, ...]: + """Return specs for all submodules of the given module.""" + return tuple( + module_info.module_finder.find_spec(module_info.name) + for module_info in pkgutil.iter_modules(module.__path__) + if isinstance(module_info.module_finder, FileFinder) + ) + + +def _loaded_submodules_from_specs( + submodule_specs: tuple[ModuleSpec | None, ...] +) -> Generator[ModuleType, None, None]: + """Load all submodules from the given specs.""" + for submodule in ( + importlib_util.module_from_spec(spec) for spec in submodule_specs if spec is not None + ): + assert isinstance( + submodule.__loader__, SourceFileLoader + ), "Module found from FileFinder should have SourceFileLoader!" + submodule.__loader__.exec_module(submodule) + yield submodule + + +def _submodule_by_name(module: ModuleType, name: str): + for submod in _loaded_submodules_from_specs(_submodule_specs(module)): + if submod.__name__ == name: + return submod + return None + + +def _get_widgets_from_module(module: ModuleType) -> dict[str, "type[BECWidget]"]: + """Find any BECWidget subclasses in the given module and return them with their names.""" + from bec_widgets.utils.bec_widget import BECWidget # avoid circular import + + return dict( + inspect.getmembers( + module, + predicate=lambda item: inspect.isclass(item) + and issubclass(item, BECWidget) + and item is not BECWidget, + ) + ) + + +def _all_widgets_from_all_submods(module): + """Recursively load submodules, find any BECWidgets, and return them all as a flat dict.""" + widgets = _get_widgets_from_module(module) + if not hasattr(module, "__path__"): + return widgets + for submod in _loaded_submodules_from_specs(_submodule_specs(module)): + widgets.update(_all_widgets_from_all_submods(submod)) + return widgets + + +def user_widget_plugin() -> ModuleType | None: + plugins = importlib.metadata.entry_points(group="bec.widgets.user_widgets") # type: ignore + return None if len(plugins) == 0 else tuple(plugins)[0].load() + + +def get_plugin_client_module() -> ModuleType | None: + """If there is a plugin repository installed, return the client module.""" + return _submodule_by_name(plugin, "client") if (plugin := user_widget_plugin()) else None + + +def get_all_plugin_widgets() -> dict[str, "type[BECWidget]"]: + """If there is a plugin repository installed, load all widgets from it.""" + if plugin := user_widget_plugin(): + return _all_widgets_from_all_submods(plugin) + else: + return {} + + +if __name__ == "__main__": # pragma: no cover + # print(get_all_plugin_widgets()) + client = get_plugin_client_module() + ... diff --git a/bec_widgets/utils/plugin_utils.py b/bec_widgets/utils/plugin_utils.py index afda74be..ed0146eb 100644 --- a/bec_widgets/utils/plugin_utils.py +++ b/bec_widgets/utils/plugin_utils.py @@ -63,6 +63,9 @@ class BECClassContainer: def __repr__(self): return str(list(cl.name for cl in self.collection)) + def __iter__(self): + return self._collection.__iter__() + def add_class(self, class_info: BECClassInfo): """ Add a class to the collection. diff --git a/bec_widgets/widgets/containers/dock/dock.py b/bec_widgets/widgets/containers/dock/dock.py index 6f0c5874..587b6be6 100644 --- a/bec_widgets/widgets/containers/dock/dock.py +++ b/bec_widgets/widgets/containers/dock/dock.py @@ -265,8 +265,9 @@ class BECDock(BECWidget, Dock): """ return list(widget_handler.widget_classes.keys()) - def _get_list_of_widget_name_of_parent_dock_area(self): - docks = self.parent_dock_area.panel_list + def _get_list_of_widget_name_of_parent_dock_area(self) -> list[str]: + if (docks := self.parent_dock_area.panel_list) is None: + return [] widgets = [] for dock in docks: widgets.extend(dock.elements.keys()) diff --git a/bec_widgets/widgets/utility/logpanel/logpanel.py b/bec_widgets/widgets/utility/logpanel/logpanel.py index 2eb2e4a6..f4d02ede 100644 --- a/bec_widgets/widgets/utility/logpanel/logpanel.py +++ b/bec_widgets/widgets/utility/logpanel/logpanel.py @@ -103,8 +103,8 @@ class BecLogsQueue: self._display_queue.append(self._line_formatter(_msg)) if self._new_message_signal: self._new_message_signal.emit() - except Exception: - logger.warning("Error in LogPanel incoming message callback!") + except Exception as e: + logger.warning(f"Error in LogPanel incoming message callback: {e}") def _set_formatter_and_update_filter(self, line_formatter: LineFormatter = noop_format): self._line_formatter: LineFormatter = line_formatter diff --git a/tests/end-2-end/test_bec_dock_rpc_e2e.py b/tests/end-2-end/test_bec_dock_rpc_e2e.py index 37effd0a..01758902 100644 --- a/tests/end-2-end/test_bec_dock_rpc_e2e.py +++ b/tests/end-2-end/test_bec_dock_rpc_e2e.py @@ -1,6 +1,6 @@ import pytest -from bec_widgets.cli import Image, MotorMap, Waveform +from bec_widgets.cli.client import Image, MotorMap, Waveform from bec_widgets.cli.rpc.rpc_base import RPCReference # pylint: disable=unused-argument diff --git a/tests/unit_tests/test_bec_plugin_helper.py b/tests/unit_tests/test_bec_plugin_helper.py new file mode 100644 index 00000000..94e8ff35 --- /dev/null +++ b/tests/unit_tests/test_bec_plugin_helper.py @@ -0,0 +1,61 @@ +from importlib.machinery import FileFinder, SourceFileLoader +from types import ModuleType +from unittest import mock + +from bec_widgets.utils.bec_plugin_helper import BECWidget, _all_widgets_from_all_submods + + +def test_all_widgets_from_module_no_submodules(): + """ + Test _all_widgets_from_all_submodules with a module that has no submodules. + """ + module = mock.MagicMock(spec=ModuleType) + + with mock.patch( + "bec_widgets.utils.bec_plugin_helper._get_widgets_from_module", + return_value={"TestWidget": BECWidget}, + ): + widgets = _all_widgets_from_all_submods(module) + + assert widgets == {"TestWidget": BECWidget} + + +def test_all_widgets_from_module_with_submodules(): + """ + Test _all_widgets_from_all_submodules with a module that has submodules. + """ + module = mock.MagicMock() + module.__path__ = ["path/to/module"] + + submodule = mock.MagicMock() + submodule.__loader__ = mock.MagicMock(spec=SourceFileLoader) + + finder_mock = mock.MagicMock(spec=FileFinder, return_value=True) + with ( + mock.patch( + "pkgutil.iter_modules", + return_value=[mock.MagicMock(module_finder=finder_mock, name="submodule")], + ), + mock.patch("importlib.util.module_from_spec", return_value=submodule), + mock.patch( + "bec_widgets.utils.bec_plugin_helper._get_widgets_from_module", + side_effect=[{"TestWidget": BECWidget}, {"SubWidget": BECWidget}], + ), + ): + widgets = _all_widgets_from_all_submods(module) + + assert widgets == {"TestWidget": BECWidget, "SubWidget": BECWidget} + + +def test_all_widgets_from_module_no_widgets(): + """ + Test _all_widgets_from_all_submodules with a module that has no widgets. + """ + module = mock.MagicMock() + + with mock.patch( + "bec_widgets.utils.bec_plugin_helper._get_widgets_from_module", return_value={} + ): + widgets = _all_widgets_from_all_submods(module) + + assert widgets == {} diff --git a/tests/unit_tests/test_client_plugin_widgets.py b/tests/unit_tests/test_client_plugin_widgets.py new file mode 100644 index 00000000..4bafb15a --- /dev/null +++ b/tests/unit_tests/test_client_plugin_widgets.py @@ -0,0 +1,60 @@ +import enum +import inspect +import sys +from importlib import reload +from types import SimpleNamespace +from unittest.mock import MagicMock, call, patch + +from bec_widgets.cli import client +from bec_widgets.cli.rpc.rpc_base import RPCBase + + +class _TestGlobalPlugin(RPCBase): ... + + +mock_client_module_globals = SimpleNamespace() +_TestGlobalPlugin.__name__ = "Widgets" +mock_client_module_globals.Widgets = _TestGlobalPlugin + + +@patch("bec_lib.logger.bec_logger") +@patch( + "bec_widgets.utils.bec_plugin_helper.get_plugin_client_module", + lambda: mock_client_module_globals, +) +def test_plugins_dont_clobber_client_globals(bec_logger: MagicMock): + reload(client) + bec_logger.logger.warning.assert_called_with( + "Plugin widget Widgets from namespace(Widgets=) conflicts with a built-in class!" + ) + if sys.version_info >= (3, 11): # No EnumType in python3.10 + assert isinstance(client.Widgets, enum.EnumType) + + +class _TestDuplicatePlugin(RPCBase): ... + + +mock_client_module_duplicate = SimpleNamespace() +_TestDuplicatePlugin.__name__ = "DeviceComboBox" + +mock_client_module_duplicate.DeviceComboBox = _TestDuplicatePlugin + + +@patch("bec_lib.logger.bec_logger") +@patch( + "bec_widgets.utils.bec_plugin_helper.get_plugin_client_module", + lambda: mock_client_module_duplicate, +) +@patch( + "bec_widgets.utils.bec_plugin_helper.get_all_plugin_widgets", + return_value={"DeviceComboBox": _TestDuplicatePlugin}, +) +def test_duplicate_plugins_not_allowed(_, bec_logger: MagicMock): + reload(client) + assert ( + call( + f"Detected duplicate widget DeviceComboBox in plugin repo file: {inspect.getfile(_TestDuplicatePlugin)} !" + ) + in bec_logger.logger.warning.mock_calls + ) + assert client.BECDock is not _TestDuplicatePlugin diff --git a/tests/unit_tests/test_generate_cli.py b/tests/unit_tests/test_generate_cli.py deleted file mode 100644 index 24b74e60..00000000 --- a/tests/unit_tests/test_generate_cli.py +++ /dev/null @@ -1,98 +0,0 @@ -from unittest import mock - -import pytest - -from bec_widgets.cli.generate_cli import BECClassContainer, ClientGenerator - - -def test_client_generator_init(): - """ - Test the initialization of the ClientGenerator class. - """ - generator = ClientGenerator() - assert generator.header.startswith("# This file was automatically generated by generate_cli.py") - assert generator.content == "" - - -def test_generate_client(): - """ - Test the generate_client method of the ClientGenerator class. - """ - generator = ClientGenerator() - class_container = mock.MagicMock(spec=BECClassContainer) - class_container.rpc_top_level_classes = [mock.MagicMock(RPC=True, __name__="TestClass1")] - class_container.connector_classes = [mock.MagicMock(RPC=True, __name__="TestClass2")] - - generator.generate_client(class_container) - - assert '"TestClass1": "TestClass1"' in generator.content - assert "class TestClass2(RPCBase):" in generator.content - - -@pytest.mark.parametrize("plugin", (True, False)) -def test_write_client_enum(plugin): - """ - Test the write_client_enum method of the ClientGenerator class. - """ - generator = ClientGenerator(base=plugin) - published_classes = [ - mock.MagicMock(__name__="TestClass1"), - mock.MagicMock(__name__="TestClass2"), - ] - - generator.write_client_enum(published_classes) - - assert ("class _WidgetsEnumType(str, enum.Enum):" in generator.content) is plugin - assert '"TestClass1": "TestClass1",' in generator.content - assert '"TestClass2": "TestClass2",' in generator.content - - -def test_generate_content_for_class(): - """ - Test the generate_content_for_class method of the ClientGenerator class. - """ - generator = ClientGenerator() - cls = mock.MagicMock(__name__="TestClass", USER_ACCESS=["method1"]) - method = mock.MagicMock() - method.__name__ = "method1" - method.__doc__ = "Test method" - method_signature = "(self)" - cls.method1 = method - - with mock.patch("inspect.signature", return_value=method_signature): - generator.generate_content_for_class(cls) - - assert "class TestClass(RPCBase):" in generator.content - assert "def method1(self):" in generator.content - assert "Test method" in generator.content - - -def test_write_is_black_formatted(tmp_path): - """ - Test the write method of the ClientGenerator class. - """ - generator = ClientGenerator() - generator.content = """ -def test_content(): - pass - -a=1 -b=2 -c=a+b -""" - - corrected = """def test_content(): - pass - - -a = 1 -b = 2 -c = a + b""" - file_name = tmp_path / "test_client.py" - - generator.write(str(file_name)) - - with open(file_name, "r", encoding="utf-8") as file: - content = file.read() - - assert corrected in content diff --git a/tests/unit_tests/test_generate_cli_client.py b/tests/unit_tests/test_generate_cli_client.py index f6655b39..bfc627d6 100644 --- a/tests/unit_tests/test_generate_cli_client.py +++ b/tests/unit_tests/test_generate_cli_client.py @@ -1,7 +1,9 @@ from textwrap import dedent +from unittest import mock import black import isort +import pytest from bec_widgets.cli.generate_cli import ClientGenerator from bec_widgets.utils.plugin_utils import BECClassContainer, BECClassInfo @@ -33,7 +35,7 @@ class MockBECFigure: def test_client_generator_with_black_formatting(): - generator = ClientGenerator() + generator = ClientGenerator(base=True) container = BECClassContainer() container.add_class( BECClassInfo( @@ -68,20 +70,51 @@ def test_client_generator_with_black_formatting(): from __future__ import annotations import enum - from typing import Literal, Optional, overload + import inspect + from typing import Literal, Optional + + from bec_lib.logger import bec_logger from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call + from bec_widgets.utils.bec_plugin_helper import (get_all_plugin_widgets, + get_plugin_client_module) + + logger = bec_logger.logger # pylint: skip-file - class Widgets(str, enum.Enum): - """ - Enum for the available widgets. - """ + class _WidgetsEnumType(str, enum.Enum): + """Enum for the available widgets, to be generated programatically""" - MockBECFigure = "MockBECFigure" + ... + _Widgets = { + "MockBECFigure": "MockBECFigure", + } + + + _plugin_widgets = get_all_plugin_widgets() + plugin_client = get_plugin_client_module() + Widgets = _WidgetsEnumType("Widgets", {name: name for name in _plugin_widgets} | _Widgets) + + if (_overlap := _Widgets.keys() & _plugin_widgets.keys()) != set(): + for _widget in _overlap: + logger.warning(f"Detected duplicate widget {_widget} in plugin repo file: {inspect.getfile(_plugin_widgets[_widget])} !") + for plugin_name, plugin_class in inspect.getmembers(plugin_client, inspect.isclass): + if issubclass(plugin_class, RPCBase) and plugin_class is not RPCBase: + if plugin_name in globals(): + conflicting_file = ( + inspect.getfile(_plugin_widgets[plugin_name]) + if plugin_name in _plugin_widgets + else f"{plugin_client}" + ) + logger.warning( + f"Plugin widget {plugin_name} from {conflicting_file} conflicts with a built-in class!" + ) + continue + if plugin_name not in _overlap: + globals()[plugin_name] = plugin_class class MockBECFigure(RPCBase): @rpc_call @@ -121,5 +154,99 @@ def test_client_generator_with_black_formatting(): ) generated_output_formatted = isort.code(generated_output_formatted) + expected_output_formatted = isort.code(expected_output_formatted) assert expected_output_formatted == generated_output_formatted + + +def test_client_generator_init(): + """ + Test the initialization of the ClientGenerator class. + """ + generator = ClientGenerator() + assert generator.header.startswith("# This file was automatically generated by generate_cli.py") + assert generator.content == "" + + +def test_generate_client(): + """ + Test the generate_client method of the ClientGenerator class. + """ + generator = ClientGenerator() + class_container = mock.MagicMock(spec=BECClassContainer) + class_container.rpc_top_level_classes = [mock.MagicMock(RPC=True, __name__="TestClass1")] + class_container.connector_classes = [mock.MagicMock(RPC=True, __name__="TestClass2")] + + generator.generate_client(class_container) + + assert '"TestClass1": "TestClass1"' in generator.content + assert "class TestClass2(RPCBase):" in generator.content + + +@pytest.mark.parametrize("plugin", (True, False)) +def test_write_client_enum(plugin): + """ + Test the write_client_enum method of the ClientGenerator class. + """ + generator = ClientGenerator(base=plugin) + published_classes = [ + mock.MagicMock(__name__="TestClass1"), + mock.MagicMock(__name__="TestClass2"), + ] + + generator.write_client_enum(published_classes) + + assert ("class _WidgetsEnumType(str, enum.Enum):" in generator.content) is plugin + assert '"TestClass1": "TestClass1",' in generator.content + assert '"TestClass2": "TestClass2",' in generator.content + + +def test_generate_content_for_class(): + """ + Test the generate_content_for_class method of the ClientGenerator class. + """ + generator = ClientGenerator() + cls = mock.MagicMock(__name__="TestClass", USER_ACCESS=["method1"]) + method = mock.MagicMock() + method.__name__ = "method1" + method.__doc__ = "Test method" + method_signature = "(self)" + cls.method1 = method + + with mock.patch("inspect.signature", return_value=method_signature): + generator.generate_content_for_class(cls) + + assert "class TestClass(RPCBase):" in generator.content + assert "def method1(self):" in generator.content + assert "Test method" in generator.content + + +def test_write_is_black_formatted(tmp_path): + """ + Test the write method of the ClientGenerator class. + """ + generator = ClientGenerator() + generator.content = """ +def test_content(): + pass + +a=1 +b=2 +c=a+b +""" + + corrected = """def test_content(): + pass + + +a = 1 +b = 2 +c = a + b""" + file_name = tmp_path / "test_client.py" + + generator.write(str(file_name)) + + with open(file_name, "r", encoding="utf-8") as file: + content = file.read() + + assert corrected in content diff --git a/tests/unit_tests/test_rpc_widget_handler.py b/tests/unit_tests/test_rpc_widget_handler.py index 84677976..cbdea1a8 100644 --- a/tests/unit_tests/test_rpc_widget_handler.py +++ b/tests/unit_tests/test_rpc_widget_handler.py @@ -1,7 +1,29 @@ +import enum +from importlib import reload +from types import SimpleNamespace +from unittest.mock import MagicMock, call, patch + +from bec_widgets.cli import client +from bec_widgets.cli.rpc.rpc_base import RPCBase from bec_widgets.cli.rpc.rpc_widget_handler import RPCWidgetHandler +from bec_widgets.utils.bec_widget import BECWidget +from bec_widgets.widgets.containers.dock.dock import BECDock def test_rpc_widget_handler(): handler = RPCWidgetHandler() assert "Image" in handler.widget_classes assert "RingProgressBar" in handler.widget_classes + + +class _TestPluginWidget(BECWidget): ... + + +@patch( + "bec_widgets.cli.rpc.rpc_widget_handler.get_all_plugin_widgets", + return_value={"DeviceComboBox": _TestPluginWidget, "NewPluginWidget": _TestPluginWidget}, +) +def test_duplicate_plugins_not_allowed(_): + handler = RPCWidgetHandler() + assert handler.widget_classes["DeviceComboBox"] is not _TestPluginWidget + assert handler.widget_classes["NewPluginWidget"] is _TestPluginWidget