From e94ce73950b97f85ba57ffca0eb45dc60f0fefce Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Tue, 13 Jan 2026 11:32:19 +0100 Subject: [PATCH] fix: sanitize name space util for bec connector and ads --- bec_widgets/utils/bec_connector.py | 3 +++ bec_widgets/utils/name_utils.py | 19 ++++++++++++++++++ .../advanced_dock_area/advanced_dock_area.py | 4 +++- .../advanced_dock_area/basic_dock_area.py | 8 +++++++- .../advanced_dock_area/profile_utils.py | 20 +------------------ 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/bec_widgets/utils/bec_connector.py b/bec_widgets/utils/bec_connector.py index 6b7ef0c5..91d53e33 100644 --- a/bec_widgets/utils/bec_connector.py +++ b/bec_widgets/utils/bec_connector.py @@ -16,6 +16,7 @@ from qtpy.QtWidgets import QApplication from bec_widgets.cli.rpc.rpc_register import RPCRegister from bec_widgets.utils.error_popups import ErrorPopupUtility, SafeSlot +from bec_widgets.utils.name_utils import sanitize_namespace from bec_widgets.utils.widget_io import WidgetHierarchy from bec_widgets.utils.yaml_dialog import load_yaml, load_yaml_gui, save_yaml, save_yaml_gui @@ -102,6 +103,8 @@ class BECConnector: """ # Extract object_name from kwargs to not pass it to Qt class object_name = object_name or kwargs.pop("objectName", None) + if object_name is not None: + object_name = sanitize_namespace(object_name) # Ensure the parent is always the first argument for QObject parent = kwargs.pop("parent", None) # This initializes the QObject or any qt related class BECConnector has to be used from this line down with QObject, otherwise hierarchy logic will not work diff --git a/bec_widgets/utils/name_utils.py b/bec_widgets/utils/name_utils.py index 02fb7b2d..45c15f05 100644 --- a/bec_widgets/utils/name_utils.py +++ b/bec_widgets/utils/name_utils.py @@ -14,3 +14,22 @@ def pascal_to_snake(name: str) -> str: 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() + + +def sanitize_namespace(namespace: str | None) -> str | None: + """ + Clean user-provided namespace labels for filesystem compatibility. + + Args: + namespace (str | None): Arbitrary namespace identifier supplied by the caller. + + Returns: + str | None: Sanitized namespace containing only safe characters, or ``None`` + when the input is empty. + """ + if not namespace: + return None + ns = namespace.strip() + if not ns: + return None + return re.sub(r"[^0-9A-Za-z._-]+", "_", ns) diff --git a/bec_widgets/widgets/containers/advanced_dock_area/advanced_dock_area.py b/bec_widgets/widgets/containers/advanced_dock_area/advanced_dock_area.py index a112d26d..ccd01985 100644 --- a/bec_widgets/widgets/containers/advanced_dock_area/advanced_dock_area.py +++ b/bec_widgets/widgets/containers/advanced_dock_area/advanced_dock_area.py @@ -21,6 +21,7 @@ from bec_widgets import BECWidget, SafeProperty, SafeSlot from bec_widgets.cli.rpc.rpc_widget_handler import widget_handler from bec_widgets.utils import BECDispatcher from bec_widgets.utils.colors import apply_theme +from bec_widgets.utils.name_utils import sanitize_namespace from bec_widgets.utils.toolbars.actions import ( ExpandableMenuAction, MaterialIconAction, @@ -48,7 +49,6 @@ from bec_widgets.widgets.containers.advanced_dock_area.profile_utils import ( profile_origin_display, read_manifest, restore_user_from_default, - sanitize_namespace, set_last_profile, set_quick_select, user_profile_candidates, @@ -277,6 +277,7 @@ class AdvancedDockArea(DockAreaWidget): title_buttons: Mapping[str, bool] | Sequence[str] | str | None = None, show_settings_action: bool | None = None, promote_central: bool = False, + object_name: str | None = None, **widget_kwargs, ) -> QWidget | CDockWidget | BECWidget: """ @@ -301,6 +302,7 @@ class AdvancedDockArea(DockAreaWidget): title_buttons=title_buttons, show_settings_action=show_settings_action, promote_central=promote_central, + object_name=object_name, **widget_kwargs, ) diff --git a/bec_widgets/widgets/containers/advanced_dock_area/basic_dock_area.py b/bec_widgets/widgets/containers/advanced_dock_area/basic_dock_area.py index 3331619d..1c9c5ed3 100644 --- a/bec_widgets/widgets/containers/advanced_dock_area/basic_dock_area.py +++ b/bec_widgets/widgets/containers/advanced_dock_area/basic_dock_area.py @@ -1227,6 +1227,7 @@ class DockAreaWidget(BECWidget, QWidget): promote_central: bool = False, dock_icon: QIcon | None = None, apply_widget_icon: bool = True, + object_name: str | None = None, **widget_kwargs, ) -> QWidget | CDockWidget | BECWidget: """ @@ -1262,6 +1263,9 @@ class DockAreaWidget(BECWidget, QWidget): the widget's ``ICON_NAME`` attribute is used when available. apply_widget_icon(bool): When False, skip automatically resolving the icon from the widget's ``ICON_NAME`` (useful for callers who want no icon and do not pass one explicitly). + object_name(str | None): Optional object name to assign to the created widget. + **widget_kwargs: Additional keyword arguments passed to the widget constructor + when creating by type name. Returns: The widget instance by default, or the created `CDockWidget` when `return_dock` is True. @@ -1273,7 +1277,9 @@ class DockAreaWidget(BECWidget, QWidget): ) widget = cast( BECWidget, - widget_handler.create_widget(widget_type=widget, parent=self, **widget_kwargs), + widget_handler.create_widget( + widget_type=widget, parent=self, object_name=object_name, **widget_kwargs + ), ) spec = self._build_creation_spec( diff --git a/bec_widgets/widgets/containers/advanced_dock_area/profile_utils.py b/bec_widgets/widgets/containers/advanced_dock_area/profile_utils.py index 87f03968..406e5037 100644 --- a/bec_widgets/widgets/containers/advanced_dock_area/profile_utils.py +++ b/bec_widgets/widgets/containers/advanced_dock_area/profile_utils.py @@ -24,6 +24,7 @@ from qtpy.QtCore import QByteArray, QDateTime, QSettings, Qt from qtpy.QtGui import QPixmap from qtpy.QtWidgets import QApplication +from bec_widgets.utils.name_utils import sanitize_namespace from bec_widgets.widgets.containers.qt_ads import CDockWidget logger = bec_logger.logger @@ -124,25 +125,6 @@ def _settings_profiles_root() -> str: return root -def sanitize_namespace(namespace: str | None) -> str | None: - """ - Clean user-provided namespace labels for filesystem compatibility. - - Args: - namespace (str | None): Arbitrary namespace identifier supplied by the caller. - - Returns: - str | None: Sanitized namespace containing only safe characters, or ``None`` - when the input is empty. - """ - if not namespace: - return None - ns = namespace.strip() - if not ns: - return None - return re.sub(r"[^0-9A-Za-z._-]+", "_", ns) - - def _profiles_dir(segment: str, namespace: str | None) -> str: """ Build (and ensure) the directory that holds profiles for a namespace segment.