1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-05-09 16:22:08 +02:00

Compare commits

...

6 Commits

88 changed files with 615 additions and 434 deletions
+12 -10
View File
@@ -1,19 +1,21 @@
import os import os
import sys import sys
import bec_widgets.widgets.containers.qt_ads as QtAds
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
if sys.platform.startswith("linux"): if sys.platform.startswith("linux"):
qt_platform = os.environ.get("QT_QPA_PLATFORM", "") qt_platform = os.environ.get("QT_QPA_PLATFORM", "")
if qt_platform != "offscreen": if qt_platform != "offscreen":
os.environ["QT_QPA_PLATFORM"] = "xcb" os.environ["QT_QPA_PLATFORM"] = "xcb"
# Default QtAds configuration
QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.eConfigFlag.FocusHighlighting, True)
QtAds.CDockManager.setConfigFlag(
QtAds.CDockManager.eConfigFlag.RetainTabSizeWhenCloseButtonHidden, True
)
__all__ = ["BECWidget", "SafeSlot", "SafeProperty"] __all__ = ["BECWidget", "SafeSlot", "SafeProperty"]
def __getattr__(name: str):
if name == "BECWidget":
from bec_widgets.utils.bec_widget import BECWidget
return BECWidget
if name in {"SafeProperty", "SafeSlot"}:
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
return {"SafeProperty": SafeProperty, "SafeSlot": SafeSlot}[name]
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -1,2 +0,0 @@
from .config_choice_dialog import ConfigChoiceDialog
from .device_form_dialog import DeviceFormDialog
@@ -8,14 +8,16 @@ from ophyd_devices.interfaces.device_config_templates.ophyd_templates import OPH
from qtpy import QtCore, QtWidgets from qtpy import QtCore, QtWidgets
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.widgets.control.device_manager.components import OphydValidation
from bec_widgets.widgets.control.device_manager.components.device_config_template.device_config_template import ( from bec_widgets.widgets.control.device_manager.components.device_config_template.device_config_template import (
DeviceConfigTemplate, DeviceConfigTemplate,
) )
from bec_widgets.widgets.control.device_manager.components.device_config_template.template_items import ( from bec_widgets.widgets.control.device_manager.components.device_config_template.template_items import (
validate_name, validate_name,
) )
from bec_widgets.widgets.control.device_manager.components.ophyd_validation import ( from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation import (
OphydValidation,
)
from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation_utils import (
ConfigStatus, ConfigStatus,
ConnectionStatus, ConnectionStatus,
format_error_to_md, format_error_to_md,
@@ -12,7 +12,7 @@ from qtpy import QtCore, QtGui, QtWidgets
from bec_widgets.utils.colors import get_accent_colors from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.widgets.control.device_manager.components.ophyd_validation import ( from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation_utils import (
ConfigStatus, ConfigStatus,
ConnectionStatus, ConnectionStatus,
get_validation_icons, get_validation_icons,
@@ -13,6 +13,7 @@ from bec_lib.file_utils import DeviceConfigWriter
from bec_lib.logger import bec_logger from bec_lib.logger import bec_logger
from bec_lib.messages import ConfigAction, ScanStatusMessage from bec_lib.messages import ConfigAction, ScanStatusMessage
from bec_lib.plugin_helper import plugin_package_name, plugin_repo_path from bec_lib.plugin_helper import plugin_package_name, plugin_repo_path
from bec_lib.utils.import_utils import lazy_import_from
from bec_qthemes import apply_theme, material_icon from bec_qthemes import apply_theme, material_icon
from qtpy.QtCore import QMetaObject, Qt, QThreadPool, Signal from qtpy.QtCore import QMetaObject, Qt, QThreadPool, Signal
from qtpy.QtGui import QColor from qtpy.QtGui import QColor
@@ -26,26 +27,18 @@ from qtpy.QtWidgets import (
QWidget, QWidget,
) )
from bec_widgets.applications.views.device_manager_view.device_manager_dialogs import (
ConfigChoiceDialog,
DeviceFormDialog,
)
from bec_widgets.applications.views.device_manager_view.device_manager_dialogs.upload_redis_dialog import (
UploadRedisDialog,
)
from bec_widgets.utils.colors import get_accent_colors from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.toolbars.actions import MaterialIconAction from bec_widgets.utils.toolbars.actions import MaterialIconAction
from bec_widgets.utils.toolbars.bundles import ToolbarBundle from bec_widgets.utils.toolbars.bundles import ToolbarBundle
from bec_widgets.utils.toolbars.toolbar import ModularToolBar from bec_widgets.utils.toolbars.toolbar import ModularToolBar
from bec_widgets.widgets.containers.dock_area.basic_dock_area import DockAreaWidget from bec_widgets.widgets.containers.dock_area.basic_dock_area import DockAreaWidget
from bec_widgets.widgets.control.device_manager.components import (
DeviceTable,
DMConfigView,
DocstringView,
OphydValidation,
)
from bec_widgets.widgets.control.device_manager.components._util import SharedSelectionSignal from bec_widgets.widgets.control.device_manager.components._util import SharedSelectionSignal
from bec_widgets.widgets.control.device_manager.components.device_table.device_table import (
DeviceTable,
)
from bec_widgets.widgets.control.device_manager.components.dm_config_view import DMConfigView
from bec_widgets.widgets.control.device_manager.components.dm_docstring_view import DocstringView
from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation_utils import ( from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation_utils import (
ConfigStatus, ConfigStatus,
ConnectionStatus, ConnectionStatus,
@@ -61,8 +54,29 @@ from bec_widgets.widgets.utility.spinner.spinner import SpinnerWidget
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from bec_lib.client import BECClient from bec_lib.client import BECClient
from bec_widgets.applications.views.device_manager_view.device_manager_dialogs.upload_redis_dialog import (
UploadRedisDialog,
)
logger = bec_logger.logger logger = bec_logger.logger
ConfigChoiceDialog = lazy_import_from(
"bec_widgets.applications.views.device_manager_view.device_manager_dialogs.config_choice_dialog",
("ConfigChoiceDialog",),
)
DeviceFormDialog = lazy_import_from(
"bec_widgets.applications.views.device_manager_view.device_manager_dialogs.device_form_dialog",
("DeviceFormDialog",),
)
UploadRedisDialog = lazy_import_from(
"bec_widgets.applications.views.device_manager_view.device_manager_dialogs.upload_redis_dialog",
("UploadRedisDialog",),
)
OphydValidation = lazy_import_from(
"bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation",
("OphydValidation",),
)
_yes_no_question = partial( _yes_no_question = partial(
QMessageBox.question, QMessageBox.question,
buttons=QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, buttons=QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
@@ -1,14 +1,17 @@
"""Module for Device Manager View.""" """Module for Device Manager View."""
from bec_lib.utils.import_utils import lazy_import_from
from qtpy.QtCore import QRect from qtpy.QtCore import QRect
from qtpy.QtWidgets import QWidget from qtpy.QtWidgets import QWidget
from bec_widgets.applications.views.device_manager_view.device_manager_widget import (
DeviceManagerWidget,
)
from bec_widgets.applications.views.view import ViewBase, ViewTourSteps from bec_widgets.applications.views.view import ViewBase, ViewTourSteps
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
DeviceManagerWidget = lazy_import_from(
"bec_widgets.applications.views.device_manager_view.device_manager_widget",
("DeviceManagerWidget",),
)
class DeviceManagerView(ViewBase): class DeviceManagerView(ViewBase):
""" """
@@ -6,15 +6,18 @@ import os
from bec_lib.bec_yaml_loader import yaml_load from bec_lib.bec_yaml_loader import yaml_load
from bec_lib.logger import bec_logger from bec_lib.logger import bec_logger
from bec_lib.utils.import_utils import lazy_import_from
from bec_qthemes import material_icon from bec_qthemes import material_icon
from qtpy import QtCore, QtWidgets from qtpy import QtCore, QtWidgets
from bec_widgets.applications.views.device_manager_view.device_manager_display_widget import (
DeviceManagerDisplayWidget,
)
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
DeviceManagerDisplayWidget = lazy_import_from(
"bec_widgets.applications.views.device_manager_view.device_manager_display_widget",
("DeviceManagerDisplayWidget",),
)
logger = bec_logger.logger logger = bec_logger.logger
-184
View File
@@ -3172,190 +3172,6 @@ class LogPanel(RPCBase):
class Minesweeper(RPCBase): ... class Minesweeper(RPCBase): ...
class MonacoDock(RPCBase):
"""MonacoDock is a dock widget that contains Monaco editor instances."""
@rpc_call
def new(
self,
widget: "QWidget | str",
*,
closable: "bool" = True,
floatable: "bool" = True,
movable: "bool" = True,
start_floating: "bool" = False,
floating_state: "Mapping[str, object] | None" = None,
where: "Literal['left', 'right', 'top', 'bottom'] | None" = None,
on_close: "Callable[[CDockWidget, QWidget], None] | None" = None,
tab_with: "CDockWidget | QWidget | str | None" = None,
relative_to: "CDockWidget | QWidget | str | None" = None,
return_dock: "bool" = False,
show_title_bar: "bool | None" = None,
title_buttons: "Mapping[str, bool] | Sequence[str] | str | None" = None,
show_settings_action: "bool | None" = False,
promote_central: "bool" = False,
dock_icon: "QIcon | None" = None,
apply_widget_icon: "bool" = True,
object_name: "str | None" = None,
**widget_kwargs,
) -> "QWidget | CDockWidget | BECWidget":
"""
Create a new widget (or reuse an instance) and add it as a dock.
Args:
widget(QWidget | str): Instance or registered widget type string.
closable(bool): Whether the dock is closable.
floatable(bool): Whether the dock is floatable.
movable(bool): Whether the dock is movable.
start_floating(bool): Whether to start the dock floating.
floating_state(Mapping | None): Optional floating geometry metadata to apply when floating.
where(Literal["left", "right", "top", "bottom"] | None): Dock placement hint relative to the dock area (ignored when
``relative_to`` is provided without an explicit value).
on_close(Callable[[CDockWidget, QWidget], None] | None): Optional custom close handler accepting (dock, widget).
tab_with(CDockWidget | QWidget | str | None): Existing dock (or widget/name) to tab the new dock alongside.
relative_to(CDockWidget | QWidget | str | None): Existing dock (or widget/name) used as the positional anchor.
When supplied and ``where`` is ``None``, the new dock inherits the
anchor's current dock area.
return_dock(bool): When True, return the created dock instead of the widget.
show_title_bar(bool | None): Explicitly show or hide the dock area's title bar.
title_buttons(Mapping[str, bool] | Sequence[str] | str | None): Mapping or iterable describing which title bar buttons should
remain visible. Provide a mapping of button names (``"float"``,
``"close"``, ``"menu"``, ``"auto_hide"``, ``"minimize"``) to booleans,
or a sequence of button names to hide.
show_settings_action(bool | None): Control whether a dock settings/property action should
be installed. Defaults to ``False`` for the basic dock area; subclasses
such as `BECDockArea` override the default to ``True``.
promote_central(bool): When True, promote the created dock to be the dock manager's
central widget (useful for editor stacks or other root content).
dock_icon(QIcon | None): Optional icon applied to the dock via ``CDockWidget.setIcon``.
Provide a `QIcon` (e.g. from ``material_icon``). When ``None`` (default),
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.
"""
@rpc_call
def dock_map(self) -> "dict[str, CDockWidget]":
"""
Return the dock widgets map as dictionary with names as keys.
"""
@rpc_call
def dock_list(self) -> "list[CDockWidget]":
"""
Return the list of dock widgets.
"""
@rpc_call
def widget_map(self, bec_widgets_only: "bool" = True) -> "dict[str, QWidget]":
"""
Return a dictionary mapping widget names to their corresponding widgets.
Args:
bec_widgets_only(bool): If True, only include widgets that are BECConnector instances.
"""
@rpc_call
def widget_list(self, bec_widgets_only: "bool" = True) -> "list[QWidget]":
"""
Return a list of widgets contained in the dock area.
Args:
bec_widgets_only(bool): If True, only include widgets that are BECConnector instances.
"""
@rpc_call
def attach_all(self):
"""
Re-attach floating docks back into the dock manager.
"""
@rpc_call
def delete_all(self):
"""
Delete all docks and their associated widgets.
"""
@rpc_call
def delete(self, object_name: "str") -> "bool":
"""
Remove a widget from the dock area by its object name.
Args:
object_name: The object name of the widget to remove.
Returns:
bool: True if the widget was found and removed, False otherwise.
Raises:
ValueError: If no widget with the given object name is found.
Example:
>>> dock_area.delete("my_widget")
True
"""
@rpc_call
def set_layout_ratios(
self,
*,
horizontal: "Sequence[float] | Mapping[int | str, float] | None" = None,
vertical: "Sequence[float] | Mapping[int | str, float] | None" = None,
splitter_overrides: "Mapping[int | str | Sequence[int], Sequence[float] | Mapping[int | str, float]] | None" = None,
) -> "None":
"""
Adjust splitter ratios in the dock layout.
Args:
horizontal: Weights applied to every horizontal splitter encountered.
vertical: Weights applied to every vertical splitter encountered.
splitter_overrides: Optional overrides targeting specific splitters identified
by their index path (e.g. ``{0: [1, 2], (1, 0): [3, 5]}``). Paths are zero-based
indices following the splitter hierarchy, starting from the root splitter.
Example:
To build three columns with custom per-column ratios::
area.set_layout_ratios(
horizontal=[1, 2, 1], # column widths
splitter_overrides={
0: [1, 2], # column 0 (two rows)
1: [3, 2, 1], # column 1 (three rows)
2: [1], # column 2 (single row)
},
)
"""
@rpc_call
def describe_layout(self) -> "list[dict[str, Any]]":
"""
Return metadata describing splitter paths, orientations, and contained docks.
Useful for determining the keys to use in `set_layout_ratios(splitter_overrides=...)`.
"""
@rpc_call
def print_layout_structure(self) -> "None":
"""
Pretty-print the current splitter paths to stdout.
"""
@rpc_call
def set_central_dock(self, dock: "CDockWidget | QWidget | str") -> "None":
"""
Promote an existing dock to be the dock manager's central widget.
Args:
dock(CDockWidget | QWidget | str): Dock reference to promote.
"""
class MonacoWidget(RPCBase): class MonacoWidget(RPCBase):
"""A simple Monaco editor widget""" """A simple Monaco editor widget"""
+32 -10
View File
@@ -1,9 +1,19 @@
from __future__ import annotations from __future__ import annotations
from bec_widgets.cli.client_utils import IGNORE_WIDGETS from typing import TYPE_CHECKING
from bec_widgets.utils.bec_plugin_helper import get_all_plugin_widgets
from bec_widgets.utils.bec_widget import BECWidget from bec_lib.utils.import_utils import lazy_import_from
from bec_widgets.utils.plugin_utils import get_custom_classes
from bec_widgets.utils.bec_plugin_helper import get_all_plugin_widget_references
from bec_widgets.utils.plugin_utils import get_custom_class_references
try:
from bec_widgets.cli.constants import IGNORE_WIDGETS
except ModuleNotFoundError: # pragma: no cover
IGNORE_WIDGETS = ["LaunchWindow"]
if TYPE_CHECKING: # pragma: no cover
from bec_widgets.utils.bec_widget import BECWidget
class RPCWidgetHandler: class RPCWidgetHandler:
@@ -13,7 +23,7 @@ class RPCWidgetHandler:
self._widget_classes = None self._widget_classes = None
@property @property
def widget_classes(self) -> dict[str, type[BECWidget]]: def widget_classes(self) -> dict[str, type["BECWidget"]]:
""" """
Get the available widget classes. Get the available widget classes.
@@ -31,12 +41,24 @@ class RPCWidgetHandler:
Returns: Returns:
None None
""" """
self._widget_classes = ( ignored = set(IGNORE_WIDGETS)
get_custom_classes("bec_widgets", packages=("widgets", "applications")) widget_classes = {
+ get_all_plugin_widgets() reference.name: lazy_import_from(reference.module, (reference.name,))
).as_dict(IGNORE_WIDGETS) for reference in get_all_plugin_widget_references(use_cache=False)
if reference.name not in ignored
}
widget_classes.update(
{
reference.name: lazy_import_from(reference.module, (reference.name,))
for reference in get_custom_class_references(
"bec_widgets", packages=("widgets", "applications"), use_cache=False
)
if reference.name not in ignored
}
)
self._widget_classes = widget_classes
def create_widget(self, widget_type, **kwargs) -> BECWidget: def create_widget(self, widget_type, **kwargs) -> "BECWidget":
""" """
Create a widget from an RPC message. Create a widget from an RPC message.
-13
View File
@@ -1,13 +0,0 @@
from qtpy.QtWebEngineWidgets import QWebEngineView
from .bec_connector import BECConnector, ConnectionConfig
from .bec_dispatcher import BECDispatcher
from .bec_table import BECTable
from .colors import Colors
from .container_utils import WidgetContainerUtils
from .crosshair import Crosshair
from .entry_validator import EntryValidator
from .layout_manager import GridLayoutManager
from .rpc_decorator import register_rpc_methods, rpc_public
from .ui_loader import UILoader
from .validator_delegate import DoubleValidationDelegate
+59 -3
View File
@@ -1,7 +1,9 @@
from __future__ import annotations from __future__ import annotations
import ast
import importlib.metadata import importlib.metadata
import inspect import inspect
import logging
import pkgutil import pkgutil
import traceback import traceback
from importlib import util as importlib_util from importlib import util as importlib_util
@@ -9,11 +11,65 @@ from importlib.machinery import FileFinder, ModuleSpec, SourceFileLoader
from types import ModuleType from types import ModuleType
from typing import Generator from typing import Generator
from bec_lib.logger import bec_logger from bec_widgets.utils.plugin_utils import (
BECClassContainer,
BECClassInfo,
BECClassReference,
_ast_node_name,
_class_has_rpc_markers,
_discover_class_references_from_roots,
_find_package_roots,
)
from bec_widgets.utils.plugin_utils import BECClassContainer, BECClassInfo logger = logging.getLogger(__name__)
logger = bec_logger.logger
def _plugin_class_is_candidate(node: ast.ClassDef) -> bool:
base_names = {_ast_node_name(base) for base in node.bases}
return bool({"BECWidget", "BECConnector"} & base_names) or _class_has_rpc_markers(node)
_PLUGIN_WIDGET_REFERENCE_CACHE: dict[tuple[tuple[str, str], ...], tuple[BECClassReference, ...]] = (
{}
)
def _plugin_entry_point_snapshot() -> tuple[tuple[str, str], ...]:
return tuple(
sorted(
(entry_point.name, entry_point.module)
for entry_point in importlib.metadata.entry_points(group="bec.widgets.user_widgets") # type: ignore
)
)
def _build_plugin_widget_references() -> tuple[BECClassReference, ...]:
references: list[BECClassReference] = []
seen_names: set[str] = set()
for entry_point in importlib.metadata.entry_points(group="bec.widgets.user_widgets"): # type: ignore
try:
package_roots = _find_package_roots(entry_point.module)
except ModuleNotFoundError:
continue
for reference in _discover_class_references_from_roots(
entry_point.module,
package_roots,
file_name_filter=lambda file_name: file_name.endswith(".py")
and not file_name.startswith("__"),
candidate_filter=_plugin_class_is_candidate,
):
if reference.name in seen_names:
continue
references.append(reference)
seen_names.add(reference.name)
return tuple(references)
def get_all_plugin_widget_references(*, use_cache: bool = True) -> list[BECClassReference]:
snapshot = _plugin_entry_point_snapshot()
if not use_cache or snapshot not in _PLUGIN_WIDGET_REFERENCE_CACHE:
_PLUGIN_WIDGET_REFERENCE_CACHE[snapshot] = _build_plugin_widget_references()
return list(_PLUGIN_WIDGET_REFERENCE_CACHE[snapshot])
def _submodule_specs(module: ModuleType) -> tuple[ModuleSpec | None, ...]: def _submodule_specs(module: ModuleType) -> tuple[ModuleSpec | None, ...]:
-1
View File
@@ -20,7 +20,6 @@ from bec_widgets.widgets.utility.spinner.spinner import SpinnerWidget
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from bec_widgets.utils.busy_loader import BusyLoaderOverlay from bec_widgets.utils.busy_loader import BusyLoaderOverlay
from bec_widgets.widgets.containers.dock import BECDock
logger = bec_logger.logger logger = bec_logger.logger
+288 -47
View File
@@ -1,22 +1,23 @@
from __future__ import annotations from __future__ import annotations
import ast
import importlib import importlib
import inspect import inspect
import os import os
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING, Iterable from functools import lru_cache
from importlib import util as importlib_util
from bec_lib.plugin_helper import _get_available_plugins from typing import TYPE_CHECKING, Callable, Iterable
from qtpy.QtWidgets import QWidget
from bec_widgets.utils import BECConnector
from bec_widgets.utils.bec_widget import BECWidget
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates
_DISCOVERY_BASE_NAMES = frozenset({"BECConnector", "BECWidget", "ViewBase"})
def get_plugin_widgets() -> dict[str, BECConnector]:
def get_plugin_widgets() -> dict[str, "BECConnector"]:
""" """
Get all available widgets from the plugin directory. Widgets are classes that inherit from BECConnector. Get all available widgets from the plugin directory. Widgets are classes that inherit from BECConnector.
The plugins are provided through python plugins and specified in the respective pyproject.toml file using The plugins are provided through python plugins and specified in the respective pyproject.toml file using
@@ -35,9 +36,10 @@ def get_plugin_widgets() -> dict[str, BECConnector]:
Returns: Returns:
dict[str, BECConnector]: A dictionary of widget names and their respective classes. dict[str, BECConnector]: A dictionary of widget names and their respective classes.
""" """
from bec_lib.plugin_helper import _get_available_plugins
modules = _get_available_plugins("bec.widgets.user_widgets") modules = _get_available_plugins("bec.widgets.user_widgets")
loaded_plugins = {} loaded_plugins = {}
print(modules)
for module in modules: for module in modules:
mods = inspect.getmembers(module, predicate=_filter_plugins) mods = inspect.getmembers(module, predicate=_filter_plugins)
for name, mod_cls in mods: for name, mod_cls in mods:
@@ -48,6 +50,8 @@ def get_plugin_widgets() -> dict[str, BECConnector]:
def _filter_plugins(obj): def _filter_plugins(obj):
from bec_widgets.utils.bec_connector import BECConnector
return inspect.isclass(obj) and issubclass(obj, BECConnector) return inspect.isclass(obj) and issubclass(obj, BECConnector)
@@ -66,6 +70,8 @@ def get_plugin_auto_updates() -> dict[str, type[AutoUpdates]]:
Returns: Returns:
dict[str, AutoUpdates]: A dictionary of widget names and their respective classes. dict[str, AutoUpdates]: A dictionary of widget names and their respective classes.
""" """
from bec_lib.plugin_helper import _get_available_plugins
modules = _get_available_plugins("bec.widgets.auto_updates") modules = _get_available_plugins("bec.widgets.auto_updates")
loaded_plugins = {} loaded_plugins = {}
for module in modules: for module in modules:
@@ -90,14 +96,20 @@ class BECClassInfo:
name: str name: str
module: str module: str
file: str file: str
obj: type[BECWidget] obj: type["BECWidget"]
is_connector: bool = False is_connector: bool = False
is_widget: bool = False is_widget: bool = False
is_plugin: bool = False is_plugin: bool = False
@dataclass(frozen=True)
class BECClassReference:
name: str
module: str
class BECClassContainer: class BECClassContainer:
def __init__(self, initial: Iterable[BECClassInfo] = []): def __init__(self, initial: Iterable[BECClassInfo] = ()):
self._collection: list[BECClassInfo] = list(initial) self._collection: list[BECClassInfo] = list(initial)
def __repr__(self): def __repr__(self):
@@ -109,12 +121,13 @@ class BECClassContainer:
def __add__(self, other: BECClassContainer): def __add__(self, other: BECClassContainer):
return BECClassContainer((*self, *(c for c in other if c.name not in self.names))) return BECClassContainer((*self, *(c for c in other if c.name not in self.names)))
def as_dict(self, ignores: list[str] = []) -> dict[str, type[BECWidget]]: def as_dict(self, ignores: list[str] | None = None) -> dict[str, type["BECWidget"]]:
"""get a dict of {name: Type} for all the entries in the collection. """get a dict of {name: Type} for all the entries in the collection.
Args: Args:
ignores(list[str]): a list of class names to exclude from the dictionary.""" ignores(list[str]): a list of class names to exclude from the dictionary."""
return {c.name: c.obj for c in self if c.name not in ignores} ignore_set = set(ignores or ())
return {c.name: c.obj for c in self if c.name not in ignore_set}
def add_class(self, class_info: BECClassInfo): def add_class(self, class_info: BECClassInfo):
""" """
@@ -166,48 +179,276 @@ class BECClassContainer:
return [info.obj for info in self.collection] return [info.obj for info in self.collection]
def _ast_node_name(node: ast.expr) -> str | None:
if isinstance(node, ast.Name):
return node.id
if isinstance(node, ast.Attribute):
return node.attr
return None
def _class_has_rpc_markers(node: ast.ClassDef) -> bool:
for stmt in node.body:
if isinstance(stmt, ast.Assign):
target_names = {target.id for target in stmt.targets if isinstance(target, ast.Name)}
if (
"PLUGIN" in target_names
and isinstance(stmt.value, ast.Constant)
and stmt.value.value
):
return True
if {"RPC_CONTENT_CLASS", "USER_ACCESS"} & target_names:
return True
if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name):
if (
stmt.target.id == "PLUGIN"
and isinstance(stmt.value, ast.Constant)
and stmt.value.value
):
return True
if stmt.target.id in {"RPC_CONTENT_CLASS", "USER_ACCESS"}:
return True
return False
def _class_is_candidate(node: ast.ClassDef) -> bool:
base_names = {_ast_node_name(base) for base in node.bases}
return bool(_DISCOVERY_BASE_NAMES & base_names) or _class_has_rpc_markers(node)
def _candidate_top_level_class_names(path: str) -> list[str]:
with open(path, encoding="utf-8") as file_handle:
module = ast.parse(file_handle.read(), filename=path)
return [
node.name
for node in module.body
if isinstance(node, ast.ClassDef) and _class_is_candidate(node)
]
@lru_cache(maxsize=64)
def _find_package_roots(module_name: str) -> tuple[str, ...]:
spec = importlib_util.find_spec(module_name)
if spec is None:
raise ModuleNotFoundError(module_name)
package_roots = tuple(spec.submodule_search_locations or ())
if package_roots:
return package_roots
if spec.origin:
return (os.path.dirname(spec.origin),)
raise ModuleNotFoundError(module_name)
def _discover_class_references_from_roots(
module_prefix: str,
package_roots: Iterable[str],
*,
file_name_filter: Callable[[str], bool],
candidate_filter: Callable[[ast.ClassDef], bool],
) -> tuple[BECClassReference, ...]:
references: list[BECClassReference] = []
seen_names: set[str] = set()
for package_root in package_roots:
for root, _, files in sorted(os.walk(package_root)):
for file_name in sorted(files):
if not file_name_filter(file_name):
continue
path = os.path.join(root, file_name)
with open(path, encoding="utf-8") as file_handle:
module = ast.parse(file_handle.read(), filename=path)
rel_path = os.path.relpath(path, package_root).removesuffix(".py")
module_name = ".".join([module_prefix, *rel_path.split(os.sep)])
for node in module.body:
if not isinstance(node, ast.ClassDef) or not candidate_filter(node):
continue
if node.name in seen_names:
continue
references.append(BECClassReference(name=node.name, module=module_name))
seen_names.add(node.name)
return tuple(references)
def _iter_candidate_modules(repo_name: str, package: str) -> Iterable[tuple[str, str, list[str]]]:
try:
package_roots = _find_package_roots(f"{repo_name}.{package}")
except ModuleNotFoundError:
return ()
modules: list[tuple[str, str, list[str]]] = []
for directory in package_roots:
for root, _, files in sorted(os.walk(directory)):
for file_name in sorted(files):
if (
not file_name.endswith(".py")
or file_name.startswith("__")
or file_name.startswith("register_")
or file_name.endswith("_plugin.py")
):
continue
path = os.path.join(root, file_name)
rel_dir = os.path.dirname(os.path.relpath(path, directory))
module_name = (
file_name.removesuffix(".py")
if rel_dir in ("", ".")
else ".".join(rel_dir.split(os.sep) + [file_name.removesuffix(".py")])
)
class_names = _candidate_top_level_class_names(path)
if class_names:
modules.append((f"{repo_name}.{package}.{module_name}", path, class_names))
return tuple(modules)
def _collect_classes_from_package(repo_name: str, package: str) -> BECClassContainer: def _collect_classes_from_package(repo_name: str, package: str) -> BECClassContainer:
"""Collect classes from a package subtree (for example ``widgets`` or ``applications``).""" """Collect classes from a package subtree (for example ``widgets`` or ``applications``)."""
collection = BECClassContainer() collection = BECClassContainer()
try: for module_name, path, _ in _iter_candidate_modules(repo_name, package):
anchor_module = importlib.import_module(f"{repo_name}.{package}") from qtpy.QtWidgets import QWidget
except ModuleNotFoundError as exc:
# Some plugin repositories expose only one subtree. Skip gracefully if it does not exist.
if exc.name == f"{repo_name}.{package}":
return collection
raise
directory = os.path.dirname(anchor_module.__file__) from bec_widgets.utils.bec_connector import BECConnector
for root, _, files in sorted(os.walk(directory)): from bec_widgets.utils.bec_widget import BECWidget
for file in files:
if not file.endswith(".py") or file.startswith("__"): module = importlib.import_module(module_name)
for name, obj in inspect.getmembers(module, inspect.isclass):
if obj.__module__ != module.__name__:
continue continue
class_info = BECClassInfo(name=name, module=module.__name__, file=path, obj=obj)
path = os.path.join(root, file) if issubclass(obj, BECConnector):
rel_dir = os.path.dirname(os.path.relpath(path, directory)) class_info.is_connector = True
if rel_dir in ("", "."): if issubclass(obj, QWidget) or issubclass(obj, BECWidget):
module_name = file.split(".")[0] class_info.is_widget = True
else: if hasattr(obj, "PLUGIN") and obj.PLUGIN:
module_name = ".".join(rel_dir.split(os.sep) + [file.split(".")[0]]) class_info.is_plugin = True
collection.add_class(class_info)
module = importlib.import_module(f"{repo_name}.{package}.{module_name}")
for name in dir(module):
obj = getattr(module, name)
if not hasattr(obj, "__module__") or obj.__module__ != module.__name__:
continue
if isinstance(obj, type):
class_info = BECClassInfo(name=name, module=module.__name__, file=path, obj=obj)
if issubclass(obj, BECConnector):
class_info.is_connector = True
if issubclass(obj, QWidget) or issubclass(obj, BECWidget):
class_info.is_widget = True
if hasattr(obj, "PLUGIN") and obj.PLUGIN:
class_info.is_plugin = True
collection.add_class(class_info)
return collection return collection
def _build_ast_inheritance_map(
repo_name: str, packages: tuple[str, ...]
) -> dict[str, tuple[str, set[str]]]:
"""
Walk all candidate modules in the given packages and return a map of:
class_name -> (module_name, {direct_base_names})
This is used for the transitive-closure widget discovery so that subclasses
of discovered widget bases are themselves discoverable without needing to
repeat ``PLUGIN = True`` on every intermediate or leaf class.
"""
mapping: dict[str, tuple[str, set[str]]] = {}
for package in packages:
try:
package_roots = _find_package_roots(f"{repo_name}.{package}")
except ModuleNotFoundError:
continue
for directory in package_roots:
for root, _, files in sorted(os.walk(directory)):
for file_name in sorted(files):
if (
not file_name.endswith(".py")
or file_name.startswith("__")
or file_name.startswith("register_")
or file_name.endswith("_plugin.py")
):
continue
path = os.path.join(root, file_name)
rel_dir = os.path.dirname(os.path.relpath(path, directory))
module_name = (
file_name.removesuffix(".py")
if rel_dir in ("", ".")
else ".".join(rel_dir.split(os.sep) + [file_name.removesuffix(".py")])
)
full_module = f"{repo_name}.{package}.{module_name}"
with open(path, encoding="utf-8") as fh:
tree = ast.parse(fh.read(), filename=path)
for node in tree.body:
if not isinstance(node, ast.ClassDef):
continue
base_names = {_ast_node_name(b) for b in node.bases} - {None}
mapping[node.name] = (full_module, base_names) # type: ignore[arg-type]
return mapping
@lru_cache(maxsize=32)
def _cached_custom_class_references(
repo_name: str, packages: tuple[str, ...]
) -> tuple[BECClassReference, ...]:
"""Discover widget/connector class references using a transitive-closure AST scan.
The first pass identifies classes that directly inherit from
``_DISCOVERY_BASE_NAMES`` or carry explicit RPC markers (``PLUGIN``,
``USER_ACCESS``, ``RPC_CONTENT_CLASS``). Subsequent passes treat every
newly found class name as an additional base name, so subclasses of
subclasses are discovered automatically — without requiring each
intermediate class to repeat ``PLUGIN = True``.
"""
inheritance_map = _build_ast_inheritance_map(repo_name, packages)
# Seed with _class_has_rpc_markers — we need the AST nodes for that check.
# Re-parse only to identify initial RPC-marker classes; inheritance_map
# already has everything else we need.
rpc_marker_names: set[str] = set()
for package in packages:
try:
package_roots = _find_package_roots(f"{repo_name}.{package}")
except ModuleNotFoundError:
continue
for directory in package_roots:
for root, _, files in sorted(os.walk(directory)):
for file_name in sorted(files):
if (
not file_name.endswith(".py")
or file_name.startswith("__")
or file_name.startswith("register_")
or file_name.endswith("_plugin.py")
):
continue
path = os.path.join(root, file_name)
with open(path, encoding="utf-8") as fh:
tree = ast.parse(fh.read(), filename=path)
for node in tree.body:
if isinstance(node, ast.ClassDef) and _class_has_rpc_markers(node):
rpc_marker_names.add(node.name)
# Transitive closure: start with known base names + RPC-marker classes,
# then repeatedly add classes whose direct bases are already known.
known: set[str] = set(_DISCOVERY_BASE_NAMES) | rpc_marker_names
changed = True
while changed:
changed = False
for class_name, (_, bases) in inheritance_map.items():
if class_name not in known and bases & known:
known.add(class_name)
changed = True
# Build the final list of references, preserving first-seen order and
# keeping only classes that are in the inheritance map (i.e. have a module).
references: list[BECClassReference] = []
seen_names: set[str] = set()
for class_name, (module_name, bases) in inheritance_map.items():
if class_name not in known:
continue
if class_name in seen_names:
continue
# Only emit if the class is actually a candidate (not just a raw base)
if class_name in _DISCOVERY_BASE_NAMES:
continue
references.append(BECClassReference(name=class_name, module=module_name))
seen_names.add(class_name)
return tuple(references)
def get_custom_class_references(
repo_name: str, packages: tuple[str, ...] | None = None, *, use_cache: bool = True
) -> list[BECClassReference]:
selected_packages = packages or ("widgets",)
if use_cache:
return list(_cached_custom_class_references(repo_name, tuple(selected_packages)))
_cached_custom_class_references.cache_clear()
return list(_cached_custom_class_references(repo_name, tuple(selected_packages)))
def get_custom_classes( def get_custom_classes(
repo_name: str, packages: tuple[str, ...] | None = None repo_name: str, packages: tuple[str, ...] | None = None
) -> BECClassContainer: ) -> BECClassContainer:
+1 -2
View File
@@ -15,8 +15,8 @@ from qtpy.QtWidgets import QWidget
from redis.exceptions import RedisError from redis.exceptions import RedisError
from bec_widgets.cli.rpc.rpc_register import RPCRegister from bec_widgets.cli.rpc.rpc_register import RPCRegister
from bec_widgets.utils import BECDispatcher
from bec_widgets.utils.bec_connector import BECConnector from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.utils.bec_dispatcher import BECDispatcher
from bec_widgets.utils.container_utils import WidgetContainerUtils from bec_widgets.utils.container_utils import WidgetContainerUtils
from bec_widgets.utils.error_popups import ErrorPopupUtility from bec_widgets.utils.error_popups import ErrorPopupUtility
from bec_widgets.utils.screen_utils import apply_window_geometry from bec_widgets.utils.screen_utils import apply_window_geometry
@@ -25,7 +25,6 @@ from bec_widgets.widgets.containers.main_window.main_window import BECMainWindow
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from bec_lib import messages from bec_lib import messages
from qtpy.QtCore import QObject
else: else:
messages = lazy_import("bec_lib.messages") messages = lazy_import("bec_lib.messages")
logger = bec_logger.logger logger = bec_logger.logger
+6 -6
View File
@@ -26,7 +26,7 @@ from qtpy.QtWidgets import (
from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from bec_widgets.utils import BECConnector from bec_widgets.utils.bec_connector import BECConnector
logger = bec_logger.logger logger = bec_logger.logger
@@ -418,7 +418,7 @@ class WidgetHierarchy:
only_bec_widgets(bool, optional): Whether to print only widgets that are instances of BECWidget. only_bec_widgets(bool, optional): Whether to print only widgets that are instances of BECWidget.
show_parent(bool, optional): Whether to display which BECWidget is the parent of each discovered BECWidget. show_parent(bool, optional): Whether to display which BECWidget is the parent of each discovered BECWidget.
""" """
from bec_widgets.utils import BECConnector from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.widgets.plots.waveform.waveform import Waveform from bec_widgets.widgets.plots.waveform.waveform import Waveform
for node in WidgetHierarchy.iter_widget_tree( for node in WidgetHierarchy.iter_widget_tree(
@@ -468,7 +468,7 @@ class WidgetHierarchy:
from qtpy.QtWidgets import QApplication from qtpy.QtWidgets import QApplication
from bec_widgets.utils import BECConnector from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.widgets.plots.plot_base import PlotBase from bec_widgets.widgets.plots.plot_base import PlotBase
# 1) Gather ALL QWidget-based BECConnector objects # 1) Gather ALL QWidget-based BECConnector objects
@@ -534,7 +534,7 @@ class WidgetHierarchy:
Returns: Returns:
The nearest ancestor that is a BECConnector, or None if not found. The nearest ancestor that is a BECConnector, or None if not found.
""" """
from bec_widgets.utils import BECConnector from bec_widgets.utils.bec_connector import BECConnector
# Guard against deleted/invalid Qt wrappers # Guard against deleted/invalid Qt wrappers
if not shb.isValid(widget): if not shb.isValid(widget):
@@ -636,7 +636,7 @@ class WidgetHierarchy:
Return all BECConnector instances whose closest BECConnector ancestor is the given widget, Return all BECConnector instances whose closest BECConnector ancestor is the given widget,
including the widget itself if it is a BECConnector. including the widget itself if it is a BECConnector.
""" """
from bec_widgets.utils import BECConnector from bec_widgets.utils.bec_connector import BECConnector
connectors: list[BECConnector] = [] connectors: list[BECConnector] = []
if isinstance(widget, BECConnector): if isinstance(widget, BECConnector):
@@ -664,7 +664,7 @@ class WidgetHierarchy:
return None return None
try: try:
from bec_widgets.utils import BECConnector # local import to avoid cycles from bec_widgets.utils.bec_connector import BECConnector # local import to avoid cycles
is_bec_target = False is_bec_target = False
if isinstance(ancestor_class, str): if isinstance(ancestor_class, str):
@@ -21,7 +21,7 @@ import bec_widgets.widgets.containers.qt_ads as QtAds
from bec_widgets import BECWidget, SafeProperty, SafeSlot from bec_widgets import BECWidget, SafeProperty, SafeSlot
from bec_widgets.applications.views.view import ViewTourSteps from bec_widgets.applications.views.view import ViewTourSteps
from bec_widgets.cli.rpc.rpc_widget_handler import widget_handler from bec_widgets.cli.rpc.rpc_widget_handler import widget_handler
from bec_widgets.utils import BECDispatcher from bec_widgets.utils.bec_dispatcher import BECDispatcher
from bec_widgets.utils.colors import apply_theme from bec_widgets.utils.colors import apply_theme
from bec_widgets.utils.rpc_decorator import rpc_timeout from bec_widgets.utils.rpc_decorator import rpc_timeout
from bec_widgets.utils.toolbars.actions import ( from bec_widgets.utils.toolbars.actions import (
@@ -68,7 +68,7 @@ from bec_widgets.widgets.containers.dock_area.toolbar_components.workspace_actio
from bec_widgets.widgets.containers.main_window.main_window import BECMainWindowNoRPC from bec_widgets.widgets.containers.main_window.main_window import BECMainWindowNoRPC
from bec_widgets.widgets.containers.qt_ads import CDockWidget from bec_widgets.widgets.containers.qt_ads import CDockWidget
from bec_widgets.widgets.control.device_control.positioner_box import PositionerBox, PositionerBox2D from bec_widgets.widgets.control.device_control.positioner_box import PositionerBox, PositionerBox2D
from bec_widgets.widgets.control.scan_control import ScanControl from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
from bec_widgets.widgets.editors.bec_console.bec_console import BecConsole, BECShell from bec_widgets.widgets.editors.bec_console.bec_console import BecConsole, BECShell
from bec_widgets.widgets.plots.heatmap.heatmap import Heatmap from bec_widgets.widgets.plots.heatmap.heatmap import Heatmap
from bec_widgets.widgets.plots.image.image import Image from bec_widgets.widgets.plots.image.image import Image
@@ -28,7 +28,7 @@ from qtpy.QtCore import QObject, QTimer
from qtpy.QtWidgets import QApplication, QFrame, QMainWindow, QScrollArea, QWidget from qtpy.QtWidgets import QApplication, QFrame, QMainWindow, QScrollArea, QWidget
from bec_widgets import SafeProperty, SafeSlot from bec_widgets import SafeProperty, SafeSlot
from bec_widgets.utils import BECConnector from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.utils.colors import apply_theme from bec_widgets.utils.colors import apply_theme
from bec_widgets.utils.widget_io import WidgetIO from bec_widgets.utils.widget_io import WidgetIO
@@ -18,10 +18,10 @@ from qtpy.QtWidgets import (
) )
import bec_widgets import bec_widgets
from bec_widgets.utils import UILoader
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import apply_theme from bec_widgets.utils.colors import apply_theme
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.widgets.containers.main_window.addons.hover_widget import HoverWidget from bec_widgets.widgets.containers.main_window.addons.hover_widget import HoverWidget
from bec_widgets.widgets.containers.main_window.addons.notification_center.notification_banner import ( from bec_widgets.widgets.containers.main_window.addons.notification_center.notification_banner import (
BECNotificationBroker, BECNotificationBroker,
@@ -1 +1,4 @@
from PySide6QtAds import * from PySide6QtAds import *
CDockManager.setConfigFlag(CDockManager.eConfigFlag.FocusHighlighting, True)
CDockManager.setConfigFlag(CDockManager.eConfigFlag.RetainTabSizeWhenCloseButtonHidden, True)
@@ -11,9 +11,9 @@ from qtpy.QtCore import Qt, Signal
from qtpy.QtGui import QDoubleValidator from qtpy.QtGui import QDoubleValidator
from qtpy.QtWidgets import QDoubleSpinBox from qtpy.QtWidgets import QDoubleSpinBox
from bec_widgets.utils import UILoader
from bec_widgets.utils.colors import apply_theme, get_accent_colors from bec_widgets.utils.colors import apply_theme, get_accent_colors
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base import ( from bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base import (
DeviceUpdateUIComponents, DeviceUpdateUIComponents,
PositionerBoxBase, PositionerBoxBase,
@@ -12,9 +12,9 @@ from qtpy.QtCore import Signal
from qtpy.QtGui import QDoubleValidator from qtpy.QtGui import QDoubleValidator
from qtpy.QtWidgets import QDoubleSpinBox from qtpy.QtWidgets import QDoubleSpinBox
from bec_widgets.utils import UILoader
from bec_widgets.utils.colors import apply_theme from bec_widgets.utils.colors import apply_theme
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base import ( from bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base import (
DeviceUpdateUIComponents, DeviceUpdateUIComponents,
PositionerBoxBase, PositionerBoxBase,
@@ -7,7 +7,7 @@ from bec_lib.device import Signal as BECSignal
from bec_lib.logger import bec_logger from bec_lib.logger import bec_logger
from pydantic import field_validator from pydantic import field_validator
from bec_widgets.utils import ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.filter_io import FilterIO from bec_widgets.utils.filter_io import FilterIO
@@ -3,7 +3,7 @@ from bec_lib.device import Signal
from bec_lib.logger import bec_logger from bec_lib.logger import bec_logger
from qtpy.QtCore import Property from qtpy.QtCore import Property
from bec_widgets.utils import ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.filter_io import FilterIO from bec_widgets.utils.filter_io import FilterIO
@@ -1 +0,0 @@
from .components import DeviceTable, DMConfigView, DocstringView, OphydValidation
@@ -1,5 +0,0 @@
# from .device_table_view import DeviceTableView
from .device_table.device_table import DeviceTable
from .dm_config_view import DMConfigView
from .dm_docstring_view import DocstringView, docstring_to_markdown
from .ophyd_validation.ophyd_validation import OphydValidation
@@ -1,3 +0,0 @@
from .available_device_resources import AvailableDeviceResources
__all__ = ["AvailableDeviceResources"]
@@ -22,7 +22,7 @@ from bec_widgets.utils.fuzzy_search import is_match
from bec_widgets.widgets.control.device_manager.components.device_table.device_table_row import ( from bec_widgets.widgets.control.device_manager.components.device_table.device_table_row import (
DeviceTableRow, DeviceTableRow,
) )
from bec_widgets.widgets.control.device_manager.components.ophyd_validation import ( from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation_utils import (
ConfigStatus, ConfigStatus,
ConnectionStatus, ConnectionStatus,
get_validation_icons, get_validation_icons,
@@ -2,7 +2,7 @@
from bec_lib.atlas_models import Device as DeviceModel from bec_lib.atlas_models import Device as DeviceModel
from bec_widgets.widgets.control.device_manager.components.ophyd_validation import ( from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation_utils import (
ConfigStatus, ConfigStatus,
ConnectionStatus, ConnectionStatus,
) )
@@ -1,8 +0,0 @@
from .ophyd_validation_utils import (
ConfigStatus,
ConnectionStatus,
DeviceTestModel,
format_error_to_md,
get_validation_icons,
)
from .validation_list_item import ValidationButton, ValidationListItem
@@ -22,15 +22,17 @@ from bec_widgets.utils.bec_list import BECList
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import get_accent_colors from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.widgets.control.device_manager.components.ophyd_validation import ( from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation_utils import (
ConfigStatus, ConfigStatus,
ConnectionStatus, ConnectionStatus,
DeviceTestModel, DeviceTestModel,
ValidationButton,
ValidationListItem,
format_error_to_md, format_error_to_md,
get_validation_icons, get_validation_icons,
) )
from bec_widgets.widgets.control.device_manager.components.ophyd_validation.validation_list_item import (
ValidationButton,
ValidationListItem,
)
READY_TO_TEST = False READY_TO_TEST = False
@@ -7,7 +7,7 @@ from qtpy import QtCore, QtGui, QtWidgets
from bec_widgets.utils.colors import get_accent_colors from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.widgets.control.device_manager.components.ophyd_validation import ( from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation_utils import (
ConfigStatus, ConfigStatus,
ConnectionStatus, ConnectionStatus,
DeviceTestModel, DeviceTestModel,
@@ -1 +0,0 @@
from .scan_control import ScanControl
@@ -19,7 +19,7 @@ from qtpy.QtWidgets import (
QWidget, QWidget,
) )
from bec_widgets.utils import ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import apply_theme, get_accent_colors from bec_widgets.utils.colors import apply_theme, get_accent_colors
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
@@ -5,10 +5,10 @@ from bec_lib.logger import bec_logger
from qtpy.QtCore import Signal from qtpy.QtCore import Signal
from qtpy.QtWidgets import QPushButton, QSizePolicy, QTreeWidgetItem, QVBoxLayout, QWidget from qtpy.QtWidgets import QPushButton, QSizePolicy, QTreeWidgetItem, QVBoxLayout, QWidget
from bec_widgets.utils import UILoader
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import get_accent_colors from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.ui_loader import UILoader
logger = bec_logger.logger logger = bec_logger.logger
@@ -1,10 +1,12 @@
from bec_ipython_client.main import BECIPythonClient from bec_lib.utils.import_utils import lazy_import_from
from qtconsole.inprocess import QtInProcessKernelManager from qtconsole.inprocess import QtInProcessKernelManager
from qtconsole.manager import QtKernelManager from qtconsole.manager import QtKernelManager
from qtconsole.rich_jupyter_widget import RichJupyterWidget from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtpy.QtCore import Qt from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication, QMainWindow from qtpy.QtWidgets import QApplication, QMainWindow
BECIPythonClient = lazy_import_from("bec_ipython_client.main", ("BECIPythonClient",))
class BECJupyterConsole(RichJupyterWidget): # pragma: no cover: class BECJupyterConsole(RichJupyterWidget): # pragma: no cover:
def __init__(self, inprocess: bool = False): def __init__(self, inprocess: bool = False):
@@ -11,7 +11,7 @@ from bec_lib.logger import bec_logger
from qtpy.QtCore import QSize, Qt from qtpy.QtCore import QSize, Qt
from qtpy.QtWidgets import QDialog, QDialogButtonBox, QPushButton, QVBoxLayout from qtpy.QtWidgets import QDialog, QDialogButtonBox, QPushButton, QVBoxLayout
from bec_widgets.widgets.control.scan_control import ScanControl from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
logger = bec_logger.logger logger = bec_logger.logger
+1 -1
View File
@@ -19,8 +19,8 @@ from scipy.interpolate import (
from scipy.spatial import cKDTree from scipy.spatial import cKDTree
from toolz import partition from toolz import partition
from bec_widgets.utils import Colors
from bec_widgets.utils.bec_connector import ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.colors import Colors
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.settings_dialog import SettingsDialog from bec_widgets.utils.settings_dialog import SettingsDialog
from bec_widgets.utils.toolbars.actions import MaterialIconAction from bec_widgets.utils.toolbars.actions import MaterialIconAction
@@ -4,9 +4,9 @@ import os
from qtpy.QtWidgets import QFrame, QScrollArea, QVBoxLayout from qtpy.QtWidgets import QFrame, QScrollArea, QVBoxLayout
from bec_widgets.utils import UILoader
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.settings_dialog import SettingWidget from bec_widgets.utils.settings_dialog import SettingWidget
from bec_widgets.utils.ui_loader import UILoader
class HeatmapSettings(SettingWidget): class HeatmapSettings(SettingWidget):
+1 -1
View File
@@ -10,7 +10,7 @@ from pydantic import BaseModel, Field, field_validator
from qtpy.QtCore import QTimer from qtpy.QtCore import QTimer
from qtpy.QtWidgets import QWidget from qtpy.QtWidgets import QWidget
from bec_widgets.utils import ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.colors import Colors, apply_theme from bec_widgets.utils.colors import Colors, apply_theme
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.widgets.plots.image.image_base import ImageBase from bec_widgets.widgets.plots.image.image_base import ImageBase
@@ -9,7 +9,7 @@ from pydantic import BaseModel, ConfigDict, Field, ValidationError
from qtpy.QtCore import QPointF, Signal, SignalInstance from qtpy.QtCore import QPointF, Signal, SignalInstance
from qtpy.QtWidgets import QDialog, QVBoxLayout from qtpy.QtWidgets import QDialog, QVBoxLayout
from bec_widgets.utils import Colors from bec_widgets.utils.colors import Colors
from bec_widgets.utils.container_utils import WidgetContainerUtils from bec_widgets.utils.container_utils import WidgetContainerUtils
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.side_panel import SidePanel from bec_widgets.utils.side_panel import SidePanel
@@ -9,7 +9,8 @@ from pydantic import Field, ValidationError, field_validator
from qtpy.QtCore import Signal from qtpy.QtCore import Signal
from qtpy.QtGui import QTransform from qtpy.QtGui import QTransform
from bec_widgets.utils import BECConnector, Colors, ConnectionConfig from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
from bec_widgets.utils.colors import Colors
from bec_widgets.widgets.plots.image.image_processor import ( from bec_widgets.widgets.plots.image.image_processor import (
ImageProcessor, ImageProcessor,
ImageStats, ImageStats,
@@ -20,7 +20,8 @@ from qtpy.QtWidgets import (
) )
from bec_widgets import BECWidget from bec_widgets import BECWidget
from bec_widgets.utils import BECDispatcher, ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_dispatcher import BECDispatcher
from bec_widgets.utils.toolbars.actions import WidgetAction from bec_widgets.utils.toolbars.actions import WidgetAction
from bec_widgets.utils.toolbars.bundles import ToolbarBundle from bec_widgets.utils.toolbars.bundles import ToolbarBundle
from bec_widgets.utils.toolbars.toolbar import MaterialIconAction, ModularToolBar from bec_widgets.utils.toolbars.toolbar import MaterialIconAction, ModularToolBar
@@ -10,8 +10,8 @@ from qtpy.QtCore import Signal
from qtpy.QtGui import QColor from qtpy.QtGui import QColor
from qtpy.QtWidgets import QHBoxLayout, QMainWindow, QWidget from qtpy.QtWidgets import QHBoxLayout, QMainWindow, QWidget
from bec_widgets.utils import Colors, ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.colors import apply_theme from bec_widgets.utils.colors import Colors, apply_theme
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.settings_dialog import SettingsDialog from bec_widgets.utils.settings_dialog import SettingsDialog
from bec_widgets.utils.toolbars.toolbar import MaterialIconAction from bec_widgets.utils.toolbars.toolbar import MaterialIconAction
@@ -2,9 +2,9 @@ import os
from qtpy.QtWidgets import QFrame, QScrollArea, QVBoxLayout, QWidget from qtpy.QtWidgets import QFrame, QScrollArea, QVBoxLayout, QWidget
from bec_widgets.utils import UILoader
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.settings_dialog import SettingWidget from bec_widgets.utils.settings_dialog import SettingWidget
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.utils.widget_io import WidgetIO from bec_widgets.utils.widget_io import WidgetIO
@@ -10,7 +10,8 @@ from pydantic import Field, ValidationError, field_validator
from qtpy.QtCore import Signal from qtpy.QtCore import Signal
from qtpy.QtWidgets import QWidget from qtpy.QtWidgets import QWidget
from bec_widgets.utils import Colors, ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.colors import Colors
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.side_panel import SidePanel from bec_widgets.utils.side_panel import SidePanel
from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import DeviceComboBox from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import DeviceComboBox
@@ -2,9 +2,9 @@ import os
from qtpy.QtWidgets import QVBoxLayout, QWidget from qtpy.QtWidgets import QVBoxLayout, QWidget
from bec_widgets.utils import UILoader
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.settings_dialog import SettingWidget from bec_widgets.utils.settings_dialog import SettingWidget
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.utils.widget_io import WidgetIO from bec_widgets.utils.widget_io import WidgetIO
+3 -1
View File
@@ -8,8 +8,10 @@ from bec_lib import bec_logger
from qtpy.QtCore import QPoint, QPointF, Qt, Signal from qtpy.QtCore import QPoint, QPointF, Qt, Signal
from qtpy.QtWidgets import QHBoxLayout, QLabel, QMainWindow, QVBoxLayout, QWidget from qtpy.QtWidgets import QHBoxLayout, QLabel, QMainWindow, QVBoxLayout, QWidget
from bec_widgets.utils import ConnectionConfig, Crosshair, EntryValidator from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.crosshair import Crosshair
from bec_widgets.utils.entry_validator import EntryValidator
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.fps_counter import FPSCounter from bec_widgets.utils.fps_counter import FPSCounter
from bec_widgets.utils.plot_indicator_items import BECArrowItem, BECTickItem from bec_widgets.utils.plot_indicator_items import BECArrowItem, BECTickItem
+1 -1
View File
@@ -10,7 +10,7 @@ from qtpy import QtCore
from qtpy.QtCore import QObject, Signal from qtpy.QtCore import QObject, Signal
from bec_widgets import SafeProperty from bec_widgets import SafeProperty
from bec_widgets.utils import BECConnector, ConnectionConfig from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
from bec_widgets.utils.colors import Colors from bec_widgets.utils.colors import Colors
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -8,7 +8,8 @@ from bec_lib import bec_logger
from pydantic import BaseModel, Field, ValidationError, field_validator from pydantic import BaseModel, Field, ValidationError, field_validator
from qtpy import QtCore from qtpy import QtCore
from bec_widgets.utils import BECConnector, Colors, ConnectionConfig from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
from bec_widgets.utils.colors import Colors
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from bec_widgets.widgets.plots.scatter_waveform.scatter_waveform import ScatterWaveform from bec_widgets.widgets.plots.scatter_waveform.scatter_waveform import ScatterWaveform
@@ -7,7 +7,8 @@ from pydantic import Field, ValidationError, field_validator
from qtpy.QtCore import QTimer, Signal from qtpy.QtCore import QTimer, Signal
from qtpy.QtWidgets import QHBoxLayout, QMainWindow, QWidget from qtpy.QtWidgets import QHBoxLayout, QMainWindow, QWidget
from bec_widgets.utils import Colors, ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.colors import Colors
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.settings_dialog import SettingsDialog from bec_widgets.utils.settings_dialog import SettingsDialog
from bec_widgets.utils.toolbars.toolbar import MaterialIconAction from bec_widgets.utils.toolbars.toolbar import MaterialIconAction
@@ -2,9 +2,9 @@ import os
from qtpy.QtWidgets import QFrame, QScrollArea, QVBoxLayout from qtpy.QtWidgets import QFrame, QScrollArea, QVBoxLayout
from bec_widgets.utils import UILoader
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.settings_dialog import SettingWidget from bec_widgets.utils.settings_dialog import SettingWidget
from bec_widgets.utils.ui_loader import UILoader
class ScatterCurveSettings(SettingWidget): class ScatterCurveSettings(SettingWidget):
@@ -2,9 +2,9 @@ import os
from qtpy.QtWidgets import QFrame, QScrollArea, QVBoxLayout, QWidget from qtpy.QtWidgets import QFrame, QScrollArea, QVBoxLayout, QWidget
from bec_widgets.utils import UILoader
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.settings_dialog import SettingWidget from bec_widgets.utils.settings_dialog import SettingWidget
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.utils.widget_io import WidgetIO from bec_widgets.utils.widget_io import WidgetIO
+2 -1
View File
@@ -8,7 +8,8 @@ from bec_lib import bec_logger
from pydantic import BaseModel, Field, field_validator from pydantic import BaseModel, Field, field_validator
from qtpy import QtCore from qtpy import QtCore
from bec_widgets.utils import BECConnector, Colors, ConnectionConfig from bec_widgets.utils.bec_connector import BECConnector, ConnectionConfig
from bec_widgets.utils.colors import Colors
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from bec_widgets.widgets.plots.waveform.waveform import Waveform from bec_widgets.widgets.plots.waveform.waveform import Waveform
@@ -50,9 +50,10 @@ from qtpy.QtWidgets import (
) )
from bec_widgets import SafeSlot from bec_widgets import SafeSlot
from bec_widgets.utils import ConnectionConfig, EntryValidator from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import Colors from bec_widgets.utils.colors import Colors
from bec_widgets.utils.entry_validator import EntryValidator
from bec_widgets.utils.toolbars.actions import WidgetAction from bec_widgets.utils.toolbars.actions import WidgetAction
from bec_widgets.utils.toolbars.bundles import ToolbarBundle from bec_widgets.utils.toolbars.bundles import ToolbarBundle
from bec_widgets.utils.toolbars.toolbar import MaterialIconAction, ModularToolBar from bec_widgets.utils.toolbars.toolbar import MaterialIconAction, ModularToolBar
@@ -25,7 +25,7 @@ from qtpy.QtWidgets import (
QWidget, QWidget,
) )
from bec_widgets.utils import ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_signal_proxy import BECSignalProxy from bec_widgets.utils.bec_signal_proxy import BECSignalProxy
from bec_widgets.utils.colors import Colors, apply_theme from bec_widgets.utils.colors import Colors, apply_theme
from bec_widgets.utils.container_utils import WidgetContainerUtils from bec_widgets.utils.container_utils import WidgetContainerUtils
@@ -6,8 +6,8 @@ from bec_lib.logger import bec_logger
from qtpy.QtCore import QPointF, QSize, Qt from qtpy.QtCore import QPointF, QSize, Qt
from qtpy.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget from qtpy.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget
from bec_widgets.utils import Colors
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import Colors
from bec_widgets.utils.error_popups import SafeProperty from bec_widgets.utils.error_popups import SafeProperty
from bec_widgets.utils.settings_dialog import SettingsDialog from bec_widgets.utils.settings_dialog import SettingsDialog
from bec_widgets.utils.toolbars.actions import MaterialIconAction from bec_widgets.utils.toolbars.actions import MaterialIconAction
@@ -19,9 +19,9 @@ from qtpy.QtWidgets import (
QWidget, QWidget,
) )
from bec_widgets.utils import UILoader
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.settings_dialog import SettingWidget from bec_widgets.utils.settings_dialog import SettingWidget
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.widgets.progress.ring_progress_bar.ring import Ring from bec_widgets.widgets.progress.ring_progress_bar.ring import Ring
from bec_widgets.widgets.utility.visual.colormap_widget.colormap_widget import BECColorMapWidget from bec_widgets.widgets.utility.visual.colormap_widget.colormap_widget import BECColorMapWidget
@@ -17,10 +17,10 @@ from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.error_popups import SafeSlot from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.list_of_expandable_frames import ListOfExpandableFrames from bec_widgets.utils.list_of_expandable_frames import ListOfExpandableFrames
from bec_widgets.utils.ui_loader import UILoader from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.widgets.services.device_browser.device_item import DeviceItem
from bec_widgets.widgets.services.device_browser.device_item.device_config_dialog import ( from bec_widgets.widgets.services.device_browser.device_item.device_config_dialog import (
DirectUpdateDeviceConfigDialog, DirectUpdateDeviceConfigDialog,
) )
from bec_widgets.widgets.services.device_browser.device_item.device_item import DeviceItem
from bec_widgets.widgets.services.device_browser.util import map_device_type_to_icon from bec_widgets.widgets.services.device_browser.util import map_device_type_to_icon
logger = bec_logger.logger logger = bec_logger.logger
@@ -1 +0,0 @@
from .device_item import DeviceItem
@@ -1,5 +0,0 @@
from .scan_history_device_viewer import ScanHistoryDeviceViewer
from .scan_history_metadata_viewer import ScanHistoryMetadataViewer
from .scan_history_view import ScanHistoryView
__all__ = ["ScanHistoryDeviceViewer", "ScanHistoryMetadataViewer", "ScanHistoryView"]
@@ -330,8 +330,10 @@ class ScanHistoryView(BECWidget, QtWidgets.QTreeWidget):
if __name__ == "__main__": # pragma: no cover if __name__ == "__main__": # pragma: no cover
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from bec_widgets.widgets.services.scan_history_browser.components import ( from bec_widgets.widgets.services.scan_history_browser.components.scan_history_device_viewer import (
ScanHistoryDeviceViewer, ScanHistoryDeviceViewer,
)
from bec_widgets.widgets.services.scan_history_browser.components.scan_history_metadata_viewer import (
ScanHistoryMetadataViewer, ScanHistoryMetadataViewer,
) )
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
@@ -1,9 +1,13 @@
from qtpy import QtCore, QtWidgets from qtpy import QtCore, QtWidgets
from bec_widgets.utils.bec_widget import BECWidget, ConnectionConfig from bec_widgets.utils.bec_widget import BECWidget, ConnectionConfig
from bec_widgets.widgets.services.scan_history_browser.components import ( from bec_widgets.widgets.services.scan_history_browser.components.scan_history_device_viewer import (
ScanHistoryDeviceViewer, ScanHistoryDeviceViewer,
)
from bec_widgets.widgets.services.scan_history_browser.components.scan_history_metadata_viewer import (
ScanHistoryMetadataViewer, ScanHistoryMetadataViewer,
)
from bec_widgets.widgets.services.scan_history_browser.components.scan_history_view import (
ScanHistoryView, ScanHistoryView,
) )
@@ -3,8 +3,7 @@ from qtpy import QtCore, QtGui
from qtpy.QtCore import Property, Signal, Slot from qtpy.QtCore import Property, Signal, Slot
from qtpy.QtWidgets import QSizePolicy, QVBoxLayout, QWidget from qtpy.QtWidgets import QSizePolicy, QVBoxLayout, QWidget
from bec_widgets.utils import Colors from bec_widgets.utils.colors import Colors
from bec_widgets.utils.bec_widget import BECWidget
class RoundedColorMapButton(ColorMapButton): class RoundedColorMapButton(ColorMapButton):
@@ -19,7 +19,7 @@ from qtpy.QtWidgets import (
QWidget, QWidget,
) )
from bec_widgets.utils import BECConnector from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.utils.widget_highlighter import WidgetHighlighter from bec_widgets.utils.widget_highlighter import WidgetHighlighter
from bec_widgets.utils.widget_io import WidgetHierarchy from bec_widgets.utils.widget_io import WidgetHierarchy
+1 -1
View File
@@ -5,7 +5,7 @@ import random
import pytest import pytest
from bec_widgets.cli.client_utils import BECGuiClient from bec_widgets.cli.client_utils import BECGuiClient
from bec_widgets.widgets.control.scan_control import ScanControl from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
# pylint: disable=unused-argument # pylint: disable=unused-argument
# pylint: disable=redefined-outer-name # pylint: disable=redefined-outer-name
-7
View File
@@ -89,13 +89,6 @@ def test_available_widgets(qtbot, connected_client_gui_obj):
# Skip private attributes # Skip private attributes
if object_name.startswith("_"): if object_name.startswith("_"):
continue continue
# Skip BECShell as ttyd is not installed
if object_name == "BECShell":
continue
# Skip BecConsole as ttyd is not installed
if object_name == "BecConsole":
continue
############################# #############################
######### Add widget ######## ######### Add widget ########
+1 -1
View File
@@ -10,7 +10,7 @@ except ImportError:
from qtpy.QtWidgets import QGridLayout from qtpy.QtWidgets import QGridLayout
from bec_widgets.utils.widget_io import WidgetIO from bec_widgets.utils.widget_io import WidgetIO
from bec_widgets.widgets.control.scan_control import ScanControl from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
+1 -1
View File
@@ -5,7 +5,7 @@ import pytest
from qtpy.QtCore import QObject from qtpy.QtCore import QObject
from qtpy.QtWidgets import QApplication, QWidget from qtpy.QtWidgets import QApplication, QWidget
from bec_widgets.utils import BECConnector from bec_widgets.utils.bec_connector import BECConnector
from bec_widgets.utils.error_popups import SafeProperty from bec_widgets.utils.error_popups import SafeProperty
from bec_widgets.utils.error_popups import SafeSlot as Slot from bec_widgets.utils.error_popups import SafeSlot as Slot
+5 -4
View File
@@ -4,12 +4,13 @@ from pydantic import ValidationError
from qtpy.QtGui import QColor from qtpy.QtGui import QColor
from qtpy.QtWidgets import QVBoxLayout, QWidget from qtpy.QtWidgets import QVBoxLayout, QWidget
from bec_widgets.utils import Colors, ConnectionConfig from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import apply_theme from bec_widgets.utils.colors import Colors, apply_theme
from bec_widgets.widgets.plots.waveform.curve import CurveConfig from bec_widgets.widgets.plots.waveform.curve import CurveConfig
from tests.unit_tests.client_mocks import mocked_client
from tests.unit_tests.conftest import create_widget from .client_mocks import mocked_client
from .conftest import create_widget
def test_color_validation_CSS(): def test_color_validation_CSS():
+3 -3
View File
@@ -4,11 +4,11 @@ import pytest
from qtpy.QtCore import QPointF, Qt from qtpy.QtCore import QPointF, Qt
from qtpy.QtGui import QTransform from qtpy.QtGui import QTransform
from bec_widgets.utils import Crosshair from bec_widgets.utils.crosshair import Crosshair
from bec_widgets.widgets.plots.image.image_item import ImageItem from bec_widgets.widgets.plots.image.image_item import ImageItem
from bec_widgets.widgets.plots.waveform.waveform import Waveform from bec_widgets.widgets.plots.waveform.waveform import Waveform
from tests.unit_tests.client_mocks import mocked_client
from .client_mocks import mocked_client
from .conftest import create_widget from .conftest import create_widget
# pylint: disable = redefined-outer-name # pylint: disable = redefined-outer-name
@@ -214,7 +214,7 @@ def test_crosshair_clicked_signal(qtbot, plot_widget_with_crosshair):
pos_in_widget = graphics_view.mapFromScene(pos_in_scene) pos_in_widget = graphics_view.mapFromScene(pos_in_scene)
# Simulate mouse click # Simulate mouse click
qtbot.mouseClick(graphics_view.viewport(), Qt.LeftButton, pos=pos_in_widget) qtbot.mouseClick(graphics_view.viewport(), Qt.MouseButton.LeftButton, pos=pos_in_widget)
x, y = emitted_positions[0] x, y = emitted_positions[0]
+3 -2
View File
@@ -12,8 +12,9 @@ from bec_widgets.widgets.plots.waveform.settings.curve_settings.curve_tree impor
ScanIndexValidator, ScanIndexValidator,
) )
from bec_widgets.widgets.plots.waveform.waveform import Waveform from bec_widgets.widgets.plots.waveform.waveform import Waveform
from tests.unit_tests.client_mocks import dap_plugin_message, mocked_client, mocked_client_with_dap
from tests.unit_tests.conftest import create_widget from .client_mocks import dap_plugin_message, mocked_client, mocked_client_with_dap
from .conftest import create_widget
################################################## ##################################################
# CurveSetting # CurveSetting
+1 -1
View File
@@ -19,7 +19,7 @@ from .client_mocks import mocked_client
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from qtpy.QtWidgets import QListWidgetItem from qtpy.QtWidgets import QListWidgetItem
from bec_widgets.widgets.services.device_browser.device_item import DeviceItem from bec_widgets.widgets.services.device_browser.device_item.device_item import DeviceItem
# pylint: disable=no-member # pylint: disable=no-member
@@ -16,8 +16,6 @@ from qtpy import QtCore, QtGui, QtWidgets
from bec_widgets.utils.bec_list import BECList from bec_widgets.utils.bec_list import BECList
from bec_widgets.utils.colors import get_accent_colors from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.widgets.control.device_manager import DeviceTable, DMConfigView, DocstringView
from bec_widgets.widgets.control.device_manager.components import docstring_to_markdown
from bec_widgets.widgets.control.device_manager.components.constants import HEADERS_HELP_MD from bec_widgets.widgets.control.device_manager.components.constants import HEADERS_HELP_MD
from bec_widgets.widgets.control.device_manager.components.device_config_template.device_config_template import ( from bec_widgets.widgets.control.device_manager.components.device_config_template.device_config_template import (
DeviceConfigTemplate, DeviceConfigTemplate,
@@ -34,9 +32,17 @@ from bec_widgets.widgets.control.device_manager.components.device_config_templat
ReadoutPriorityComboBox, ReadoutPriorityComboBox,
_try_literal_eval, _try_literal_eval,
) )
from bec_widgets.widgets.control.device_manager.components.device_table.device_table import (
DeviceTable,
)
from bec_widgets.widgets.control.device_manager.components.device_table.device_table_row import ( from bec_widgets.widgets.control.device_manager.components.device_table.device_table_row import (
DeviceTableRow, DeviceTableRow,
) )
from bec_widgets.widgets.control.device_manager.components.dm_config_view import DMConfigView
from bec_widgets.widgets.control.device_manager.components.dm_docstring_view import (
DocstringView,
docstring_to_markdown,
)
from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation import ( from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation import (
DeviceTest, DeviceTest,
LegendLabel, LegendLabel,
+3 -4
View File
@@ -31,12 +31,11 @@ from bec_widgets.applications.views.device_manager_view.device_manager_view impo
DeviceManagerWidget, DeviceManagerWidget,
) )
from bec_widgets.utils.colors import get_accent_colors from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.widgets.control.device_manager.components import ( from bec_widgets.widgets.control.device_manager.components.device_table.device_table import (
DeviceTable, DeviceTable,
DMConfigView,
DocstringView,
OphydValidation,
) )
from bec_widgets.widgets.control.device_manager.components.dm_config_view import DMConfigView
from bec_widgets.widgets.control.device_manager.components.dm_docstring_view import DocstringView
from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation import ( from bec_widgets.widgets.control.device_manager.components.ophyd_validation.ophyd_validation import (
ConfigStatus, ConfigStatus,
ConnectionStatus, ConnectionStatus,
+1 -3
View File
@@ -16,9 +16,7 @@ from bec_widgets.widgets.plots.heatmap.heatmap import (
) )
# pytest: disable=unused-import # pytest: disable=unused-import
from tests.unit_tests.client_mocks import mocked_client from .client_mocks import create_dummy_scan_item, mocked_client
from .client_mocks import create_dummy_scan_item
@pytest.fixture @pytest.fixture
+10 -5
View File
@@ -7,8 +7,9 @@ from qtpy.QtCore import QPointF, Qt
from bec_widgets.widgets.plots.image.image import Image from bec_widgets.widgets.plots.image.image import Image
from bec_widgets.widgets.plots.image.setting_widgets.image_roi_tree import ROIPropertyTree from bec_widgets.widgets.plots.image.setting_widgets.image_roi_tree import ROIPropertyTree
from bec_widgets.widgets.plots.roi.image_roi import CircularROI, RectangularROI from bec_widgets.widgets.plots.roi.image_roi import CircularROI, RectangularROI
from tests.unit_tests.client_mocks import mocked_client
from tests.unit_tests.conftest import create_widget from .client_mocks import mocked_client
from .conftest import create_widget
@pytest.fixture @pytest.fixture
@@ -44,7 +45,7 @@ def test_initialization(roi_tree, image_widget):
assert roi_tree.plot == image_widget.plot_item assert roi_tree.plot == image_widget.plot_item
assert roi_tree.controller == image_widget.roi_controller assert roi_tree.controller == image_widget.roi_controller
assert isinstance(roi_tree.roi_items, dict) assert isinstance(roi_tree.roi_items, dict)
assert len(roi_tree.tree.findItems("", Qt.MatchContains)) == 0 # Empty tree initially assert len(roi_tree.tree.findItems("", Qt.MatchFlag.MatchContains)) == 0 # Empty tree initially
# Check toolbar actions # Check toolbar actions
assert roi_tree.toolbar.components.get_action("roi_rectangle") assert roi_tree.toolbar.components.get_action("roi_rectangle")
@@ -66,12 +67,16 @@ def test_controller_connection(roi_tree, image_widget):
# Verify that ROI was added to the tree # Verify that ROI was added to the tree
assert roi in roi_tree.roi_items assert roi in roi_tree.roi_items
assert len(roi_tree.tree.findItems("test_roi", Qt.MatchExactly, roi_tree.COL_ROI)) == 1 assert (
len(roi_tree.tree.findItems("test_roi", Qt.MatchFlag.MatchExactly, roi_tree.COL_ROI)) == 1
)
# Remove ROI via controller and check that it's removed from the tree # Remove ROI via controller and check that it's removed from the tree
image_widget.remove_roi(0) image_widget.remove_roi(0)
assert roi not in roi_tree.roi_items assert roi not in roi_tree.roi_items
assert len(roi_tree.tree.findItems("test_roi", Qt.MatchExactly, roi_tree.COL_ROI)) == 0 assert (
len(roi_tree.tree.findItems("test_roi", Qt.MatchFlag.MatchExactly, roi_tree.COL_ROI)) == 0
)
def test_expand_collapse_tree(roi_tree, image_widget): def test_expand_collapse_tree(roi_tree, image_widget):
+3 -2
View File
@@ -12,8 +12,9 @@ from bec_widgets.widgets.plots.roi.image_roi import (
RectangularROI, RectangularROI,
ROIController, ROIController,
) )
from tests.unit_tests.client_mocks import mocked_client
from tests.unit_tests.conftest import create_widget from .client_mocks import mocked_client
from .conftest import create_widget
@pytest.fixture(params=["rect", "circle", "ellipse"]) @pytest.fixture(params=["rect", "circle", "ellipse"])
+3 -2
View File
@@ -5,8 +5,9 @@ from bec_lib.endpoints import MessageEndpoints
from qtpy.QtCore import QPointF from qtpy.QtCore import QPointF
from bec_widgets.widgets.plots.image.image import Image from bec_widgets.widgets.plots.image.image import Image
from tests.unit_tests.client_mocks import mocked_client
from tests.unit_tests.conftest import create_widget from .client_mocks import mocked_client
from .conftest import create_widget
################################################## ##################################################
# Image widget base functionality tests # Image widget base functionality tests
+1 -1
View File
@@ -1,8 +1,8 @@
from qtpy.QtTest import QSignalSpy from qtpy.QtTest import QSignalSpy
from bec_widgets.widgets.plots.motor_map.motor_map import MotorMap from bec_widgets.widgets.plots.motor_map.motor_map import MotorMap
from tests.unit_tests.client_mocks import mocked_client
from .client_mocks import mocked_client
from .conftest import create_widget from .conftest import create_widget
@@ -1,8 +1,8 @@
import numpy as np import numpy as np
from bec_widgets.widgets.plots.multi_waveform.multi_waveform import MultiWaveform from bec_widgets.widgets.plots.multi_waveform.multi_waveform import MultiWaveform
from tests.unit_tests.client_mocks import mocked_client
from .client_mocks import mocked_client
from .conftest import create_widget from .conftest import create_widget
################################################## ##################################################
+13 -1
View File
@@ -1,4 +1,6 @@
from bec_widgets.utils.plugin_utils import get_custom_classes import sys
from bec_widgets.utils.plugin_utils import get_custom_class_references, get_custom_classes
def test_client_generator_classes(): def test_client_generator_classes():
@@ -10,3 +12,13 @@ def test_client_generator_classes():
assert "Waveform" in connector_cls_names assert "Waveform" in connector_cls_names
assert "MotorMap" in plugins assert "MotorMap" in plugins
assert "NonExisting" not in plugins assert "NonExisting" not in plugins
def test_get_custom_class_references_avoids_importing_widget_modules():
target_module = "bec_widgets.widgets.plots.image.image"
sys.modules.pop(target_module, None)
references = get_custom_class_references("bec_widgets", use_cache=False)
assert "Image" in [reference.name for reference in references]
assert target_module not in sys.modules
+1 -1
View File
@@ -8,7 +8,7 @@ from qtpy.QtCore import QEvent, QPoint, QPointF, Qt
from qtpy.QtGui import QColor, QMouseEvent from qtpy.QtGui import QColor, QMouseEvent
from qtpy.QtWidgets import QApplication from qtpy.QtWidgets import QApplication
from bec_widgets.utils import Colors from bec_widgets.utils.colors import Colors
from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_bar import ( from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_bar import (
RingProgressBar, RingProgressBar,
RingProgressContainerWidget, RingProgressContainerWidget,
@@ -3,7 +3,8 @@ import pytest
from bec_widgets.utils.settings_dialog import SettingsDialog from bec_widgets.utils.settings_dialog import SettingsDialog
from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_bar import RingProgressBar from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_bar import RingProgressBar
from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_settings_cards import RingSettings from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_settings_cards import RingSettings
from tests.unit_tests.client_mocks import mocked_client
from .client_mocks import mocked_client
@pytest.fixture @pytest.fixture
+36 -16
View File
@@ -1,8 +1,9 @@
import sys
from types import ModuleType
from unittest.mock import patch from unittest.mock import patch
from bec_widgets.cli.rpc.rpc_widget_handler import RPCWidgetHandler from bec_widgets.cli.rpc.rpc_widget_handler import RPCWidgetHandler
from bec_widgets.utils.bec_widget import BECWidget from bec_widgets.utils.plugin_utils import BECClassReference
from bec_widgets.utils.plugin_utils import BECClassContainer, BECClassInfo
def test_rpc_widget_handler(): def test_rpc_widget_handler():
@@ -12,19 +13,38 @@ def test_rpc_widget_handler():
assert "BECDockArea" in handler.widget_classes assert "BECDockArea" in handler.widget_classes
class _TestPluginWidget(BECWidget): ... def test_duplicate_plugins_not_allowed(monkeypatch):
builtin_module = ModuleType("builtin_test_widgets")
plugin_module = ModuleType("plugin_test_widgets")
SharedWidgetBuiltin = type("SharedWidget", (), {})
SharedWidgetBuiltin.__module__ = builtin_module.__name__
setattr(builtin_module, "SharedWidget", SharedWidgetBuiltin)
@patch( SharedWidgetPlugin = type("SharedWidget", (), {})
"bec_widgets.cli.rpc.rpc_widget_handler.get_all_plugin_widgets", SharedWidgetPlugin.__module__ = plugin_module.__name__
return_value=BECClassContainer( setattr(plugin_module, "SharedWidget", SharedWidgetPlugin)
[
BECClassInfo(name="DeviceComboBox", obj=_TestPluginWidget, module="", file=""), NewPluginWidget = type("NewPluginWidget", (), {})
BECClassInfo(name="NewPluginWidget", obj=_TestPluginWidget, module="", file=""), NewPluginWidget.__module__ = plugin_module.__name__
] setattr(plugin_module, "NewPluginWidget", NewPluginWidget)
),
) monkeypatch.setitem(sys.modules, builtin_module.__name__, builtin_module)
def test_duplicate_plugins_not_allowed(_): monkeypatch.setitem(sys.modules, plugin_module.__name__, plugin_module)
handler = RPCWidgetHandler()
assert handler.widget_classes["DeviceComboBox"] is not _TestPluginWidget with (
assert handler.widget_classes["NewPluginWidget"] is _TestPluginWidget patch(
"bec_widgets.cli.rpc.rpc_widget_handler.get_all_plugin_widget_references",
return_value=[
BECClassReference(name="SharedWidget", module=plugin_module.__name__),
BECClassReference(name="NewPluginWidget", module=plugin_module.__name__),
],
),
patch(
"bec_widgets.cli.rpc.rpc_widget_handler.get_custom_class_references",
return_value=[BECClassReference(name="SharedWidget", module=builtin_module.__name__)],
),
):
handler = RPCWidgetHandler()
assert handler.widget_classes["SharedWidget"].__module__ == builtin_module.__name__
assert handler.widget_classes["NewPluginWidget"].__module__ == plugin_module.__name__
+1 -1
View File
@@ -9,7 +9,7 @@ from qtpy.QtCore import QModelIndex, Qt
from bec_widgets.utils.forms_from_types.items import StrFormItem from bec_widgets.utils.forms_from_types.items import StrFormItem
from bec_widgets.utils.widget_io import WidgetIO from bec_widgets.utils.widget_io import WidgetIO
from bec_widgets.widgets.control.scan_control import ScanControl from bec_widgets.widgets.control.scan_control.scan_control import ScanControl
from .client_mocks import mocked_client from .client_mocks import mocked_client
@@ -6,9 +6,13 @@ from pytestqt import qtbot
from qtpy import QtCore from qtpy import QtCore
from bec_widgets.utils.colors import get_accent_colors from bec_widgets.utils.colors import get_accent_colors
from bec_widgets.widgets.services.scan_history_browser.components import ( from bec_widgets.widgets.services.scan_history_browser.components.scan_history_device_viewer import (
ScanHistoryDeviceViewer, ScanHistoryDeviceViewer,
)
from bec_widgets.widgets.services.scan_history_browser.components.scan_history_metadata_viewer import (
ScanHistoryMetadataViewer, ScanHistoryMetadataViewer,
)
from bec_widgets.widgets.services.scan_history_browser.components.scan_history_view import (
ScanHistoryView, ScanHistoryView,
) )
from bec_widgets.widgets.services.scan_history_browser.scan_history_browser import ( from bec_widgets.widgets.services.scan_history_browser.scan_history_browser import (
+1 -1
View File
@@ -10,8 +10,8 @@ from bec_widgets.widgets.plots.scatter_waveform.scatter_waveform import ScatterW
from bec_widgets.widgets.plots.scatter_waveform.settings.scatter_curve_setting import ( from bec_widgets.widgets.plots.scatter_waveform.settings.scatter_curve_setting import (
ScatterCurveSettings, ScatterCurveSettings,
) )
from tests.unit_tests.client_mocks import create_dummy_scan_item, mocked_client
from .client_mocks import create_dummy_scan_item, mocked_client
from .conftest import create_widget from .conftest import create_widget
+2 -2
View File
@@ -18,7 +18,8 @@ from bec_widgets.widgets.plots.waveform.waveform import Waveform
from bec_widgets.widgets.services.scan_history_browser.scan_history_browser import ( from bec_widgets.widgets.services.scan_history_browser.scan_history_browser import (
ScanHistoryBrowser, ScanHistoryBrowser,
) )
from tests.unit_tests.client_mocks import (
from .client_mocks import (
DummyData, DummyData,
create_dummy_scan_item, create_dummy_scan_item,
dap_plugin_message, dap_plugin_message,
@@ -26,7 +27,6 @@ from tests.unit_tests.client_mocks import (
mocked_client, mocked_client,
mocked_client_with_dap, mocked_client_with_dap,
) )
from .conftest import create_widget from .conftest import create_widget
# pylint: disable=unexpected-keyword-arg # pylint: disable=unexpected-keyword-arg