1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-04 16:02:51 +01:00

feat: allow setting config in redis

This commit is contained in:
2025-09-10 09:24:57 +02:00
committed by wyzula-jan
parent de8908e9f9
commit a46c557652
4 changed files with 62 additions and 17 deletions

View File

@@ -1,17 +1,19 @@
from __future__ import annotations
import os
from functools import partial
from typing import TYPE_CHECKING, List
import PySide6QtAds as QtAds
import yaml
from bec_lib import config_helper
from bec_lib.bec_yaml_loader import yaml_load
from bec_lib.file_utils import DeviceConfigWriter
from bec_lib.logger import bec_logger
from bec_lib.plugin_helper import plugin_package_name, plugin_repo_path
from bec_qthemes import apply_theme
from PySide6QtAds import CDockManager, CDockWidget
from qtpy.QtCore import Qt, QTimer
from qtpy.QtCore import Qt, QThreadPool, QTimer
from qtpy.QtWidgets import QFileDialog, QMessageBox, QSplitter, QVBoxLayout, QWidget
from bec_widgets import BECWidget
@@ -29,13 +31,21 @@ from bec_widgets.widgets.control.device_manager.components._util import SharedSe
from bec_widgets.widgets.control.device_manager.components.available_device_resources.available_device_resources import (
AvailableDeviceResources,
)
from bec_widgets.widgets.services.device_browser.device_item.config_communicator import (
CommunicateConfigAction,
)
from bec_widgets.widgets.services.device_browser.device_item.device_config_dialog import (
DeviceConfigDialog,
PresetClassDeviceConfigDialog,
)
logger = bec_logger.logger
_yes_no_question = partial(
QMessageBox.question,
buttons=QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
defaultButton=QMessageBox.StandardButton.No,
)
def set_splitter_weights(splitter: QSplitter, weights: List[float]) -> None:
"""
@@ -78,6 +88,7 @@ class DeviceManagerView(BECWidget, QWidget):
def __init__(self, parent=None, *args, **kwargs):
super().__init__(parent=parent, client=None, *args, **kwargs)
self._config_helper = config_helper.ConfigHelper(self.client.connector)
self._shared_selection = SharedSelectionSignal()
# Top-level layout hosting a toolbar and the dock manager
@@ -326,12 +337,10 @@ class DeviceManagerView(BECWidget, QWidget):
@SafeSlot()
def _load_redis_action(self):
"""Action for the 'load_redis' action to load the current config from Redis for the io_bundle of the toolbar."""
reply = QMessageBox.question(
reply = _yes_no_question(
self,
"Load currently active config",
"Do you really want to discard the current config and reload?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No,
)
if reply == QMessageBox.StandardButton.Yes and self.client.device_manager is not None:
self.device_table_view.set_device_config(
@@ -340,6 +349,32 @@ class DeviceManagerView(BECWidget, QWidget):
else:
return
@SafeSlot()
def _update_redis_action(self):
"""Action to push the current composition to Redis"""
reply = _yes_no_question(
self,
"Push composition to Redis",
"Do you really want to replace the active configuration in the BEC server with the current composition? ",
)
if reply != QMessageBox.StandardButton.Yes:
return
if self.device_table_view.table.contains_invalid_devices():
return QMessageBox.warning(
self, "Validation has errors!", "Please resolve before proceeding."
)
if self.ophyd_test_view.validation_running():
return QMessageBox.warning(
self, "Validation has not completed.", "Please wait for the validation to finish."
)
self._push_compositiion_to_redis()
def _push_compositiion_to_redis(self):
config = {cfg.pop("name"): cfg for cfg in self.device_table_view.table.all_configs()}
threadpool = QThreadPool.globalInstance()
comm = CommunicateConfigAction(self._config_helper, None, config, "set")
threadpool.start(comm)
@SafeSlot()
def _save_to_disk_action(self):
"""Action for the 'safe_to_disk' action to save the current config to disk."""
@@ -360,24 +395,15 @@ class DeviceManagerView(BECWidget, QWidget):
with open(file_path, "w") as file:
file.write(yaml.dump(config))
# TODO add here logic, should be asyncronous, but probably block UI, and show a loading spinner. If failed, it should report..
@SafeSlot()
def _update_redis_action(self):
"""Action for the 'update_redis' action to update the current config in Redis."""
config = self.device_table_view.get_device_config()
reply = self._coming_soon()
# Table actions
@SafeSlot()
def _reset_composed_view(self):
"""Action for the 'reset_composed_view' action to reset the composed view."""
reply = QMessageBox.question(
reply = _yes_no_question(
self,
"Clear View",
"You are about to clear the current composed config view, please confirm...",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No,
)
if reply == QMessageBox.StandardButton.Yes:
self.device_table_view.clear_device_configs()

View File

@@ -313,7 +313,7 @@ class DeviceTableModel(QtCore.QAbstractTableModel):
def get_device_config(self) -> list[dict[str, Any]]:
"""Method to get the device configuration."""
return self._device_config
return copy.deepcopy(self._device_config)
def device_names(self, configs: _DeviceCfgIter | None = None) -> set[str]:
_configs = self._device_config if configs is None else configs
@@ -431,6 +431,9 @@ class DeviceTableModel(QtCore.QAbstractTableModel):
index = self.index(row, 0)
self.dataChanged.emit(index, index, [Qt.ItemDataRole.DisplayRole])
def validation_statuses(self):
return copy.deepcopy(self._validation_status)
class BECTableView(QtWidgets.QTableView):
"""Table View with custom keyPressEvent to delete rows with backspace or delete key"""
@@ -455,6 +458,12 @@ class BECTableView(QtWidgets.QTableView):
return self.delete_selected()
return super().keyPressEvent(event)
def contains_invalid_devices(self):
return ValidationStatus.FAILED in self.model().sourceModel().validation_statuses().values()
def all_configs(self):
return self.model().sourceModel().get_device_config()
def selected_configs(self):
return self.model().get_row_data(self.selectionModel().selectedRows())

View File

@@ -373,6 +373,9 @@ class DMOphydTest(BECWidget, QtWidgets.QWidget):
)
return html
def validation_running(self):
return self._device_list_items != {}
@SafeSlot()
def clear_list(self):
"""Clear the device list."""

View File

@@ -34,7 +34,11 @@ class CommunicateConfigAction(QRunnable):
@SafeSlot()
def run(self):
try:
if self.action in ["add", "update", "remove"]:
if self.action == "set":
self._process(
{"action": self.action, "config": self.config, "wait_for_response": False}
)
elif self.action in ["add", "update", "remove"]:
if (dev_name := self.device or self.config.get("name")) is None:
raise ValueError(
"Must be updating a device or be supplied a name for a new device"
@@ -57,6 +61,9 @@ class CommunicateConfigAction(QRunnable):
"config": {dev_name: self.config},
"wait_for_response": False,
}
self._process(req_args)
def _process(self, req_args: dict):
timeout = (
self.config_helper.suggested_timeout_s(self.config) if self.config is not None else 20
)