mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-04-19 06:45:36 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45fbf4015d | ||
|
|
0d81bdd4dd | ||
|
|
bb4c30ad80 | ||
| 3fd09fceef | |||
| 8eb8225a7f | |||
| 491d04467c |
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,6 +1,30 @@
|
||||
# CHANGELOG
|
||||
|
||||
|
||||
## v2.30.3 (2025-07-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Cleanup subscriptions in device browser
|
||||
([`0d81bdd`](https://github.com/bec-project/bec_widgets/commit/0d81bdd4ddb4ec474a414b107cbc7fc865253934))
|
||||
|
||||
|
||||
## v2.30.2 (2025-07-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Factor out device name function and add test
|
||||
([`8eb8225`](https://github.com/bec-project/bec_widgets/commit/8eb8225a7f56014d6093aa142b3a5d071837982e))
|
||||
|
||||
- **rpc_base**: Rpc_call wrapper passes full_name for Devices indeed of name
|
||||
([`491d044`](https://github.com/bec-project/bec_widgets/commit/491d04467c8ce4e116d61e614895d1dcc6b4b201))
|
||||
|
||||
### Testing
|
||||
|
||||
- **test_plotting_framework_e2e**: Added test for waveform with passing device from dev container
|
||||
([`3fd09fc`](https://github.com/bec-project/bec_widgets/commit/3fd09fceef2ffa7e7c3eee20176304bafb00d0db))
|
||||
|
||||
|
||||
## v2.30.1 (2025-07-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -7,6 +7,7 @@ from functools import wraps
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from bec_lib.client import BECClient
|
||||
from bec_lib.device import DeviceBaseWithConfig
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.utils.import_utils import lazy_import, lazy_import_from
|
||||
|
||||
@@ -24,6 +25,20 @@ else:
|
||||
# pylint: disable=protected-access
|
||||
|
||||
|
||||
def _name_arg(arg):
|
||||
if isinstance(arg, DeviceBaseWithConfig):
|
||||
# if dev.<device> is passed to GUI, it passes full_name
|
||||
if hasattr(arg, "full_name"):
|
||||
return arg.full_name
|
||||
elif hasattr(arg, "name"):
|
||||
return arg.name
|
||||
return arg
|
||||
|
||||
|
||||
def _transform_args_kwargs(args, kwargs) -> tuple[tuple, dict]:
|
||||
return tuple(_name_arg(arg) for arg in args), {k: _name_arg(v) for k, v in kwargs.items()}
|
||||
|
||||
|
||||
def rpc_call(func):
|
||||
"""
|
||||
A decorator for calling a function on the server.
|
||||
@@ -47,15 +62,7 @@ def rpc_call(func):
|
||||
return None # func(*args, **kwargs)
|
||||
caller_frame = caller_frame.f_back
|
||||
|
||||
out = []
|
||||
for arg in args:
|
||||
if hasattr(arg, "name"):
|
||||
arg = arg.name
|
||||
out.append(arg)
|
||||
args = tuple(out)
|
||||
for key, val in kwargs.items():
|
||||
if hasattr(val, "name"):
|
||||
kwargs[key] = val.name
|
||||
args, kwargs = _transform_args_kwargs(args, kwargs)
|
||||
if not self._root._gui_is_alive():
|
||||
raise RuntimeError("GUI is not alive")
|
||||
return self._run_rpc(func.__name__, *args, **kwargs)
|
||||
|
||||
@@ -28,6 +28,10 @@ class EntryValidator:
|
||||
if not available_entries:
|
||||
available_entries = [name]
|
||||
|
||||
# edge case for if name is passed instead of full_name, should not happen
|
||||
if entry in signals_dict:
|
||||
entry = signals_dict[entry].get("obj_name", entry)
|
||||
|
||||
if entry is None or entry == "":
|
||||
entry = next(iter(device._hints), name) if hasattr(device, "_hints") else name
|
||||
if entry not in available_entries:
|
||||
|
||||
@@ -64,10 +64,10 @@ class DeviceBrowser(BECWidget, QWidget):
|
||||
self.proxy_device_update = SignalProxy(
|
||||
self.ui.filter_input.textChanged, rateLimit=500, slot=self.update_device_list
|
||||
)
|
||||
self.bec_dispatcher.client.callbacks.register(
|
||||
self._device_update_callback_id = self.bec_dispatcher.client.callbacks.register(
|
||||
EventType.DEVICE_UPDATE, self.on_device_update
|
||||
)
|
||||
self.bec_dispatcher.client.callbacks.register(
|
||||
self._scan_status_callback_id = self.bec_dispatcher.client.callbacks.register(
|
||||
EventType.SCAN_STATUS, self.scan_status_changed
|
||||
)
|
||||
self._default_config_dir = os.path.abspath(
|
||||
@@ -229,6 +229,11 @@ class DeviceBrowser(BECWidget, QWidget):
|
||||
if file_path:
|
||||
self._config_helper.save_current_session(file_path)
|
||||
|
||||
def cleanup(self):
|
||||
super().cleanup()
|
||||
self.bec_dispatcher.client.callbacks.remove(self._scan_status_callback_id)
|
||||
self.bec_dispatcher.client.callbacks.remove(self._device_update_callback_id)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "bec_widgets"
|
||||
version = "2.30.1"
|
||||
version = "2.30.3"
|
||||
description = "BEC Widgets"
|
||||
requires-python = ">=3.10"
|
||||
classifiers = [
|
||||
|
||||
@@ -254,3 +254,35 @@ def test_dap_rpc(qtbot, bec_client_lib, connected_client_gui_obj):
|
||||
res.wait()
|
||||
|
||||
qtbot.waitUntil(wait_for_fit, timeout=10000)
|
||||
|
||||
|
||||
def test_waveform_passing_device(qtbot, bec_client_lib, connected_client_gui_obj):
|
||||
gui = connected_client_gui_obj
|
||||
client = bec_client_lib
|
||||
dev = client.device_manager.devices
|
||||
scans = client.scans
|
||||
|
||||
dock = gui.bec
|
||||
wf = dock.new("wf_dock").new("Waveform")
|
||||
c1 = wf.plot(
|
||||
y_name=dev.samx, y_entry=dev.samx.setpoint
|
||||
) # using setpoint to not use readback signal
|
||||
|
||||
assert c1.object_name == "samx_samx_setpoint"
|
||||
|
||||
status = scans.line_scan(dev.samx, -5, 5, steps=5, exp_time=0.05, relative=False)
|
||||
status.wait()
|
||||
|
||||
# Wait for the scan to finish and the data to be available in history
|
||||
# Wait until scan_id is in history
|
||||
def _wait_for_scan_in_history():
|
||||
if len(client.history) == 0:
|
||||
return False
|
||||
# Once items appear in storage, the last one hast to be the one we just scanned
|
||||
return client.history[-1].metadata.bec["scan_id"] == status.scan.scan_id
|
||||
|
||||
qtbot.waitUntil(_wait_for_scan_in_history, timeout=10000)
|
||||
last_scan_data = client.history[-1]
|
||||
# check plotted data
|
||||
x_data, y_data = c1.get_data()
|
||||
assert np.array_equal(y_data, last_scan_data.devices.samx.samx_setpoint.read().get("value"))
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from bec_widgets.cli.rpc.rpc_base import DeletedWidgetError, RPCBase, RPCReference
|
||||
import pytest
|
||||
from bec_lib.device import DeviceBaseWithConfig, Signal
|
||||
|
||||
from bec_widgets.cli.rpc.rpc_base import (
|
||||
DeletedWidgetError,
|
||||
RPCBase,
|
||||
RPCReference,
|
||||
_transform_args_kwargs,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -26,3 +34,20 @@ def test_rpc_base(rpc_base):
|
||||
|
||||
with pytest.raises(DeletedWidgetError):
|
||||
ref._root # Object no longer referenced in registry
|
||||
|
||||
|
||||
def test_transform_args_kwargs():
|
||||
device_mock = MagicMock(spec=DeviceBaseWithConfig)
|
||||
device_mock.full_name = "full name"
|
||||
fallthrough_device_mock = MagicMock()
|
||||
fallthrough_device_mock.name = "short name"
|
||||
string_arg = "string_arg"
|
||||
signal_mock = MagicMock(spec=Signal)
|
||||
signal_mock.full_name = "full name"
|
||||
|
||||
args, kwargs = _transform_args_kwargs(
|
||||
(device_mock, fallthrough_device_mock, string_arg, signal_mock),
|
||||
{"a": device_mock, "b": fallthrough_device_mock, "c": string_arg, "d": signal_mock},
|
||||
)
|
||||
assert args == ("full name", "short name", "string_arg", "full name")
|
||||
assert kwargs == {"a": "full name", "b": "short name", "c": "string_arg", "d": "full name"}
|
||||
|
||||
Reference in New Issue
Block a user