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

fix: ensure provided dock and dock_area names are valid and defaults are snake_case

This commit is contained in:
2025-04-23 15:55:23 +02:00
parent 1910993b2b
commit 0ac14a74b8
5 changed files with 53 additions and 35 deletions

View File

@ -231,6 +231,10 @@ class LaunchWindow(BECMainWindow):
raise ValueError(
f"Name {name} must be unique for dock areas, but already exists: {existing_dock_areas}."
)
if not WidgetContainerUtils.has_name_valid_chars(name):
raise ValueError(
f"Name {name} contains invalid characters. Only alphanumeric characters, underscores, and dashes are allowed."
)
else:
name = "dock_area"
name = WidgetContainerUtils.generate_unique_name(name, existing_dock_areas)

View File

@ -5,6 +5,8 @@ from typing import NamedTuple
from qtpy.QtCore import QObject
from bec_widgets.utils.name_utils import pascal_to_snake
EXCLUDED_PLUGINS = ["BECConnector", "BECDockArea", "BECDock", "BECFigure"]
@ -22,7 +24,7 @@ class DesignerPluginInfo:
def __init__(self, plugin_class):
self.plugin_class = plugin_class
self.plugin_name_pascal = plugin_class.__name__
self.plugin_name_snake = self.pascal_to_snake(self.plugin_name_pascal)
self.plugin_name_snake = pascal_to_snake(self.plugin_name_pascal)
self.widget_import = f"from {plugin_class.__module__} import {self.plugin_name_pascal}"
plugin_module = (
".".join(plugin_class.__module__.split(".")[:-1]) + f".{self.plugin_name_snake}_plugin"
@ -38,21 +40,6 @@ class DesignerPluginInfo:
self.base_path = os.path.dirname(inspect.getfile(plugin_class))
@staticmethod
def pascal_to_snake(name: str) -> str:
"""
Convert PascalCase to snake_case.
Args:
name (str): The name to be converted.
Returns:
str: The converted name.
"""
s1 = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name)
s2 = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s1)
return s2.lower()
class DesignerPluginGenerator:
def __init__(self, widget: type):

View File

@ -0,0 +1,16 @@
import re
def pascal_to_snake(name: str) -> str:
"""
Convert PascalCase to snake_case.
Args:
name (str): The name to be converted.
Returns:
str: The converted name.
"""
s1 = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name)
s2 = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s1)
return s2.lower()

View File

@ -14,6 +14,7 @@ from bec_widgets.cli.rpc.rpc_register import RPCRegister
from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.name_utils import pascal_to_snake
from bec_widgets.utils.toolbar import (
ExpandableMenuAction,
MaterialIconAction,
@ -242,7 +243,8 @@ class BECDockArea(BECWidget, QWidget):
def _create_widget_from_toolbar(self, widget_name: str) -> None:
# Run with RPC broadcast to namespace of all widgets
with RPCRegister.delayed_broadcast():
dock_name = WidgetContainerUtils.generate_unique_name(widget_name, self.panels.keys())
name = pascal_to_snake(widget_name)
dock_name = WidgetContainerUtils.generate_unique_name(name, self.panels.keys())
self.new(name=dock_name, widget=widget_name)
def paintEvent(self, event: QPaintEvent): # TODO decide if we want any default instructions
@ -362,6 +364,11 @@ class BECDockArea(BECWidget, QWidget):
f"Name {name} must be unique for docks, but already exists in DockArea "
f"with name: {self.object_name} and id {self.gui_id}."
)
if not WidgetContainerUtils.has_name_valid_chars(name):
raise ValueError(
f"Name {name} contains invalid characters. "
f"Only alphanumeric characters and underscores are allowed."
)
else: # Name is not provided
name = WidgetContainerUtils.generate_unique_name(name="dock", list_of_names=dock_names)

View File

