mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-12 18:51:50 +02:00
fix: parse config on submission and reload after
This commit is contained in:
@ -72,7 +72,7 @@ class DictBackedTableModel(QAbstractTableModel):
|
||||
def replaceData(self, data: dict):
|
||||
self.delete_rows(list(range(len(self._data))))
|
||||
self.resetInternalData()
|
||||
self._data = [[k, v] for k, v in data.items()]
|
||||
self._data = [[str(k), str(v)] for k, v in data.items()]
|
||||
self.dataChanged.emit(self.index(0, 0), self.index(len(self._data), 1))
|
||||
|
||||
def update_disallowed_keys(self, keys: list[str]):
|
||||
|
@ -87,14 +87,13 @@ class DeviceBrowser(BECWidget, QWidget):
|
||||
for device, device_obj in self.dev.items():
|
||||
item = QListWidgetItem(self.dev_list)
|
||||
device_item = DeviceItem(
|
||||
parent=self, device=device, icon=map_device_type_to_icon(device_obj)
|
||||
parent=self,
|
||||
device=device,
|
||||
devices=self.dev,
|
||||
icon=map_device_type_to_icon(device_obj),
|
||||
)
|
||||
|
||||
device_item.expansion_state_changed.connect(partial(_updatesize, item, device_item))
|
||||
|
||||
device_config = self.dev[device]._config # pylint: disable=protected-access
|
||||
device_item.set_display_config(device_config)
|
||||
tooltip = device_config.get("description", "")
|
||||
tooltip = self.dev[device]._config.get("description", "")
|
||||
device_item.setToolTip(tooltip)
|
||||
device_item.broadcast_size_hint.connect(item.setSizeHint)
|
||||
item.setSizeHint(device_item.sizeHint())
|
||||
|
@ -1,5 +1,4 @@
|
||||
import traceback
|
||||
from threading import Thread
|
||||
from ast import literal_eval
|
||||
|
||||
from bec_lib.atlas_models import Device as DeviceConfigModel
|
||||
from bec_lib.config_helper import CONF as DEVICE_CONF_KEYS
|
||||
@ -10,6 +9,7 @@ from qtpy.QtWidgets import (
|
||||
QApplication,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QLabel,
|
||||
QStackedLayout,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
@ -26,7 +26,7 @@ logger = bec_logger.logger
|
||||
|
||||
|
||||
class _CommSignals(QObject):
|
||||
error = Signal(str)
|
||||
error = Signal(Exception)
|
||||
done = Signal()
|
||||
|
||||
|
||||
@ -48,18 +48,17 @@ class _CommunicateUpdate(QRunnable):
|
||||
)
|
||||
logger.info("Waiting for config reply")
|
||||
reply = self.config_helper.wait_for_config_reply(RID, timeout=timeout)
|
||||
logger.info("Handling config reply")
|
||||
self.config_helper.handle_update_reply(reply, RID, timeout)
|
||||
logger.info("Done updating config!")
|
||||
except Exception as e:
|
||||
self.signals.error.emit(
|
||||
f"Error updating config: \n {''.join(traceback.format_exception(e))}"
|
||||
)
|
||||
self.signals.error.emit(e)
|
||||
finally:
|
||||
self.signals.done.emit()
|
||||
|
||||
|
||||
class DeviceConfigDialog(BECWidget, QDialog):
|
||||
RPC = False
|
||||
applied = Signal()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -78,6 +77,14 @@ class DeviceConfigDialog(BECWidget, QDialog):
|
||||
self._container = QStackedLayout()
|
||||
self._container.setStackingMode(QStackedLayout.StackAll)
|
||||
|
||||
self._layout = QVBoxLayout()
|
||||
user_warning = QLabel(
|
||||
"Warning: edit items here at your own risk - minimal validation is applied to the entered values.\n"
|
||||
"Items in the deviceConfig dictionary should correspond to python literals, e.g. numbers, lists, strings (including quotes), etc."
|
||||
)
|
||||
user_warning.setWordWrap(True)
|
||||
user_warning.setStyleSheet("QLabel { color: red; }")
|
||||
self._layout.addWidget(user_warning)
|
||||
self._add_form()
|
||||
self._add_overlay()
|
||||
self._add_buttons()
|
||||
@ -87,7 +94,6 @@ class DeviceConfigDialog(BECWidget, QDialog):
|
||||
|
||||
def _add_form(self):
|
||||
self._form_widget = QWidget()
|
||||
self._layout = QVBoxLayout()
|
||||
self._form_widget.setLayout(self._layout)
|
||||
self._form = DeviceConfigForm()
|
||||
self._layout.addWidget(self._form)
|
||||
@ -137,13 +143,20 @@ class DeviceConfigDialog(BECWidget, QDialog):
|
||||
|
||||
def updated_config(self):
|
||||
new_config = self._form.get_form_data()
|
||||
return {
|
||||
diff = {
|
||||
k: v for k, v in new_config.items() if self._initial_config.get(k) != new_config.get(k)
|
||||
}
|
||||
# TODO: replace when https://github.com/bec-project/bec/issues/528 is resolved
|
||||
if diff.get("deviceConfig") is not None:
|
||||
diff["deviceConfig"] = {
|
||||
k: literal_eval(str(v)) for k, v in diff["deviceConfig"].items()
|
||||
}
|
||||
return diff
|
||||
|
||||
@SafeSlot()
|
||||
def apply(self):
|
||||
self._process_update_action()
|
||||
self.applied.emit()
|
||||
|
||||
@SafeSlot()
|
||||
def accept(self):
|
||||
@ -181,9 +194,9 @@ class DeviceConfigDialog(BECWidget, QDialog):
|
||||
self._fetch_config()
|
||||
self._fill_form()
|
||||
|
||||
@SafeSlot(str)
|
||||
def update_error(self, e: str):
|
||||
logger.error(e)
|
||||
@SafeSlot(Exception, popup_error=True)
|
||||
def update_error(self, e: Exception):
|
||||
raise RuntimeError("Failed to update device configuration") from e
|
||||
|
||||
def _start_waiting_display(self):
|
||||
self._overlay_widget.setVisible(True)
|
||||
|
@ -1,7 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
|
||||
from bec_lib.atlas_models import Device as DeviceConfigModel
|
||||
from pydantic import BaseModel
|
||||
from qtpy.QtWidgets import QApplication
|
||||
@ -13,7 +11,6 @@ from bec_widgets.utils.forms_from_types.items import (
|
||||
DEFAULT_WIDGET_TYPES,
|
||||
BoolFormItem,
|
||||
BoolToggleFormItem,
|
||||
widget_from_type,
|
||||
)
|
||||
|
||||
|
||||
@ -46,6 +43,10 @@ class DeviceConfigForm(PydanticModelForm):
|
||||
theme = get_theme_name()
|
||||
self.setStyleSheet(styles.pretty_display_theme(theme))
|
||||
|
||||
def get_form_data(self):
|
||||
"""Get the entered metadata as a dict."""
|
||||
return self._md_schema.model_validate(super().get_form_data()).model_dump()
|
||||
|
||||
def _connect_to_theme_change(self):
|
||||
"""Connect to the theme change signal."""
|
||||
qapp = QApplication.instance()
|
||||
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib.atlas_models import Device as DeviceConfigModel
|
||||
from bec_lib.devicemanager import DeviceContainer
|
||||
from bec_lib.logger import bec_logger
|
||||
from bec_qthemes import material_icon
|
||||
from qtpy.QtCore import QMimeData, QSize, Qt, Signal
|
||||
@ -30,9 +31,9 @@ class DeviceItem(ExpandableGroupFrame):
|
||||
|
||||
RPC = False
|
||||
|
||||
def __init__(self, parent, device: str, icon: str = "") -> None:
|
||||
def __init__(self, parent, device: str, devices: DeviceContainer, icon: str = "") -> None:
|
||||
super().__init__(parent, title=device, expanded=False, icon=icon)
|
||||
|
||||
self.dev = devices
|
||||
self._drag_pos = None
|
||||
self._expanded_first_time = False
|
||||
self._data = None
|
||||
@ -54,6 +55,8 @@ class DeviceItem(ExpandableGroupFrame):
|
||||
|
||||
def _create_edit_dialog(self):
|
||||
dialog = DeviceConfigDialog(parent=self, device=self.device)
|
||||
dialog.accepted.connect(self._reload_config)
|
||||
dialog.applied.connect(self._reload_config)
|
||||
dialog.open()
|
||||
|
||||
@SafeSlot()
|
||||
@ -62,8 +65,7 @@ class DeviceItem(ExpandableGroupFrame):
|
||||
self._expanded_first_time = True
|
||||
self.form = DeviceConfigForm(parent=self, pretty_display=True)
|
||||
self._contents.layout().addWidget(self.form)
|
||||
if self._data:
|
||||
self.form.set_data(self._data)
|
||||
self._reload_config()
|
||||
self.broadcast_size_hint.emit(self.sizeHint())
|
||||
super().switch_expanded_state()
|
||||
if self._expanded_first_time:
|
||||
@ -74,6 +76,11 @@ class DeviceItem(ExpandableGroupFrame):
|
||||
self.adjustSize()
|
||||
self.broadcast_size_hint.emit(self.sizeHint())
|
||||
|
||||
@SafeSlot(popup_error=True)
|
||||
def _reload_config(self, *_):
|
||||
self.dev[self.device].read_configuration(cached=False)
|
||||
self.set_display_config(self.dev[self.device]._config)
|
||||
|
||||
def set_display_config(self, config_dict: dict):
|
||||
"""Set the displayed information from a device config dict, which must conform to the
|
||||
bec_lib.atlas_models.Device config model."""
|
||||
|
@ -26,6 +26,7 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
@pytest.fixture
|
||||
def device_browser(qtbot, mocked_client):
|
||||
dev_browser = DeviceBrowser(client=mocked_client)
|
||||
dev_browser.dev["samx"].read_configuration = mock.MagicMock()
|
||||
qtbot.addWidget(dev_browser)
|
||||
qtbot.waitExposed(dev_browser)
|
||||
yield dev_browser
|
||||
@ -88,8 +89,9 @@ def test_device_item_expansion(device_browser, qtbot):
|
||||
form = widget._contents.layout().itemAt(0).widget()
|
||||
qtbot.waitUntil(lambda: isinstance(form, DeviceConfigForm), timeout=500)
|
||||
assert widget.expanded
|
||||
device_browser.dev["samx"].read_configuration.assert_called()
|
||||
assert (name_field := form.widget_dict.get("name")) is not None
|
||||
assert name_field.getValue() == "samx"
|
||||
qtbot.waitUntil(lambda: name_field.getValue() == "samx", timeout=500)
|
||||
qtbot.mouseClick(widget._expansion_button, Qt.MouseButton.LeftButton)
|
||||
assert not widget.expanded
|
||||
|
||||
|
@ -81,8 +81,8 @@ def test_waiting_display(dialog, qtbot):
|
||||
def test_update_cycle(dialog, qtbot):
|
||||
update = {"enabled": False, "readoutPriority": "baseline", "deviceTags": {"tag"}}
|
||||
|
||||
def _mock_send(a, c, w):
|
||||
dialog.client.device_manager.devices["test_device"]._config = c["test_device"]
|
||||
def _mock_send(action="update", config=None, wait_for_response=True, timeout_s=None):
|
||||
dialog.client.device_manager.devices["test_device"]._config = config["test_device"] # type: ignore
|
||||
|
||||
dialog._config_helper.send_config_request = MagicMock(side_effect=_mock_send)
|
||||
for item in dialog._form.enumerate_form_widgets():
|
||||
|
Reference in New Issue
Block a user