bec-widgets 0.53.2__py3-none-any.whl → 0.54.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. CHANGELOG.md +24 -25
  2. PKG-INFO +1 -1
  3. bec_widgets/cli/client.py +13 -13
  4. bec_widgets/cli/client_utils.py +0 -4
  5. bec_widgets/cli/generate_cli.py +7 -5
  6. bec_widgets/cli/server.py +5 -7
  7. bec_widgets/examples/jupyter_console/jupyter_console_window.py +7 -3
  8. bec_widgets/examples/motor_movement/motor_control_compilations.py +17 -16
  9. bec_widgets/widgets/__init__.py +0 -10
  10. bec_widgets/widgets/figure/figure.py +40 -23
  11. bec_widgets/widgets/figure/plots/__init__.py +0 -0
  12. bec_widgets/widgets/figure/plots/image/__init__.py +0 -0
  13. bec_widgets/widgets/{plots → figure/plots/image}/image.py +6 -416
  14. bec_widgets/widgets/figure/plots/image/image_item.py +277 -0
  15. bec_widgets/widgets/figure/plots/image/image_processor.py +152 -0
  16. bec_widgets/widgets/figure/plots/motor_map/__init__.py +0 -0
  17. bec_widgets/widgets/{plots → figure/plots/motor_map}/motor_map.py +2 -2
  18. bec_widgets/widgets/figure/plots/waveform/__init__.py +0 -0
  19. bec_widgets/widgets/{plots → figure/plots/waveform}/waveform.py +9 -222
  20. bec_widgets/widgets/figure/plots/waveform/waveform_curve.py +227 -0
  21. bec_widgets/widgets/motor_control/__init__.py +0 -7
  22. bec_widgets/widgets/motor_control/motor_control.py +2 -948
  23. bec_widgets/widgets/motor_control/motor_table/__init__.py +0 -0
  24. bec_widgets/widgets/motor_control/motor_table/motor_table.py +483 -0
  25. bec_widgets/widgets/motor_control/movement_absolute/__init__.py +0 -0
  26. bec_widgets/widgets/motor_control/movement_absolute/movement_absolute.py +157 -0
  27. bec_widgets/widgets/motor_control/movement_relative/__init__.py +0 -0
  28. bec_widgets/widgets/motor_control/movement_relative/movement_relative.py +227 -0
  29. bec_widgets/widgets/motor_control/selection/__init__.py +0 -0
  30. bec_widgets/widgets/motor_control/selection/selection.py +110 -0
  31. {bec_widgets-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/METADATA +1 -1
  32. {bec_widgets-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/RECORD +51 -52
  33. docs/requirements.txt +1 -0
  34. pyproject.toml +1 -1
  35. tests/end-2-end/test_bec_dock_rpc_e2e.py +1 -1
  36. tests/end-2-end/test_bec_figure_rpc_e2e.py +4 -4
  37. tests/end-2-end/test_rpc_register_e2e.py +1 -1
  38. tests/unit_tests/test_bec_dock.py +1 -1
  39. tests/unit_tests/test_bec_figure.py +6 -4
  40. tests/unit_tests/test_bec_motor_map.py +2 -3
  41. tests/unit_tests/test_motor_control.py +6 -5
  42. tests/unit_tests/test_waveform1d.py +13 -1
  43. bec_widgets/validation/__init__.py +0 -2
  44. bec_widgets/validation/monitor_config_validator.py +0 -258
  45. bec_widgets/widgets/monitor/__init__.py +0 -1
  46. bec_widgets/widgets/monitor/config_dialog.py +0 -574
  47. bec_widgets/widgets/monitor/config_dialog.ui +0 -210
  48. bec_widgets/widgets/monitor/example_configs/config_device.yaml +0 -60
  49. bec_widgets/widgets/monitor/example_configs/config_scans.yaml +0 -92
  50. bec_widgets/widgets/monitor/monitor.py +0 -845
  51. bec_widgets/widgets/monitor/tab_template.ui +0 -180
  52. bec_widgets/widgets/motor_map/__init__.py +0 -1
  53. bec_widgets/widgets/motor_map/motor_map.py +0 -594
  54. bec_widgets/widgets/plots/__init__.py +0 -4
  55. tests/unit_tests/test_bec_monitor.py +0 -220
  56. tests/unit_tests/test_config_dialog.py +0 -178
  57. tests/unit_tests/test_motor_map.py +0 -171
  58. tests/unit_tests/test_validator_errors.py +0 -110
  59. /bec_widgets/{cli → assets}/bec_widgets_icon.png +0 -0
  60. /bec_widgets/{examples/jupyter_console → assets}/terminal_icon.png +0 -0
  61. /bec_widgets/widgets/{plots → figure/plots}/plot_base.py +0 -0
  62. /bec_widgets/widgets/motor_control/{motor_control_table.ui → motor_table/motor_table.ui} +0 -0
  63. /bec_widgets/widgets/motor_control/{motor_control_absolute.ui → movement_absolute/movement_absolute.ui} +0 -0
  64. /bec_widgets/widgets/motor_control/{motor_control_relative.ui → movement_relative/movement_relative.ui} +0 -0
  65. /bec_widgets/widgets/motor_control/{motor_control_selection.ui → selection/selection.ui} +0 -0
  66. {bec_widgets-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/WHEEL +0 -0
  67. {bec_widgets-0.53.2.dist-info → bec_widgets-0.54.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,220 +0,0 @@
1
- # pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
2
- import os
3
- from unittest.mock import MagicMock
4
-
5
- import pytest
6
- import yaml
7
-
8
- from bec_widgets.widgets import BECMonitor
9
-
10
- from .client_mocks import mocked_client
11
-
12
-
13
- def load_test_config(config_name):
14
- """Helper function to load config from yaml file."""
15
- config_path = os.path.join(os.path.dirname(__file__), "test_configs", f"{config_name}.yaml")
16
- with open(config_path, "r") as f:
17
- config = yaml.safe_load(f)
18
- return config
19
-
20
-
21
- @pytest.fixture(scope="function")
22
- def monitor(bec_dispatcher, qtbot, mocked_client):
23
- # client = MagicMock()
24
- widget = BECMonitor(client=mocked_client)
25
- qtbot.addWidget(widget)
26
- qtbot.waitExposed(widget)
27
- yield widget
28
-
29
-
30
- @pytest.mark.parametrize(
31
- "config_name, scan_type, number_of_plots",
32
- [
33
- ("config_device", False, 2),
34
- ("config_device_no_entry", False, 2),
35
- # ("config_scan", True, 4),
36
- ],
37
- )
38
- def test_initialization_with_device_config(monitor, config_name, scan_type, number_of_plots):
39
- config = load_test_config(config_name)
40
- monitor.on_config_update(config)
41
- assert isinstance(monitor, BECMonitor)
42
- assert monitor.client is not None
43
- assert len(monitor.plot_data) == number_of_plots
44
- assert monitor.scan_types == scan_type
45
-
46
-
47
- @pytest.mark.parametrize(
48
- "config_initial,config_update",
49
- [("config_device", "config_scan"), ("config_scan", "config_device")],
50
- )
51
- def test_on_config_update(monitor, config_initial, config_update):
52
- config_initial = load_test_config(config_initial)
53
- config_update = load_test_config(config_update)
54
- # validated config has to be compared
55
- config_initial_validated = monitor.validator.validate_monitor_config(
56
- config_initial
57
- ).model_dump()
58
- config_update_validated = monitor.validator.validate_monitor_config(config_update).model_dump()
59
- monitor.on_config_update(config_initial)
60
- assert monitor.config == config_initial_validated
61
- monitor.on_config_update(config_update)
62
- assert monitor.config == config_update_validated
63
-
64
-
65
- @pytest.mark.parametrize(
66
- "config_name, expected_num_columns, expected_plot_names, expected_coordinates",
67
- [
68
- ("config_device", 1, ["BPM4i plots vs samx", "Gauss plots vs samx"], [(0, 0), (1, 0)]),
69
- (
70
- "config_scan",
71
- 3,
72
- ["Grid plot 1", "Grid plot 2", "Grid plot 3", "Grid plot 4"],
73
- [(0, 0), (0, 1), (0, 2), (1, 0)],
74
- ),
75
- ],
76
- )
77
- def test_render_initial_plots(
78
- monitor, config_name, expected_num_columns, expected_plot_names, expected_coordinates
79
- ):
80
- config = load_test_config(config_name)
81
- monitor.on_config_update(config)
82
-
83
- # Validate number of columns
84
- assert monitor.plot_settings["num_columns"] == expected_num_columns
85
-
86
- # Validate the plots are created correctly
87
- for expected_name in expected_plot_names:
88
- assert expected_name in monitor.plots.keys()
89
-
90
- # Validate the grid_coordinates
91
- assert monitor.grid_coordinates == expected_coordinates
92
-
93
-
94
- def mock_getitem(dev_name):
95
- """Helper function to mock the __getitem__ method of the 'dev'."""
96
- mock_instance = MagicMock()
97
- if dev_name == "samx":
98
- mock_instance._hints = "samx"
99
- elif dev_name == "bpm4i":
100
- mock_instance._hints = "bpm4i"
101
- elif dev_name == "gauss_bpm":
102
- mock_instance._hints = "gauss_bpm"
103
-
104
- return mock_instance
105
-
106
-
107
- def mock_get_scan_storage(scan_id, data):
108
- """Helper function to mock the __getitem__ method of the 'dev'."""
109
- mock_instance = MagicMock()
110
- mock_instance.get_scan_storage.return_value = data
111
- return mock_instance
112
-
113
-
114
- # mocked messages and metadata
115
- msg_1 = {
116
- "data": {
117
- "samx": {"samx": {"value": 10}},
118
- "bpm4i": {"bpm4i": {"value": 5}},
119
- "gauss_bpm": {"gauss_bpm": {"value": 6}},
120
- "gauss_adc1": {"gauss_adc1": {"value": 8}},
121
- "gauss_adc2": {"gauss_adc2": {"value": 9}},
122
- },
123
- "scan_id": 1,
124
- }
125
- metadata_grid = {"scan_name": "grid_scan"}
126
- metadata_line = {"scan_name": "line_scan"}
127
-
128
-
129
- @pytest.mark.parametrize(
130
- "config_name, msg, metadata, expected_data",
131
- [
132
- # case: msg does not have 'scan_id'
133
- (
134
- "config_device",
135
- {"data": {}},
136
- {},
137
- {
138
- "scan_segment": {
139
- "bpm4i": {"bpm4i": []},
140
- "gauss_adc1": {"gauss_adc1": []},
141
- "gauss_adc2": {"gauss_adc2": []},
142
- "samx": {"samx": []},
143
- }
144
- },
145
- ),
146
- # case: scan_types is false, msg contains all valid fields, and entry is present in config
147
- (
148
- "config_device",
149
- msg_1,
150
- {},
151
- {
152
- "scan_segment": {
153
- "bpm4i": {"bpm4i": [5]},
154
- "gauss_adc1": {"gauss_adc1": [8]},
155
- "gauss_adc2": {"gauss_adc2": [9]},
156
- "samx": {"samx": [10]},
157
- }
158
- },
159
- ),
160
- # case: scan_types is false, msg contains all valid fields and entry is missing in config, should use hints
161
- (
162
- "config_device_no_entry",
163
- msg_1,
164
- {},
165
- {
166
- "scan_segment": {
167
- "bpm4i": {"bpm4i": [5]},
168
- "gauss_bpm": {"gauss_bpm": [6]},
169
- "samx": {"samx": [10]},
170
- }
171
- },
172
- ),
173
- # case: scan_types is true, msg contains all valid fields, metadata contains scan "line_scan:"
174
- (
175
- "config_scan",
176
- msg_1,
177
- metadata_line,
178
- {
179
- "scan_segment": {
180
- "bpm4i": {"bpm4i": [5]},
181
- "gauss_adc1": {"gauss_adc1": [8]},
182
- "gauss_adc2": {"gauss_adc2": [9]},
183
- "gauss_bpm": {"gauss_bpm": [6]},
184
- "samx": {"samx": [10]},
185
- }
186
- },
187
- ),
188
- (
189
- "config_scan",
190
- msg_1,
191
- metadata_grid,
192
- {
193
- "scan_segment": {
194
- "bpm4i": {"bpm4i": [5]},
195
- "gauss_adc1": {"gauss_adc1": [8]},
196
- "gauss_adc2": {"gauss_adc2": [9]},
197
- "gauss_bpm": {"gauss_bpm": [6]},
198
- "samx": {"samx": [10]},
199
- }
200
- },
201
- ),
202
- ],
203
- )
204
- def test_on_scan_segment(monitor, config_name, msg, metadata, expected_data):
205
- config = load_test_config(config_name)
206
- monitor.on_config_update(config)
207
-
208
- # Mock scan_storage.find_scan_by_ID
209
- mock_scan_data = MagicMock()
210
- mock_scan_data.data = {
211
- device_name: {
212
- entry: MagicMock(val=[msg["data"][device_name][entry]["value"]])
213
- for entry in msg["data"][device_name]
214
- }
215
- for device_name in msg["data"]
216
- }
217
- monitor.queue.scan_storage.find_scan_by_ID.return_value = mock_scan_data
218
-
219
- monitor.on_scan_segment(msg, metadata)
220
- assert monitor.database == expected_data
@@ -1,178 +0,0 @@
1
- # pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
2
- import os
3
- from unittest.mock import MagicMock
4
-
5
- import pytest
6
- import yaml
7
- from qtpy.QtWidgets import QTableWidgetItem, QTabWidget
8
-
9
- from bec_widgets.widgets.monitor.config_dialog import ConfigDialog
10
-
11
- from .client_mocks import mocked_client
12
-
13
-
14
- def load_test_config(config_name):
15
- """Helper function to load config from yaml file."""
16
- config_path = os.path.join(os.path.dirname(__file__), "test_configs", f"{config_name}.yaml")
17
- with open(config_path, "r") as f:
18
- config = yaml.safe_load(f)
19
- return config
20
-
21
-
22
- @pytest.fixture(scope="function")
23
- def config_dialog(qtbot, mocked_client):
24
- client = mocked_client
25
- widget = ConfigDialog(client=client)
26
- qtbot.addWidget(widget)
27
- qtbot.waitExposed(widget)
28
- yield widget
29
-
30
-
31
- @pytest.mark.parametrize("config_name", ["config_device", "config_scan"])
32
- def test_load_config(config_dialog, config_name):
33
- config = load_test_config(config_name)
34
- config_dialog.load_config(config)
35
-
36
- assert (
37
- config_dialog.comboBox_appearance.currentText()
38
- == config["plot_settings"]["background_color"]
39
- )
40
- assert config_dialog.spinBox_n_column.value() == config["plot_settings"]["num_columns"]
41
- assert config_dialog.comboBox_colormap.currentText() == config["plot_settings"]["colormap"]
42
-
43
-
44
- @pytest.mark.parametrize(
45
- "config_name, scan_mode",
46
- [("config_device", False), ("config_scan", True), ("config_device_no_entry", False)],
47
- )
48
- def test_initialization(config_dialog, config_name, scan_mode):
49
- config = load_test_config(config_name)
50
- config_dialog.load_config(config)
51
-
52
- assert isinstance(config_dialog, ConfigDialog)
53
- assert (
54
- config_dialog.comboBox_appearance.currentText()
55
- == config["plot_settings"]["background_color"]
56
- )
57
- assert config_dialog.spinBox_n_column.value() == config["plot_settings"]["num_columns"]
58
- assert (config_dialog.comboBox_scanTypes.currentText() == "Enabled") == scan_mode
59
- assert (
60
- config_dialog.tabWidget_scan_types.count() > 0
61
- ) # Ensures there's at least one tab created
62
-
63
- # If there's a need to check the contents of the first tab (there has to be always at least one tab)
64
- first_tab = config_dialog.tabWidget_scan_types.widget(0)
65
- if scan_mode:
66
- assert (
67
- first_tab.findChild(QTabWidget, "tabWidget_plots") is not None
68
- ) # Ensures plot tab widget exists in scan mode
69
- else:
70
- assert (
71
- first_tab.findChild(QTabWidget) is not None
72
- ) # Ensures plot tab widget exists in default mode
73
-
74
-
75
- def test_edit_and_apply_config(config_dialog):
76
- config_device = load_test_config("config_device")
77
- config_dialog.load_config(config_device)
78
-
79
- config_dialog.comboBox_appearance.setCurrentText("white")
80
- config_dialog.spinBox_n_column.setValue(2)
81
- config_dialog.comboBox_colormap.setCurrentText("viridis")
82
-
83
- applied_config = config_dialog.apply_config()
84
-
85
- assert applied_config["plot_settings"]["background_color"] == "white"
86
- assert applied_config["plot_settings"]["num_columns"] == 2
87
- assert applied_config["plot_settings"]["colormap"] == "viridis"
88
-
89
-
90
- def test_edit_and_apply_config_scan_mode(config_dialog):
91
- config_scan = load_test_config("config_scan")
92
- config_dialog.load_config(config_scan)
93
-
94
- config_dialog.comboBox_appearance.setCurrentText("white")
95
- config_dialog.spinBox_n_column.setValue(2)
96
- config_dialog.comboBox_colormap.setCurrentText("viridis")
97
- config_dialog.comboBox_scanTypes.setCurrentText("Enabled")
98
-
99
- applied_config = config_dialog.apply_config()
100
-
101
- assert applied_config["plot_settings"]["background_color"] == "white"
102
- assert applied_config["plot_settings"]["num_columns"] == 2
103
- assert applied_config["plot_settings"]["colormap"] == "viridis"
104
- assert applied_config["plot_settings"]["scan_types"] is True
105
-
106
-
107
- def test_add_new_scan(config_dialog):
108
- # Ensure the tab count is initially 1 (from the default config)
109
- assert config_dialog.tabWidget_scan_types.count() == 1
110
-
111
- # Add a new scan tab
112
- config_dialog.add_new_scan_tab(config_dialog.tabWidget_scan_types, "Test Scan Tab")
113
-
114
- # Ensure the tab count is now 2
115
- assert config_dialog.tabWidget_scan_types.count() == 2
116
-
117
- # Ensure the new tab has the correct name
118
- assert config_dialog.tabWidget_scan_types.tabText(1) == "Test Scan Tab"
119
-
120
-
121
- def test_add_new_plot_and_modify(config_dialog):
122
- # Ensure the tab count is initially 1 and it is called "Default"
123
- assert config_dialog.tabWidget_scan_types.count() == 1
124
- assert config_dialog.tabWidget_scan_types.tabText(0) == "Default"
125
-
126
- # Get the first tab (which should be a scan tab)
127
- scan_tab = config_dialog.tabWidget_scan_types.widget(0)
128
-
129
- # Ensure the plot tab count is initially 1 and it is called "Plot 1"
130
- tabWidget_plots = scan_tab.findChild(QTabWidget)
131
- assert tabWidget_plots.count() == 1
132
- assert tabWidget_plots.tabText(0) == "Plot 1"
133
-
134
- # Add a new plot tab
135
- config_dialog.add_new_plot_tab(scan_tab)
136
-
137
- # Ensure the plot tab count is now 2
138
- assert tabWidget_plots.count() == 2
139
-
140
- # Ensure the new tab has the correct name
141
- assert tabWidget_plots.tabText(1) == "Plot 2"
142
-
143
- # Access the new plot tab
144
- new_plot_tab = tabWidget_plots.widget(1)
145
-
146
- # Modify the line edits within the new plot tab
147
- new_plot_tab.ui.lineEdit_plot_title.setText("Modified Plot Title")
148
- new_plot_tab.ui.lineEdit_x_label.setText("Modified X Label")
149
- new_plot_tab.ui.lineEdit_y_label.setText("Modified Y Label")
150
- new_plot_tab.ui.lineEdit_x_name.setText("Modified X Name")
151
- new_plot_tab.ui.lineEdit_x_entry.setText("Modified X Entry")
152
-
153
- # Modify the table for signals
154
- config_dialog.add_new_signal(new_plot_tab.ui.tableWidget_y_signals)
155
-
156
- table = new_plot_tab.ui.tableWidget_y_signals
157
- assert table.rowCount() == 1 # Ensure the new row is added
158
-
159
- row_position = table.rowCount() - 1
160
-
161
- # Modify the first row
162
- table.setItem(row_position, 0, QTableWidgetItem("New Signal Name"))
163
- table.setItem(row_position, 1, QTableWidgetItem("New Signal Entry"))
164
-
165
- # Apply the configuration
166
- config = config_dialog.apply_config()
167
-
168
- # Check if the modifications are reflected in the configuration
169
- modified_plot_config = config["plot_data"][1] # Access the second plot in the plot_data list
170
- sources = modified_plot_config["sources"][0] # Access the first source in the sources list
171
-
172
- assert modified_plot_config["plot_name"] == "Modified Plot Title"
173
- assert modified_plot_config["x_label"] == "Modified X Label"
174
- assert modified_plot_config["y_label"] == "Modified Y Label"
175
- assert sources["signals"]["x"][0]["name"] == "Modified X Name"
176
- assert sources["signals"]["x"][0]["entry"] == "Modified X Entry"
177
- assert sources["signals"]["y"][0]["name"] == "New Signal Name"
178
- assert sources["signals"]["y"][0]["entry"] == "New Signal Entry"
@@ -1,171 +0,0 @@
1
- # pylint: disable = no-name-in-module,missing-module-docstring, missing-function-docstring
2
- from unittest.mock import MagicMock
3
-
4
- import pytest
5
-
6
- from bec_widgets.widgets import MotorMap
7
-
8
- from .client_mocks import mocked_client
9
-
10
- CONFIG_DEFAULT = {
11
- "plot_settings": {
12
- "colormap": "Greys",
13
- "scatter_size": 5,
14
- "max_points": 1000,
15
- "num_dim_points": 100,
16
- "precision": 2,
17
- "num_columns": 1,
18
- "background_value": 25,
19
- },
20
- "motors": [
21
- {
22
- "plot_name": "Motor Map",
23
- "x_label": "Motor X",
24
- "y_label": "Motor Y",
25
- "signals": {
26
- "x": [{"name": "samx", "entry": "samx"}],
27
- "y": [{"name": "samy", "entry": "samy"}],
28
- },
29
- },
30
- {
31
- "plot_name": "Motor Map 2 ",
32
- "x_label": "Motor X",
33
- "y_label": "Motor Y",
34
- "signals": {
35
- "x": [{"name": "aptrx", "entry": "aptrx"}],
36
- "y": [{"name": "aptry", "entry": "aptry"}],
37
- },
38
- },
39
- ],
40
- }
41
-
42
- CONFIG_ONE_DEVICE = {
43
- "plot_settings": {
44
- "colormap": "Greys",
45
- "scatter_size": 5,
46
- "max_points": 1000,
47
- "num_dim_points": 100,
48
- "precision": 2,
49
- "num_columns": 1,
50
- "background_value": 25,
51
- },
52
- "motors": [
53
- {
54
- "plot_name": "Motor Map",
55
- "x_label": "Motor X",
56
- "y_label": "Motor Y",
57
- "signals": {
58
- "x": [{"name": "samx", "entry": "samx"}],
59
- "y": [{"name": "samy", "entry": "samy"}],
60
- },
61
- }
62
- ],
63
- }
64
-
65
-
66
- @pytest.fixture(scope="function")
67
- def motor_map(qtbot, mocked_client):
68
- widget = MotorMap(client=mocked_client)
69
- qtbot.addWidget(widget)
70
- qtbot.waitExposed(widget)
71
- yield widget
72
-
73
-
74
- def test_motor_limits_initialization(motor_map):
75
- # Example test to check if motor limits are correctly initialized
76
- expected_limits = {"samx": [-10, 10], "samy": [-5, 5]}
77
- for motor_name, expected_limit in expected_limits.items():
78
- actual_limit = motor_map._get_motor_limit(motor_name)
79
- assert actual_limit == expected_limit
80
-
81
-
82
- def test_motor_initial_position(motor_map):
83
- motor_map.precision = 2
84
-
85
- motor_map_dev = motor_map.client.device_manager.devices
86
-
87
- # Example test to check if motor initial positions are correctly initialized
88
- expected_positions = {
89
- ("samx", "samx"): motor_map_dev["samx"].read()["samx"]["value"],
90
- ("samy", "samy"): motor_map_dev["samy"].read()["samy"]["value"],
91
- ("aptrx", "aptrx"): motor_map_dev["aptrx"].read()["aptrx"]["value"],
92
- ("aptry", "aptry"): motor_map_dev["aptry"].read()["aptry"]["value"],
93
- }
94
- for (motor_name, entry), expected_position in expected_positions.items():
95
- actual_position = motor_map._get_motor_init_position(motor_name, entry)
96
- assert actual_position == expected_position
97
-
98
-
99
- @pytest.mark.parametrize("config, number_of_plots", [(CONFIG_DEFAULT, 2), (CONFIG_ONE_DEVICE, 1)])
100
- def test_initialization(motor_map, config, number_of_plots):
101
- config_load = config
102
- motor_map.on_config_update(config_load)
103
- assert isinstance(motor_map, MotorMap)
104
- assert motor_map.client is not None
105
- assert motor_map.config == config_load
106
- assert len(motor_map.plot_data) == number_of_plots
107
-
108
-
109
- def test_motor_movement_updates_position_and_database(motor_map):
110
- motor_map.on_config_update(CONFIG_DEFAULT)
111
-
112
- # Initial positions
113
- initial_position_samx = 2.0
114
- initial_position_samy = 3.0
115
-
116
- # Set initial positions in the mocked database
117
- motor_map.database["samx"]["samx"] = [initial_position_samx]
118
- motor_map.database["samy"]["samy"] = [initial_position_samy]
119
-
120
- # Simulate motor movement for 'samx' only
121
- new_position_samx = 4.0
122
- motor_map.on_device_readback({"signals": {"samx": {"value": new_position_samx}}})
123
-
124
- # Verify database update for 'samx'
125
- assert motor_map.database["samx"]["samx"] == [initial_position_samx, new_position_samx]
126
-
127
- # Verify 'samy' retains its last known position
128
- assert motor_map.database["samy"]["samy"] == [initial_position_samy, initial_position_samy]
129
-
130
-
131
- def test_scatter_plot_rendering(motor_map):
132
- motor_map.on_config_update(CONFIG_DEFAULT)
133
- # Set initial positions
134
- initial_position_samx = 2.0
135
- initial_position_samy = 3.0
136
- motor_map.database["samx"]["samx"] = [initial_position_samx]
137
- motor_map.database["samy"]["samy"] = [initial_position_samy]
138
-
139
- # Simulate motor movement for 'samx' only
140
- new_position_samx = 4.0
141
- motor_map.on_device_readback({"signals": {"samx": {"value": new_position_samx}}})
142
- motor_map._update_plots()
143
-
144
- # Get the scatter plot item
145
- plot_name = "Motor Map" # Update as per your actual plot name
146
- scatter_plot_item = motor_map.curves_data[plot_name]["pos"]
147
-
148
- # Check the scatter plot item properties
149
- assert len(scatter_plot_item.data) > 0, "Scatter plot data is empty"
150
- x_data = scatter_plot_item.data["x"]
151
- y_data = scatter_plot_item.data["y"]
152
- assert x_data[-1] == new_position_samx, "Scatter plot X data not updated correctly"
153
- assert (
154
- y_data[-1] == initial_position_samy
155
- ), "Scatter plot Y data should retain last known position"
156
-
157
-
158
- def test_plot_visualization_consistency(motor_map):
159
- motor_map.on_config_update(CONFIG_DEFAULT)
160
- # Simulate updating the plot with new data
161
- motor_map.on_device_readback({"signals": {"samx": {"value": 5}}})
162
- motor_map.on_device_readback({"signals": {"samy": {"value": 9}}})
163
- motor_map._update_plots()
164
-
165
- plot_name = "Motor Map"
166
- scatter_plot_item = motor_map.curves_data[plot_name]["pos"]
167
-
168
- # Check if the scatter plot reflects the new data correctly
169
- assert (
170
- scatter_plot_item.data["x"][-1] == 5 and scatter_plot_item.data["y"][-1] == 9
171
- ), "Plot not updated correctly with new data"
@@ -1,110 +0,0 @@
1
- # pylint: disable = no-name-in-module,missing-class-docstring, missing-module-docstring
2
- import pytest
3
- from pydantic import ValidationError
4
-
5
- from bec_widgets.validation.monitor_config_validator import (
6
- AxisSignal,
7
- MonitorConfigValidator,
8
- PlotConfig,
9
- Signal,
10
- )
11
-
12
- from .test_bec_monitor import mocked_client
13
-
14
-
15
- @pytest.fixture(scope="function")
16
- def setup_devices(mocked_client):
17
- MonitorConfigValidator.devices = mocked_client.device_manager.devices
18
-
19
-
20
- def test_signal_validation_name_missing(setup_devices):
21
- with pytest.raises(ValidationError) as excinfo:
22
- Signal(name=None)
23
- errors = excinfo.value.errors()
24
- assert len(errors) == 1
25
- assert errors[0]["type"] == "no_device_name"
26
- assert "Device name must be provided" in str(excinfo.value)
27
-
28
-
29
- def test_signal_validation_name_not_in_bec(setup_devices):
30
- with pytest.raises(ValidationError) as excinfo:
31
- Signal(name="non_existent_device")
32
- errors = excinfo.value.errors()
33
- assert len(errors) == 1
34
- assert errors[0]["type"] == "no_device_bec"
35
- assert 'Device "non_existent_device" not found in current BEC session' in str(excinfo.value)
36
-
37
-
38
- def test_signal_validation_entry_not_in_device(setup_devices):
39
- with pytest.raises(ValidationError) as excinfo:
40
- Signal(name="samx", entry="non_existent_entry")
41
-
42
- errors = excinfo.value.errors()
43
- assert len(errors) == 1
44
- assert errors[0]["type"] == "no_entry_for_device"
45
- assert 'Entry "non_existent_entry" not found in device "samx" signals' in errors[0]["msg"]
46
-
47
-
48
- def test_signal_validation_success(setup_devices):
49
- signal = Signal(name="samx")
50
- assert signal.name == "samx"
51
-
52
-
53
- def test_plot_config_x_axis_signal_validation(setup_devices):
54
- # Setup a valid signal
55
- valid_signal = Signal(name="samx")
56
-
57
- with pytest.raises(ValidationError) as excinfo:
58
- AxisSignal(x=[valid_signal, valid_signal], y=[valid_signal, valid_signal])
59
-
60
- errors = excinfo.value.errors()
61
- assert len(errors) == 1
62
- assert errors[0]["type"] == "x_axis_multiple_signals"
63
- assert "There must be exactly one signal for x axis" in errors[0]["msg"]
64
-
65
-
66
- def test_plot_config_unsupported_source_type(setup_devices):
67
- with pytest.raises(ValidationError) as excinfo:
68
- PlotConfig(sources=[{"type": "unsupported_type", "signals": {}}])
69
-
70
- errors = excinfo.value.errors()
71
- print(errors)
72
- assert len(errors) == 1
73
- assert errors[0]["type"] == "literal_error"
74
-
75
-
76
- def test_plot_config_no_source_type_provided(setup_devices):
77
- with pytest.raises(ValidationError) as excinfo:
78
- PlotConfig(sources=[{"signals": {}}])
79
-
80
- errors = excinfo.value.errors()
81
- assert len(errors) == 1
82
- assert errors[0]["type"] == "missing"
83
-
84
-
85
- def test_plot_config_history_source_type(setup_devices):
86
- history_source = {
87
- "type": "history",
88
- "scan_id": "valid_scan_id",
89
- "signals": {"x": [{"name": "samx"}], "y": [{"name": "samx"}]},
90
- }
91
-
92
- plot_config = PlotConfig(sources=[history_source])
93
-
94
- assert len(plot_config.sources) == 1
95
- assert plot_config.sources[0].type == "history"
96
- assert plot_config.sources[0].scan_id == "valid_scan_id"
97
-
98
-
99
- def test_plot_config_redis_source_type(setup_devices):
100
- history_source = {
101
- "type": "redis",
102
- "endpoint": "valid_endpoint",
103
- "update": "append",
104
- "signals": {"x": [{"name": "samx"}], "y": [{"name": "samx"}]},
105
- }
106
-
107
- plot_config = PlotConfig(sources=[history_source])
108
-
109
- assert len(plot_config.sources) == 1
110
- assert plot_config.sources[0].type == "redis"
File without changes