@ -94,41 +94,45 @@ def test_undock_and_dock_docks(bec_dock_area, qtbot):
###################################
def test_toolbar_add_plot_waveform(bec_dock_area):
bec_dock_area.toolbar.widgets["menu_plots"].widgets["waveform"].trigger()
assert "Waveform_0" in bec_dock_area.panels
assert bec_dock_area.panels["Waveform_0"].widgets[0].config.widget_class == "Waveform"
assert "waveform_0" in bec_dock_area.panels
assert bec_dock_area.panels["waveform_0"].widgets[0].config.widget_class == "Waveform"
def test_toolbar_add_plot_scatter_waveform(bec_dock_area):
bec_dock_area.toolbar.widgets["menu_plots"].widgets["scatter_waveform"].trigger()
assert "ScatterWaveform_0" in bec_dock_area.panels
assert "scatter_waveform_0" in bec_dock_area.panels
assert (
bec_dock_area.panels["ScatterWaveform_0"].widgets[0].config.widget_class
bec_dock_area.panels["scatter_waveform_0"].widgets[0].config.widget_class
== "ScatterWaveform"
)
def test_toolbar_add_plot_image(bec_dock_area):
bec_dock_area.toolbar.widgets["menu_plots"].widgets["image"].trigger()
assert "Image_0" in bec_dock_area.panels
assert bec_dock_area.panels["Image_0"].widgets[0].config.widget_class == "Image"
assert "image_0" in bec_dock_area.panels
assert bec_dock_area.panels["image_0"].widgets[0].config.widget_class == "Image"
def test_toolbar_add_plot_motor_map(bec_dock_area):
bec_dock_area.toolbar.widgets["menu_plots"].widgets["motor_map"].trigger()
assert "MotorMap_0" in bec_dock_area.panels
assert bec_dock_area.panels["MotorMap_0"].widgets[0].config.widget_class == "MotorMap"
assert "motor_map_0" in bec_dock_area.panels
assert bec_dock_area.panels["motor_map_0"].widgets[0].config.widget_class == "MotorMap"
def test_toolbar_add_multi_waveform(bec_dock_area):
bec_dock_area.toolbar.widgets["menu_plots"].widgets["multi_waveform"].trigger()
assert "MultiWaveform_0" in bec_dock_area.panels
assert bec_dock_area.panels["MultiWaveform_0"].widgets[0].config.widget_class == "MultiWaveform"
assert "multi_waveform_0" in bec_dock_area.panels
assert (
bec_dock_area.panels["multi_waveform_0"].widgets[0].config.widget_class == "MultiWaveform"
)
def test_toolbar_add_device_positioner_box(bec_dock_area):
bec_dock_area.toolbar.widgets["menu_devices"].widgets["positioner_box"].trigger()
assert "PositionerBox_0" in bec_dock_area.panels
assert bec_dock_area.panels["PositionerBox_0"].widgets[0].config.widget_class == "PositionerBox"
assert "positioner_box_0" in bec_dock_area.panels
assert (
bec_dock_area.panels["positioner_box_0"].widgets[0].config.widget_class == "PositionerBox"
)
def test_toolbar_add_utils_queue(bec_dock_area, bec_queue_msg_full):
@ -136,20 +140,20 @@ def test_toolbar_add_utils_queue(bec_dock_area, bec_queue_msg_full):
MessageEndpoints.scan_queue_status(), bec_queue_msg_full
)
bec_dock_area.toolbar.widgets["menu_utils"].widgets["queue"].trigger()
assert "BECQueue_0" in bec_dock_area.panels
assert bec_dock_area.panels["BECQueue_0"].widgets[0].config.widget_class == "BECQueue"
assert "bec_queue_0" in bec_dock_area.panels
assert bec_dock_area.panels["bec_queue_0"].widgets[0].config.widget_class == "BECQueue"
def test_toolbar_add_utils_status(bec_dock_area):
bec_dock_area.toolbar.widgets["menu_utils"].widgets["status"].trigger()
assert "BECStatusBox_0" in bec_dock_area.panels
assert bec_dock_area.panels["BECStatusBox_0"].widgets[0].config.widget_class == "BECStatusBox"
assert "bec_status_box_0" in bec_dock_area.panels
assert bec_dock_area.panels["bec_status_box_0"].widgets[0].config.widget_class == "BECStatusBox"
def test_toolbar_add_utils_progress_bar(bec_dock_area):
bec_dock_area.toolbar.widgets["menu_utils"].widgets["progress_bar"].trigger()
assert "RingProgressBar_0" in bec_dock_area.panels
assert "ring_progress_bar_0" in bec_dock_area.panels
assert (
bec_dock_area.panels["RingProgressBar_0"].widgets[0].config.widget_class
bec_dock_area.panels["ring_progress_bar_0"].widgets[0].config.widget_class
== "RingProgressBar"
)