mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-06-19 11:30:57 +02:00
fix(beamline-states): better pydantic model handling
This commit is contained in:
@@ -3,10 +3,9 @@ from __future__ import annotations
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from bec_lib import bl_states
|
||||
from bec_lib import bl_states, messages
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_qthemes import material_icon
|
||||
from pydantic import BaseModel
|
||||
from qtpy.QtCore import QAbstractListModel, QModelIndex, QSize, Qt
|
||||
from qtpy.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
@@ -48,7 +47,7 @@ class _BeamlineStateListModel(QAbstractListModel):
|
||||
super().__init__(parent)
|
||||
self._state_order: list[str] = []
|
||||
self._state_rows: dict[str, int] = {}
|
||||
self._state_configs: dict[str, dict[str, Any]] = {}
|
||||
self._state_configs: dict[str, messages.BeamlineStateConfig] = {}
|
||||
|
||||
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int: # noqa: N802
|
||||
return 0 if parent.isValid() else len(self._state_order)
|
||||
@@ -60,7 +59,7 @@ class _BeamlineStateListModel(QAbstractListModel):
|
||||
if role in (Qt.ItemDataRole.DisplayRole, self.NameRole):
|
||||
return name
|
||||
if role == self.ConfigRole:
|
||||
return self._state_configs.get(name, {})
|
||||
return self._state_configs.get(name)
|
||||
return None
|
||||
|
||||
def flags(self, index: QModelIndex) -> Qt.ItemFlag:
|
||||
@@ -68,9 +67,9 @@ class _BeamlineStateListModel(QAbstractListModel):
|
||||
return Qt.ItemFlag.NoItemFlags
|
||||
return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
|
||||
|
||||
def set_states(self, state_configs: list[dict[str, Any]]) -> None:
|
||||
new_order = [str(state["name"]) for state in state_configs if state.get("name")]
|
||||
new_configs = {str(state["name"]): state for state in state_configs if state.get("name")}
|
||||
def set_states(self, state_configs: list[messages.BeamlineStateConfig]) -> None:
|
||||
new_order = [state.name for state in state_configs]
|
||||
new_configs = {state.name: state for state in state_configs}
|
||||
|
||||
for row in reversed(
|
||||
[row for row, name in enumerate(self._state_order) if name not in new_configs]
|
||||
@@ -129,11 +128,8 @@ class _BeamlineStatePillDelegate(QStyledItemDelegate):
|
||||
self, parent: QWidget, _option: QStyleOptionViewItem, index: QModelIndex
|
||||
) -> QWidget:
|
||||
name = index.data(_BeamlineStateListModel.NameRole)
|
||||
state_config = index.data(_BeamlineStateListModel.ConfigRole) or {}
|
||||
title = state_config.get("title") or name
|
||||
pill = BeamlineStatePill(
|
||||
parent=parent, state_name=name, title=title, client=self._manager.client
|
||||
)
|
||||
state_config = index.data(_BeamlineStateListModel.ConfigRole)
|
||||
pill = BeamlineStatePill(parent=parent, state_name=name, client=self._manager.client)
|
||||
pill.idle_card_background = self._manager.idle_card_background
|
||||
pill.set_state_config(state_config)
|
||||
pill.state_changed.connect(self._manager._on_pill_state_changed)
|
||||
@@ -147,9 +143,8 @@ class _BeamlineStatePillDelegate(QStyledItemDelegate):
|
||||
if not isinstance(editor, BeamlineStatePill):
|
||||
return
|
||||
name = index.data(_BeamlineStateListModel.NameRole)
|
||||
state_config = index.data(_BeamlineStateListModel.ConfigRole) or {}
|
||||
title = state_config.get("title") or name
|
||||
editor.set_state_name(str(name), title=str(title))
|
||||
state_config = index.data(_BeamlineStateListModel.ConfigRole)
|
||||
editor.set_state_name(str(name))
|
||||
editor.idle_card_background = self._manager.idle_card_background
|
||||
editor.set_state_config(state_config)
|
||||
|
||||
@@ -229,7 +224,7 @@ class BeamlineStateManager(BECWidget, QWidget):
|
||||
)
|
||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
self._state_pills: dict[str, BeamlineStatePill] = {}
|
||||
self._state_configs: dict[str, dict[str, Any]] = {}
|
||||
self._state_configs: dict[str, messages.BeamlineStateConfig] = {}
|
||||
self._state_order: list[str] = []
|
||||
self._selected_statuses: set[str] | None = None
|
||||
self._selected_devices: set[str] | None = None
|
||||
@@ -414,14 +409,12 @@ class BeamlineStateManager(BECWidget, QWidget):
|
||||
) -> None:
|
||||
"""Update the displayed pills from ``AvailableBeamlineStatesMessage`` content."""
|
||||
expanded_names = {name for name, pill in self._state_pills.items() if pill.is_expanded()}
|
||||
states = content.get("states", [])
|
||||
state_configs = [self._state_config_to_dict(state) for state in states]
|
||||
state_configs = [state for state in state_configs if state.get("name")]
|
||||
state_configs: list[messages.BeamlineStateConfig] = content.get("states", [])
|
||||
if state_configs == list(self._state_configs.values()):
|
||||
self._apply_filters()
|
||||
return
|
||||
self._state_configs = {str(state["name"]): state for state in state_configs}
|
||||
self._state_order = [str(state["name"]) for state in state_configs]
|
||||
self._state_configs = {state.name: state for state in state_configs}
|
||||
self._state_order = [state.name for state in state_configs]
|
||||
self._model.set_states(state_configs)
|
||||
self._open_persistent_editors(expanded_names)
|
||||
self._apply_filters()
|
||||
@@ -472,7 +465,7 @@ class BeamlineStateManager(BECWidget, QWidget):
|
||||
):
|
||||
return False
|
||||
|
||||
device = self._state_device(self._state_configs.get(name, {}))
|
||||
device = self._state_device(self._state_configs.get(name))
|
||||
if self._selected_devices is not None and device not in self._selected_devices:
|
||||
return False
|
||||
|
||||
@@ -505,14 +498,13 @@ class BeamlineStateManager(BECWidget, QWidget):
|
||||
)
|
||||
return
|
||||
try:
|
||||
parameters = config.model_dump(exclude={"name"})
|
||||
state_client.update_parameters(**parameters)
|
||||
state_client.update_parameters(**config.model_dump(exclude={"name"}))
|
||||
except Exception as exc:
|
||||
QMessageBox.warning(self, "Cannot Update State", str(exc))
|
||||
return
|
||||
pill = self._state_pills.get(state_name)
|
||||
if pill is not None:
|
||||
pill.mark_current_settings_clean(config)
|
||||
pill.mark_current_settings_clean()
|
||||
|
||||
@SafeSlot(str)
|
||||
def _remove_state_requested(self, state_name: str) -> None:
|
||||
@@ -551,27 +543,10 @@ class BeamlineStateManager(BECWidget, QWidget):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _state_device(state: dict[str, Any]) -> str | None:
|
||||
parameters = state.get("parameters")
|
||||
if isinstance(parameters, dict):
|
||||
device = parameters.get("device")
|
||||
else:
|
||||
device = state.get("device")
|
||||
def _state_device(state: messages.BeamlineStateConfig | None) -> str | None:
|
||||
device = state.parameters.get("device") if state is not None else None
|
||||
return str(device) if device else None
|
||||
|
||||
@staticmethod
|
||||
def _state_config_to_dict(state: dict[str, Any] | BaseModel) -> dict[str, Any]:
|
||||
if isinstance(state, dict):
|
||||
return state
|
||||
state_dict = state.model_dump()
|
||||
state_type = getattr(state, "state_type", None)
|
||||
if state_type is not None:
|
||||
state_dict.setdefault("state_type", state_type)
|
||||
title = getattr(state, "title", None)
|
||||
if title is not None and not state_dict.get("title"):
|
||||
state_dict["title"] = title
|
||||
return state_dict
|
||||
|
||||
def cleanup(self) -> None:
|
||||
self.bec_dispatcher.disconnect_slot(
|
||||
self.update_available_states, MessageEndpoints.available_beamline_states()
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from bec_lib import bl_states
|
||||
from bec_lib import bl_states, messages
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_qthemes import material_icon
|
||||
from qtpy.QtCore import Qt, Signal
|
||||
@@ -76,7 +76,6 @@ class BeamlineStatePill(BECWidget, QWidget):
|
||||
self,
|
||||
parent: QWidget | None = None,
|
||||
state_name: str | None = None,
|
||||
title: str | None = None,
|
||||
client=None,
|
||||
config: ConnectionConfig | None = None,
|
||||
gui_id: str | None = None,
|
||||
@@ -89,8 +88,7 @@ class BeamlineStatePill(BECWidget, QWidget):
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
|
||||
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
self._state_name: str | None = None
|
||||
self._title: str | None = None
|
||||
self._state_config: dict[str, Any] = {}
|
||||
self._state_config: messages.BeamlineStateConfig | None = None
|
||||
self._status = "unknown"
|
||||
self._label = "No state information available."
|
||||
self._expanded = False
|
||||
@@ -100,9 +98,9 @@ class BeamlineStatePill(BECWidget, QWidget):
|
||||
self._settings_dirty_fields: set[str] = set()
|
||||
self._settings_form_stale = True
|
||||
|
||||
self._init_ui(state_name, title)
|
||||
self._init_ui(state_name)
|
||||
|
||||
def _init_ui(self, state_name: str | None = None, title: str | None = None) -> None:
|
||||
def _init_ui(self, state_name: str | None = None) -> None:
|
||||
self._shadow = QGraphicsDropShadowEffect(self)
|
||||
self._shadow.setBlurRadius(18)
|
||||
self._shadow.setOffset(0, 2)
|
||||
@@ -212,7 +210,7 @@ class BeamlineStatePill(BECWidget, QWidget):
|
||||
self.setLayout(layout)
|
||||
|
||||
self._settings.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
self.set_state_name(state_name, title=title)
|
||||
self.set_state_name(state_name)
|
||||
self._update_button.setEnabled(False)
|
||||
self._revert_button.setEnabled(False)
|
||||
|
||||
@@ -225,15 +223,14 @@ class BeamlineStatePill(BECWidget, QWidget):
|
||||
def state_name(self, state_name: str | None) -> None:
|
||||
self.set_state_name(state_name)
|
||||
|
||||
def set_state_name(self, state_name: str | None, title: str | None = None) -> None:
|
||||
def set_state_name(self, state_name: str | None) -> None:
|
||||
"""
|
||||
Set the BEC beamline state this pill displays.
|
||||
|
||||
Args:
|
||||
state_name: State name as published by ``AvailableBeamlineStatesMessage``.
|
||||
title: Optional human-readable title for the state.
|
||||
"""
|
||||
if state_name == self._state_name and title == self._title:
|
||||
if state_name == self._state_name:
|
||||
return
|
||||
|
||||
if self._state_name is not None:
|
||||
@@ -242,8 +239,7 @@ class BeamlineStatePill(BECWidget, QWidget):
|
||||
)
|
||||
|
||||
self._state_name = state_name
|
||||
self._title = title
|
||||
self._name_label.setText(title or state_name or "Beamline state")
|
||||
self._name_label.setText(state_name or "Beamline state")
|
||||
|
||||
if self._state_name is None:
|
||||
self._set_visual_state("unknown", "No beamline state selected.")
|
||||
@@ -255,13 +251,13 @@ class BeamlineStatePill(BECWidget, QWidget):
|
||||
self.update_state, MessageEndpoints.beamline_state(self._state_name)
|
||||
)
|
||||
|
||||
def set_state_config(self, state_config: dict[str, Any]) -> None:
|
||||
def set_state_config(self, state_config: messages.BeamlineStateConfig | None) -> None:
|
||||
"""Set the editable BEC state configuration displayed by the expanded panel."""
|
||||
self._state_config = state_config
|
||||
self._settings_form_stale = True
|
||||
if self._config_form is not None:
|
||||
self._populate_settings()
|
||||
self._mark_settings_clean_from_current()
|
||||
self.mark_current_settings_clean()
|
||||
|
||||
@SafeProperty(bool, default=False)
|
||||
def idle_card_background(self) -> bool:
|
||||
@@ -439,13 +435,13 @@ class BeamlineStatePill(BECWidget, QWidget):
|
||||
def _ensure_settings_form_current(self) -> PydanticWidgetForm:
|
||||
if self._settings_form_stale:
|
||||
self._populate_settings()
|
||||
self._mark_settings_clean_from_current()
|
||||
self.mark_current_settings_clean()
|
||||
return self._ensure_config_form()
|
||||
|
||||
def _populate_settings(self) -> None:
|
||||
self._populating_settings = True
|
||||
try:
|
||||
state_type = str(self._state_config.get("state_type") or "")
|
||||
state_type = self._state_config.state_type if self._state_config is not None else ""
|
||||
config_class = None
|
||||
for state_class in SUPPORTED_BEAMLINE_STATES:
|
||||
if state_type in {state_class.__name__, state_class.CONFIG_CLASS.state_type}:
|
||||
@@ -468,25 +464,8 @@ class BeamlineStatePill(BECWidget, QWidget):
|
||||
config = self._ensure_settings_form_current().model_instance()
|
||||
return config # type: ignore[return-value]
|
||||
|
||||
def mark_current_settings_clean(
|
||||
self, config: bl_states.BeamlineStateConfig | None = None
|
||||
) -> None:
|
||||
def mark_current_settings_clean(self) -> None:
|
||||
"""Mark the current editor values as saved."""
|
||||
if config is None:
|
||||
parameters = self._ensure_config_form().raw_editable_data()
|
||||
else:
|
||||
parameters = config.model_dump(exclude={"name"})
|
||||
if self._state_config:
|
||||
state_parameters = self._state_config.get("parameters")
|
||||
if isinstance(state_parameters, dict):
|
||||
state_parameters.update(parameters)
|
||||
else:
|
||||
self._state_config.update(parameters)
|
||||
if "title" in parameters:
|
||||
self._state_config["title"] = parameters["title"]
|
||||
self._mark_settings_clean_from_current()
|
||||
|
||||
def _mark_settings_clean_from_current(self) -> None:
|
||||
config_form = self._ensure_config_form()
|
||||
self._settings_baseline = config_form.raw_editable_data()
|
||||
config_form.mark_clean()
|
||||
@@ -561,15 +540,12 @@ class BeamlineStatePill(BECWidget, QWidget):
|
||||
self, config_class: type[bl_states.BeamlineStateConfig]
|
||||
) -> dict[str, Any]:
|
||||
data: dict[str, Any] = {}
|
||||
parameters = self._state_config.get("parameters")
|
||||
parameter_values = parameters if isinstance(parameters, dict) else {}
|
||||
parameters = self._state_config.parameters if self._state_config is not None else {}
|
||||
for name in config_class.model_fields:
|
||||
if name == "name":
|
||||
data[name] = self._state_name
|
||||
elif name in parameter_values:
|
||||
data[name] = parameter_values[name]
|
||||
elif name in self._state_config:
|
||||
data[name] = self._state_config[name]
|
||||
elif name in parameters:
|
||||
data[name] = parameters[name]
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import shiboken6
|
||||
from bec_lib import bl_states
|
||||
from bec_lib import bl_states, messages
|
||||
from qtpy.QtCore import QCoreApplication, QEvent, Qt
|
||||
from qtpy.QtWidgets import QMessageBox, QStyleOptionViewItem
|
||||
from qtpy.QtWidgets import QMessageBox
|
||||
|
||||
from bec_widgets.utils.toolbars.toolbar import ModularToolBar
|
||||
from bec_widgets.utils.widget_io import WidgetIO
|
||||
@@ -15,14 +15,30 @@ from .client_mocks import mocked_client
|
||||
from .conftest import create_widget
|
||||
|
||||
|
||||
def test_beamline_state_pill_updates_from_message(qtbot, mocked_client):
|
||||
pill = create_widget(
|
||||
qtbot, BeamlineStatePill, state_name="shutter_open", title="Shutter", client=mocked_client
|
||||
def _state(name: str, state_type: str, parameters: dict | None = None):
|
||||
return messages.BeamlineStateConfig(
|
||||
name=name, state_type=state_type, parameters=parameters or {}
|
||||
)
|
||||
|
||||
|
||||
def _limits_state(name: str = "limits", **overrides):
|
||||
parameters = {
|
||||
"device": "samx",
|
||||
"signal": "samx",
|
||||
"low_limit": 0.0,
|
||||
"high_limit": 10.0,
|
||||
"tolerance": 0.1,
|
||||
}
|
||||
parameters.update(overrides)
|
||||
return _state(name, "DeviceWithinLimitsState", parameters)
|
||||
|
||||
|
||||
def test_beamline_state_pill_updates_from_message(qtbot, mocked_client):
|
||||
pill = create_widget(qtbot, BeamlineStatePill, state_name="shutter_open", client=mocked_client)
|
||||
pill.update_state({"name": "shutter_open", "status": "valid", "label": "Shutter is open."}, {})
|
||||
|
||||
assert pill._state_name == "shutter_open"
|
||||
assert pill._name_label.text() == "Shutter"
|
||||
assert pill._name_label.text() == "shutter_open"
|
||||
assert pill._status_label.text() == "VALID"
|
||||
assert pill._detail_label.text() == "Shutter is open."
|
||||
assert not pill._icon_label.pixmap().isNull()
|
||||
@@ -30,9 +46,7 @@ def test_beamline_state_pill_updates_from_message(qtbot, mocked_client):
|
||||
|
||||
|
||||
def test_beamline_state_pill_ignores_other_states(qtbot, mocked_client):
|
||||
pill = create_widget(
|
||||
qtbot, BeamlineStatePill, state_name="shutter_open", title="Shutter", client=mocked_client
|
||||
)
|
||||
pill = create_widget(qtbot, BeamlineStatePill, state_name="shutter_open", client=mocked_client)
|
||||
pill.update_state(
|
||||
{"name": "other_state", "status": "invalid", "label": "Should be ignored."}, {}
|
||||
)
|
||||
@@ -42,25 +56,8 @@ def test_beamline_state_pill_ignores_other_states(qtbot, mocked_client):
|
||||
|
||||
|
||||
def test_beamline_state_pill_expands_and_emits_updated_limits(qtbot, mocked_client):
|
||||
limits_pill = create_widget(
|
||||
qtbot, BeamlineStatePill, state_name="limits", title="Limits", client=mocked_client
|
||||
)
|
||||
limits_pill.set_state_config(
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"device": "samx",
|
||||
"signal": "samx",
|
||||
"low_limit": 0.0,
|
||||
"high_limit": 10.0,
|
||||
"tolerance": 0.1,
|
||||
},
|
||||
}
|
||||
)
|
||||
limits_pill = create_widget(qtbot, BeamlineStatePill, state_name="limits", client=mocked_client)
|
||||
limits_pill.set_state_config(_limits_state())
|
||||
|
||||
assert limits_pill._settings.isHidden()
|
||||
assert limits_pill._config_form is None
|
||||
@@ -97,9 +94,7 @@ def test_beamline_state_pill_expands_and_emits_updated_limits(qtbot, mocked_clie
|
||||
def test_beamline_state_pill_first_expand_uses_config_class_without_rebuild(
|
||||
qtbot, mocked_client, monkeypatch
|
||||
):
|
||||
limits_pill = create_widget(
|
||||
qtbot, BeamlineStatePill, state_name="limits", title="Limits", client=mocked_client
|
||||
)
|
||||
limits_pill = create_widget(qtbot, BeamlineStatePill, state_name="limits", client=mocked_client)
|
||||
set_model_calls = []
|
||||
original_set_model = pill_module.PydanticWidgetForm.set_model
|
||||
|
||||
@@ -108,20 +103,7 @@ def test_beamline_state_pill_first_expand_uses_config_class_without_rebuild(
|
||||
return original_set_model(self, model, data=data)
|
||||
|
||||
monkeypatch.setattr(pill_module.PydanticWidgetForm, "set_model", set_model_spy)
|
||||
limits_pill.set_state_config(
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {
|
||||
"device": "samx",
|
||||
"signal": "samx",
|
||||
"low_limit": 0.0,
|
||||
"high_limit": 10.0,
|
||||
"tolerance": 0.1,
|
||||
},
|
||||
}
|
||||
)
|
||||
limits_pill.set_state_config(_limits_state())
|
||||
|
||||
limits_pill.set_expanded(True)
|
||||
assert limits_pill._config_form is not None
|
||||
@@ -129,23 +111,8 @@ def test_beamline_state_pill_first_expand_uses_config_class_without_rebuild(
|
||||
|
||||
|
||||
def test_beamline_state_pill_reverts_changed_settings(qtbot, mocked_client):
|
||||
limits_pill = create_widget(
|
||||
qtbot, BeamlineStatePill, state_name="limits", title="Limits", client=mocked_client
|
||||
)
|
||||
limits_pill.set_state_config(
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {
|
||||
"device": "samx",
|
||||
"signal": "samx",
|
||||
"low_limit": 0.0,
|
||||
"high_limit": 10.0,
|
||||
"tolerance": 0.1,
|
||||
},
|
||||
}
|
||||
)
|
||||
limits_pill = create_widget(qtbot, BeamlineStatePill, state_name="limits", client=mocked_client)
|
||||
limits_pill.set_state_config(_limits_state())
|
||||
|
||||
limits_pill.set_expanded(True)
|
||||
assert limits_pill._config_form is not None
|
||||
@@ -166,23 +133,8 @@ def test_beamline_state_pill_reverts_changed_settings(qtbot, mocked_client):
|
||||
|
||||
|
||||
def test_beamline_state_pill_does_not_override_themed_input_controls(qtbot, mocked_client):
|
||||
limits_pill = create_widget(
|
||||
qtbot, BeamlineStatePill, state_name="limits", title="Limits", client=mocked_client
|
||||
)
|
||||
limits_pill.set_state_config(
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {
|
||||
"device": "samx",
|
||||
"signal": "samx",
|
||||
"low_limit": 0.0,
|
||||
"high_limit": 10.0,
|
||||
"tolerance": 0.1,
|
||||
},
|
||||
}
|
||||
)
|
||||
limits_pill = create_widget(qtbot, BeamlineStatePill, state_name="limits", client=mocked_client)
|
||||
limits_pill.set_state_config(_limits_state())
|
||||
limits_pill.set_expanded(True)
|
||||
|
||||
stylesheet = limits_pill.styleSheet()
|
||||
@@ -197,18 +149,8 @@ def test_beamline_state_manager_adds_and_removes_pills(qtbot, mocked_client):
|
||||
beamline_state_manager.update_available_states(
|
||||
{
|
||||
"states": [
|
||||
{
|
||||
"name": "shutter_open",
|
||||
"title": "Shutter",
|
||||
"state_type": "ShutterState",
|
||||
"parameters": {},
|
||||
},
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {},
|
||||
},
|
||||
_state("shutter_open", "ShutterState"),
|
||||
_state("limits", "DeviceWithinLimitsState"),
|
||||
]
|
||||
},
|
||||
{},
|
||||
@@ -216,7 +158,7 @@ def test_beamline_state_manager_adds_and_removes_pills(qtbot, mocked_client):
|
||||
|
||||
assert sorted(beamline_state_manager._state_pills) == ["limits", "shutter_open"]
|
||||
assert beamline_state_manager._model.rowCount() == 2
|
||||
assert beamline_state_manager._state_pills["shutter_open"]._name_label.text() == "Shutter"
|
||||
assert beamline_state_manager._state_pills["shutter_open"]._name_label.text() == "shutter_open"
|
||||
assert not beamline_state_manager._empty_label.isVisible()
|
||||
|
||||
beamline_state_manager._state_pills["limits"].update_state(
|
||||
@@ -227,17 +169,7 @@ def test_beamline_state_manager_adds_and_removes_pills(qtbot, mocked_client):
|
||||
assert summary["shutter_open"]["status"] == "unknown"
|
||||
|
||||
beamline_state_manager.update_available_states(
|
||||
{
|
||||
"states": [
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {},
|
||||
}
|
||||
]
|
||||
},
|
||||
{},
|
||||
{"states": [_state("limits", "DeviceWithinLimitsState")]}, {}
|
||||
)
|
||||
|
||||
assert sorted(beamline_state_manager._state_pills) == ["limits"]
|
||||
@@ -246,22 +178,7 @@ def test_beamline_state_manager_adds_and_removes_pills(qtbot, mocked_client):
|
||||
|
||||
def test_beamline_state_manager_ignores_unchanged_available_states(qtbot, mocked_client):
|
||||
beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client)
|
||||
content = {
|
||||
"states": [
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {
|
||||
"device": "samx",
|
||||
"signal": "samx",
|
||||
"low_limit": 0.0,
|
||||
"high_limit": 10.0,
|
||||
"tolerance": 0.1,
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
content = {"states": [_limits_state()]}
|
||||
|
||||
beamline_state_manager.update_available_states(content, {})
|
||||
pill = beamline_state_manager._state_pills["limits"]
|
||||
@@ -274,24 +191,8 @@ def test_beamline_state_manager_ignores_unchanged_available_states(qtbot, mocked
|
||||
|
||||
def test_beamline_state_manager_adds_state_without_recreating_existing_pills(qtbot, mocked_client):
|
||||
beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client)
|
||||
limits_state = {
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {
|
||||
"device": "samx",
|
||||
"signal": "samx",
|
||||
"low_limit": 0.0,
|
||||
"high_limit": 10.0,
|
||||
"tolerance": 0.1,
|
||||
},
|
||||
}
|
||||
shutter_state = {
|
||||
"name": "shutter_open",
|
||||
"title": "Shutter",
|
||||
"state_type": "ShutterState",
|
||||
"parameters": {},
|
||||
}
|
||||
limits_state = _limits_state()
|
||||
shutter_state = _state("shutter_open", "ShutterState")
|
||||
|
||||
beamline_state_manager.update_available_states({"states": [limits_state]}, {})
|
||||
pill = beamline_state_manager._state_pills["limits"]
|
||||
@@ -309,17 +210,7 @@ def test_beamline_state_manager_adds_state_without_recreating_existing_pills(qtb
|
||||
def test_beamline_state_manager_header_click_expands_pill_once(qtbot, mocked_client):
|
||||
beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client)
|
||||
beamline_state_manager.update_available_states(
|
||||
{
|
||||
"states": [
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {"device": "samx"},
|
||||
}
|
||||
]
|
||||
},
|
||||
{},
|
||||
{"states": [_state("limits", "DeviceWithinLimitsState", {"device": "samx"})]}, {}
|
||||
)
|
||||
|
||||
pill = beamline_state_manager._state_pills["limits"]
|
||||
@@ -332,12 +223,7 @@ def test_beamline_state_manager_header_click_expands_pill_once(qtbot, mocked_cli
|
||||
|
||||
def test_beamline_state_manager_preserves_expanded_pill_on_refresh(qtbot, mocked_client):
|
||||
beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client)
|
||||
state = {
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {"device": "samx", "high_limit": 10.0},
|
||||
}
|
||||
state = _state("limits", "DeviceWithinLimitsState", {"device": "samx", "high_limit": 10.0})
|
||||
beamline_state_manager.update_available_states({"states": [state]}, {})
|
||||
|
||||
beamline_state_manager._state_pills["limits"].set_expanded(True)
|
||||
@@ -352,17 +238,7 @@ def test_beamline_state_manager_propagates_idle_card_background(qtbot, mocked_cl
|
||||
qtbot, BeamlineStateManager, client=mocked_client, idle_card_background=True
|
||||
)
|
||||
idle_card_manager.update_available_states(
|
||||
{
|
||||
"states": [
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {"device": "samx"},
|
||||
}
|
||||
]
|
||||
},
|
||||
{},
|
||||
{"states": [_state("limits", "DeviceWithinLimitsState", {"device": "samx"})]}, {}
|
||||
)
|
||||
|
||||
assert idle_card_manager._state_pills["limits"]._idle_card_background is True
|
||||
@@ -377,18 +253,8 @@ def test_beamline_state_manager_filters_status(qtbot, mocked_client):
|
||||
beamline_state_manager.update_available_states(
|
||||
{
|
||||
"states": [
|
||||
{
|
||||
"name": "shutter_open",
|
||||
"title": "Shutter",
|
||||
"state_type": "ShutterState",
|
||||
"parameters": {"device": "samy"},
|
||||
},
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {"device": "samx"},
|
||||
},
|
||||
_state("shutter_open", "ShutterState", {"device": "samy"}),
|
||||
_state("limits", "DeviceWithinLimitsState", {"device": "samx"}),
|
||||
]
|
||||
},
|
||||
{},
|
||||
@@ -433,17 +299,7 @@ def test_beamline_state_manager_filters_status(qtbot, mocked_client):
|
||||
def test_beamline_state_manager_status_filter_reacts_to_state_changes(qtbot, mocked_client):
|
||||
beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client)
|
||||
beamline_state_manager.update_available_states(
|
||||
{
|
||||
"states": [
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {"device": "samx"},
|
||||
}
|
||||
]
|
||||
},
|
||||
{},
|
||||
{"states": [_state("limits", "DeviceWithinLimitsState", {"device": "samx"})]}, {}
|
||||
)
|
||||
|
||||
beamline_state_manager._selected_statuses = {"valid"}
|
||||
@@ -468,18 +324,8 @@ def test_beamline_state_manager_filters_devices(qtbot, mocked_client, monkeypatc
|
||||
beamline_state_manager.update_available_states(
|
||||
{
|
||||
"states": [
|
||||
{
|
||||
"name": "samx_limits",
|
||||
"title": "samx",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {"device": "samx"},
|
||||
},
|
||||
{
|
||||
"name": "samy_limits",
|
||||
"title": "samy",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {"device": "samy"},
|
||||
},
|
||||
_state("samx_limits", "DeviceWithinLimitsState", {"device": "samx"}),
|
||||
_state("samy_limits", "DeviceWithinLimitsState", {"device": "samy"}),
|
||||
]
|
||||
},
|
||||
{},
|
||||
@@ -512,27 +358,28 @@ def test_beamline_state_manager_filters_devices(qtbot, mocked_client, monkeypatc
|
||||
assert captured["parent"] is beamline_state_manager
|
||||
|
||||
|
||||
def test_beamline_state_manager_backend_echo_repopulates_expanded_pill(qtbot, mocked_client):
|
||||
beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client)
|
||||
beamline_state_manager.update_available_states({"states": [_limits_state()]}, {})
|
||||
|
||||
pill = beamline_state_manager._state_pills["limits"]
|
||||
pill.set_expanded(True)
|
||||
high_limit = pill._config_form.input_widget("high_limit")
|
||||
high_limit.setValue(20.0)
|
||||
|
||||
assert pill._update_button.isEnabled()
|
||||
|
||||
beamline_state_manager.update_available_states({"states": [_limits_state(high_limit=20.0)]}, {})
|
||||
|
||||
assert pill.is_expanded()
|
||||
assert high_limit.value() == 20.0
|
||||
assert not pill._update_button.isEnabled()
|
||||
assert pill._config_form.dirty_fields() == set()
|
||||
|
||||
|
||||
def test_beamline_state_manager_updates_state_parameters(qtbot, mocked_client):
|
||||
beamline_state_manager = create_widget(qtbot, BeamlineStateManager, client=mocked_client)
|
||||
beamline_state_manager.update_available_states(
|
||||
{
|
||||
"states": [
|
||||
{
|
||||
"name": "limits",
|
||||
"title": "Limits",
|
||||
"state_type": "DeviceWithinLimitsState",
|
||||
"parameters": {
|
||||
"device": "samx",
|
||||
"signal": "samx",
|
||||
"low_limit": 0.0,
|
||||
"high_limit": 10.0,
|
||||
"tolerance": 0.1,
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{},
|
||||
)
|
||||
beamline_state_manager.update_available_states({"states": [_limits_state()]}, {})
|
||||
|
||||
class StateClient:
|
||||
def __init__(self):
|
||||
|
||||
Reference in New Issue
Block a user