bec-widgets 0.82.0__py3-none-any.whl → 0.82.2__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.
CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v0.82.2 (2024-07-08)
4
+
5
+ ### Fix
6
+
7
+ * fix(rpc_server): pass cli config to server ([`90178e2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/90178e2f61fa9dac7d82c0d0db40a9767bb133e6))
8
+
9
+ ## v0.82.1 (2024-07-07)
10
+
11
+ ### Fix
12
+
13
+ * fix(motor_map): bug where motors without limits were selected ([`c78cd89`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c78cd898f203f950d7cb589eb5609feaa88062cf))
14
+
15
+ ### Refactor
16
+
17
+ * refactor(setting_dialog): moved to qt_utils ([`3826bb3`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3826bb3d9e870e85709b5b20ef09a4d22641280c))
18
+
19
+ * refactor(toolbar): toolbar moved from widgets to qt_utils ([`7ffc06f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7ffc06f3c7ddd86a1681408a75221b9bbadb236b))
20
+
21
+ ### Test
22
+
23
+ * test(setting_dialog): tests added ([`74a249b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/74a249bd065d01006cb532bfff2a9bfedb34b592))
24
+
25
+ ### Unknown
26
+
27
+ * tests(motor_map_widget): tests added ([`734f4c7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/734f4c77507a1edafd477d81b5f7401d8e759be2))
28
+
29
+ * feat(settings_dialog):apply button ([`2020953`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2020953b933b6fcad61ecc770588d39518c26fdd))
30
+
3
31
  ## v0.82.0 (2024-07-07)
4
32
 
5
33
  ### Feature
@@ -110,12 +138,6 @@
110
138
 
111
139
  ## v0.77.0 (2024-07-02)
112
140
 
113
- ### Feature
114
-
115
- * feat(bec_connector): export config to yaml ([`a391f30`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a391f3018c50fee6a4a06884491b957df80c3cd3))
116
-
117
- * feat(utils): colors added convertor for rgba to hex ([`572f2fb`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/572f2fb8110d5cb0e80f3ca45ce57ef405572456))
118
-
119
141
  ### Fix
120
142
 
121
143
  * fix(waveform): scatter 2D brush error ([`215d59c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/215d59c8bfe7fda9aff8cec8353bef9e1ce2eca1))
@@ -123,17 +145,3 @@
123
145
  * fix(figure): API cleanup ([`008a33a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/008a33a9b192473cc58e90cd6d98c5bcb5f7b8c0))
124
146
 
125
147
  * fix(figure): if/else logic corrected in subplot_factory ([`3e78723`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3e787234c7274b0698423d7bf9a4c54ec46bad5f))
126
-
127
- * fix(image): processing of already displayed data; closes #106 ([`1173510`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1173510105d2d70d7e498c2ac1e122cea3a16597))
128
-
129
- * fix(bec_figure): full reconstruction with config from other bec figure ([`b6e1e20`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b6e1e20b7c8549bb092e981062329e601411dda6))
130
-
131
- * fix(motor_map): API changes updates current visualisation; motor_map can be initialised from config ([`2e2d422`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2e2d422910685a2527a3d961a468c787f771ca44))
132
-
133
- * fix(image): image add_custom_image fixed, closes #225 ([`f0556e4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f0556e44113ffee66cf735aa2dd758c62cb634f4))
134
-
135
- * fix(figure): subplot methods consolidated; added subplot factory ([`4a97105`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4a97105e4bd2ce77d72dfe5f8307dd9ee65b21b0))
136
-
137
- * fix(image): image can be fully reconstructed from config ([`797f73c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/797f73c39aa73e07d6311f3de4baea53f6c380e0))
138
-
139
- * fix(image_item): vrange added int for pydantic model check ([`b8f796f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b8f796fd3fcc15641e8fc6a3ca75c344ce90fc45))
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 0.82.0
3
+ Version: 0.82.2
4
4
  Summary: BEC Widgets
5
5
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
6
6
  Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import importlib
4
4
  import importlib.metadata as imd
5
+ import json
5
6
  import os
6
7
  import select
7
8
  import subprocess
@@ -87,7 +88,7 @@ def _get_output(process, logger) -> None:
87
88
  print(f"Error reading process output: {str(e)}")
88
89
 
89
90
 
90
- def _start_plot_process(gui_id, gui_class, config, logger=None) -> None:
91
+ def _start_plot_process(gui_id: str, gui_class: type, config: dict | str, logger=None) -> None:
91
92
  """
92
93
  Start the plot in a new process.
93
94
 
@@ -98,6 +99,8 @@ def _start_plot_process(gui_id, gui_class, config, logger=None) -> None:
98
99
  # pylint: disable=subprocess-run-check
99
100
  command = ["bec-gui-server", "--id", gui_id, "--gui_class", gui_class.__name__]
100
101
  if config:
102
+ if isinstance(config, dict):
103
+ config = json.dumps(config)
101
104
  command.extend(["--config", config])
102
105
 
103
106
  env_dict = os.environ.copy()
@@ -190,7 +193,7 @@ class BECGuiClientMixin:
190
193
  if self._process is None or self._process.poll() is not None:
191
194
  self._start_update_script()
192
195
  self._process, self._process_output_processing_thread = _start_plot_process(
193
- self._gui_id, self.__class__, self._client._service_config.config_path
196
+ self._gui_id, self.__class__, self._client._service_config.config
194
197
  )
195
198
  while not self.gui_is_alive():
196
199
  print("Waiting for GUI to start...")
bec_widgets/cli/server.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import inspect
4
+ import json
4
5
  import signal
5
6
  import sys
6
7
  from contextlib import redirect_stderr, redirect_stdout
@@ -141,10 +142,30 @@ class SimpleFileLikeFromLogOutputFunc:
141
142
  return
142
143
 
143
144
 
145
+ def _start_server(gui_id: str, gui_class: Union[BECFigure, BECDockArea], config: str | None = None):
146
+ if config:
147
+ try:
148
+ config = json.loads(config)
149
+ service_config = ServiceConfig(config=config)
150
+ except (json.JSONDecodeError, TypeError):
151
+ service_config = ServiceConfig(config_path=config)
152
+ else:
153
+ # if no config is provided, use the default config
154
+ service_config = ServiceConfig()
155
+
156
+ bec_logger.configure(
157
+ service_config.redis,
158
+ QtRedisConnector,
159
+ service_name="BECWidgetsCLIServer",
160
+ service_config=service_config.service_config,
161
+ )
162
+ server = BECWidgetsCLIServer(gui_id=gui_id, config=service_config, gui_class=gui_class)
163
+ return server
164
+
165
+
144
166
  def main():
145
167
  import argparse
146
168
  import os
147
- import sys
148
169
 
149
170
  from qtpy.QtCore import QSize
150
171
  from qtpy.QtGui import QIcon
@@ -159,7 +180,7 @@ def main():
159
180
  type=str,
160
181
  help="Name of the gui class to be rendered. Possible values: \n- BECFigure\n- BECDockArea",
161
182
  )
162
- parser.add_argument("--config", type=str, help="Config file")
183
+ parser.add_argument("--config", type=str, help="Config file or config string.")
163
184
 
164
185
  args = parser.parse_args()
165
186
 
@@ -188,14 +209,7 @@ def main():
188
209
  win = QMainWindow()
189
210
  win.setWindowTitle("BEC Widgets")
190
211
 
191
- service_config = ServiceConfig(args.config)
192
- bec_logger.configure(
193
- service_config.redis,
194
- QtRedisConnector,
195
- service_name="BECWidgetsCLIServer",
196
- service_config=service_config.service_config,
197
- )
198
- server = BECWidgetsCLIServer(gui_id=args.id, config=service_config, gui_class=gui_class)
212
+ server = _start_server(args.id, gui_class, args.config)
199
213
 
200
214
  gui = server.gui
201
215
  win.setCentralWidget(gui)
File without changes
@@ -0,0 +1,107 @@
1
+ from qtpy.QtCore import Slot
2
+ from qtpy.QtWidgets import QDialog, QDialogButtonBox, QHBoxLayout, QPushButton, QVBoxLayout, QWidget
3
+
4
+
5
+ class SettingWidget(QWidget):
6
+ """
7
+ Abstract class for a settings widget to enforce the implementation of the accept_changes and display_current_settings.
8
+ Can be used for toolbar actions to display the settings of a widget.
9
+
10
+ Args:
11
+ target_widget (QWidget): The widget that the settings will be taken from and applied to.
12
+ """
13
+
14
+ def __init__(self, parent=None, *args, **kwargs):
15
+ super().__init__(parent, *args, **kwargs)
16
+
17
+ self.target_widget = None
18
+
19
+ def set_target_widget(self, target_widget: QWidget):
20
+ self.target_widget = target_widget
21
+
22
+ @Slot()
23
+ def accept_changes(self):
24
+ """
25
+ Accepts the changes made in the settings widget and applies them to the target widget.
26
+ """
27
+ pass
28
+
29
+ @Slot(dict)
30
+ def display_current_settings(self, config_dict: dict):
31
+ """
32
+ Displays the current settings of the target widget in the settings widget.
33
+
34
+ Args:
35
+ config_dict(dict): The current settings of the target widget.
36
+ """
37
+ pass
38
+
39
+
40
+ class SettingsDialog(QDialog):
41
+ """
42
+ Dialog to display and edit the settings of a widget with accept and cancel buttons.
43
+
44
+ Args:
45
+ parent (QWidget): The parent widget of the dialog.
46
+ target_widget (QWidget): The widget that the settings will be taken from and applied to.
47
+ settings_widget (SettingWidget): The widget that will display the settings.
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ parent=None,
53
+ settings_widget: SettingWidget = None,
54
+ window_title: str = "Settings",
55
+ config: dict = None,
56
+ *args,
57
+ **kwargs,
58
+ ):
59
+ super().__init__(parent, *args, **kwargs)
60
+
61
+ self.setModal(False)
62
+
63
+ self.setWindowTitle(window_title)
64
+
65
+ self.widget = settings_widget
66
+ self.widget.set_target_widget(parent)
67
+ if config is None:
68
+ config = parent.get_config()
69
+
70
+ self.widget.display_current_settings(config)
71
+
72
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
73
+
74
+ self.apply_button = QPushButton("Apply")
75
+
76
+ button_layout = QHBoxLayout()
77
+ button_layout.addWidget(self.button_box.button(QDialogButtonBox.Cancel))
78
+ button_layout.addWidget(self.apply_button)
79
+ button_layout.addWidget(self.button_box.button(QDialogButtonBox.Ok))
80
+
81
+ self.button_box.accepted.connect(self.accept)
82
+ self.button_box.rejected.connect(self.reject)
83
+ self.apply_button.clicked.connect(self.apply_changes)
84
+
85
+ self.layout = QVBoxLayout(self)
86
+ self.layout.setContentsMargins(5, 5, 5, 5)
87
+ self.layout.addWidget(self.widget)
88
+ self.layout.addLayout(button_layout)
89
+
90
+ ok_button = self.button_box.button(QDialogButtonBox.Ok)
91
+ ok_button.setDefault(True)
92
+ ok_button.setAutoDefault(True)
93
+
94
+ @Slot()
95
+ def accept(self):
96
+ """
97
+ Accept the changes made in the settings widget and close the dialog.
98
+ """
99
+ self.widget.accept_changes()
100
+ super().accept()
101
+
102
+ @Slot()
103
+ def apply_changes(self):
104
+ """
105
+ Apply the changes made in the settings widget without closing the dialog.
106
+ """
107
+ self.widget.accept_changes()
@@ -3,8 +3,7 @@ from collections import defaultdict
3
3
 
4
4
  # pylint: disable=no-name-in-module
5
5
  from qtpy.QtCore import QSize
6
- from qtpy.QtGui import QAction
7
- from qtpy.QtWidgets import QHBoxLayout, QLabel, QSpinBox, QToolBar, QWidget
6
+ from qtpy.QtWidgets import QToolBar, QWidget
8
7
 
9
8
 
10
9
  class ToolBarAction(ABC):
@@ -18,34 +17,6 @@ class ToolBarAction(ABC):
18
17
  """
19
18
 
20
19
 
21
- class ColumnAdjustAction(ToolBarAction):
22
- """Toolbar spinbox to adjust number of columns in the plot layout"""
23
-
24
- def add_to_toolbar(self, toolbar: QToolBar, target: QWidget):
25
- """Creates a access history button for the toolbar.
26
-
27
- Args:
28
- toolbar (QToolBar): The toolbar to add the action to.
29
- target (QWidget): The widget that the 'Access Scan History' action will be targeted.
30
-
31
- Returns:
32
- QAction: The 'Access Scan History' action created for the toolbar.
33
- """
34
- widget = QWidget()
35
- layout = QHBoxLayout(widget)
36
-
37
- label = QLabel("Columns:")
38
- spin_box = QSpinBox()
39
- spin_box.setMinimum(1) # Set minimum value
40
- spin_box.setMaximum(10) # Set maximum value
41
- spin_box.setValue(target.get_column_count()) # Initial value
42
- spin_box.valueChanged.connect(lambda value: target.set_column_count(value))
43
-
44
- layout.addWidget(label)
45
- layout.addWidget(spin_box)
46
- toolbar.addWidget(widget)
47
-
48
-
49
20
  class ModularToolBar(QToolBar):
50
21
  """Modular toolbar with optional automatic initialization.
51
22
  Args:
@@ -271,11 +271,12 @@ class BECMotorMap(BECPlotBase):
271
271
  def _swap_limit_map(self):
272
272
  """Swap the limit map."""
273
273
  self.plot_item.removeItem(self.plot_components["limit_map"])
274
- self.plot_components["limit_map"] = self._make_limit_map(
275
- self.config.signals.x.limits, self.config.signals.y.limits
276
- )
277
- self.plot_components["limit_map"].setZValue(-1)
278
- self.plot_item.addItem(self.plot_components["limit_map"])
274
+ if self.config.signals.x.limits is not None and self.config.signals.y.limits is not None:
275
+ self.plot_components["limit_map"] = self._make_limit_map(
276
+ self.config.signals.x.limits, self.config.signals.y.limits
277
+ )
278
+ self.plot_components["limit_map"].setZValue(-1)
279
+ self.plot_item.addItem(self.plot_components["limit_map"])
279
280
 
280
281
  def _make_motor_map(self):
281
282
  """
@@ -284,9 +285,10 @@ class BECMotorMap(BECPlotBase):
284
285
  # Create limit map
285
286
  motor_x_limit = self.config.signals.x.limits
286
287
  motor_y_limit = self.config.signals.y.limits
287
- self.plot_components["limit_map"] = self._make_limit_map(motor_x_limit, motor_y_limit)
288
- self.plot_item.addItem(self.plot_components["limit_map"])
289
- self.plot_components["limit_map"].setZValue(-1)
288
+ if motor_x_limit is not None or motor_y_limit is not None:
289
+ self.plot_components["limit_map"] = self._make_limit_map(motor_x_limit, motor_y_limit)
290
+ self.plot_item.addItem(self.plot_components["limit_map"])
291
+ self.plot_components["limit_map"].setZValue(-1)
290
292
 
291
293
  # Create scatter plot
292
294
  scatter_size = self.config.scatter_size
@@ -1,19 +1,19 @@
1
1
  import os
2
2
 
3
3
  from qtpy.QtCore import Slot
4
- from qtpy.QtWidgets import QDialog, QDialogButtonBox, QLabel, QVBoxLayout, QWidget
4
+ from qtpy.QtWidgets import QVBoxLayout
5
5
 
6
+ from bec_widgets.qt_utils.settings_dialog import SettingWidget
6
7
  from bec_widgets.utils import UILoader
7
8
  from bec_widgets.utils.widget_io import WidgetIO
8
9
 
9
10
 
10
- class MotorMapSettings(QWidget):
11
- def __init__(self, parent=None, target_widget: QWidget = None, *args, **kwargs):
11
+ class MotorMapSettings(SettingWidget):
12
+ def __init__(self, parent=None, *args, **kwargs):
12
13
  super().__init__(parent, *args, **kwargs)
13
14
  current_path = os.path.dirname(__file__)
14
15
 
15
16
  self.ui = UILoader(self).loader(os.path.join(current_path, "motor_map_settings.ui"))
16
- self.target_widget = target_widget
17
17
 
18
18
  self.layout = QVBoxLayout(self)
19
19
  self.layout.addWidget(self.ui)
@@ -45,29 +45,3 @@ class MotorMapSettings(QWidget):
45
45
  self.target_widget.set_scatter_size(scatter_size)
46
46
  self.target_widget.set_background_value(background_intensity)
47
47
  self.target_widget.set_color(color)
48
-
49
-
50
- class MotorMapDialog(QDialog):
51
- def __init__(self, parent=None, target_widget: QWidget = None, *args, **kwargs):
52
- super().__init__(parent, *args, **kwargs)
53
-
54
- self.setModal(False)
55
-
56
- self.setWindowTitle("Motor Map Settings")
57
-
58
- self.target_widget = target_widget
59
- self.widget = MotorMapSettings(target_widget=self.target_widget)
60
- self.widget.display_current_settings(self.target_widget._config_dict)
61
- self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
62
-
63
- self.button_box.accepted.connect(self.accept)
64
- self.button_box.rejected.connect(self.reject)
65
-
66
- self.layout = QVBoxLayout(self)
67
- self.layout.addWidget(self.widget)
68
- self.layout.addWidget(self.button_box)
69
-
70
- @Slot()
71
- def accept(self):
72
- self.widget.accept_changes()
73
- super().accept()
@@ -4,8 +4,8 @@ from qtpy.QtCore import QSize
4
4
  from qtpy.QtGui import QAction, QIcon
5
5
  from qtpy.QtWidgets import QHBoxLayout, QLabel, QWidget
6
6
 
7
+ from bec_widgets.qt_utils.toolbar import ToolBarAction
7
8
  from bec_widgets.widgets.device_combobox.device_combobox import DeviceComboBox
8
- from bec_widgets.widgets.toolbar.toolbar import ToolBarAction
9
9
 
10
10
 
11
11
  class DeviceSelectionAction(ToolBarAction):
@@ -4,17 +4,18 @@ import sys
4
4
 
5
5
  from qtpy.QtWidgets import QVBoxLayout, QWidget
6
6
 
7
+ from bec_widgets.qt_utils.settings_dialog import SettingsDialog
8
+ from bec_widgets.qt_utils.toolbar import ModularToolBar
7
9
  from bec_widgets.utils import BECConnector
8
10
  from bec_widgets.widgets.figure import BECFigure
9
11
  from bec_widgets.widgets.figure.plots.motor_map.motor_map import MotorMapConfig
10
- from bec_widgets.widgets.motor_map.motor_map_dialog.motor_map_settings import MotorMapDialog
12
+ from bec_widgets.widgets.motor_map.motor_map_dialog.motor_map_settings import MotorMapSettings
11
13
  from bec_widgets.widgets.motor_map.motor_map_dialog.motor_map_toolbar import (
12
14
  ConnectAction,
13
15
  DeviceSelectionAction,
14
16
  ResetHistoryAction,
15
17
  SettingsAction,
16
18
  )
17
- from bec_widgets.widgets.toolbar import ModularToolBar
18
19
 
19
20
 
20
21
  class BECMotorMapWidget(BECConnector, QWidget):
@@ -92,7 +93,9 @@ class BECMotorMapWidget(BECConnector, QWidget):
92
93
  toolbar_y.setStyleSheet("QComboBox {{ background-color: " "; }}")
93
94
 
94
95
  def show_settings(self) -> None:
95
- dialog = MotorMapDialog(self, target_widget=self)
96
+ dialog = SettingsDialog(
97
+ self, settings_widget=MotorMapSettings(), window_title="Motor Map Settings"
98
+ )
96
99
  dialog.exec()
97
100
 
98
101
  ###################################
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 0.82.0
3
+ Version: 0.82.2
4
4
  Summary: BEC Widgets
5
5
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
6
6
  Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
@@ -2,11 +2,11 @@
2
2
  .gitlab-ci.yml,sha256=vuDJPAYOK0995_H6fu0N5dIgIrOJgTvKr0HZkNWlxMw,8142
3
3
  .pylintrc,sha256=eeY8YwSI74oFfq6IYIbCqnx3Vk8ZncKaatv96n_Y8Rs,18544
4
4
  .readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
5
- CHANGELOG.md,sha256=k2WtOtHgXemiWHWfpEiNOVE8J8rSXScPG5bnccZKTtw,6798
5
+ CHANGELOG.md,sha256=7VVqe14x1w6butWm_Du8rzXRFtf3RQYV3CsURU1-uT0,6428
6
6
  LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
7
- PKG-INFO,sha256=LT1qiOoINPiD0fx8bR3xpuvsb8e3_dscPXfGK1NVQNs,1309
7
+ PKG-INFO,sha256=9lGCW5x2H2CEYe7DJsHs1LlXB0Pn_hr7efNeRFUGzgo,1309
8
8
  README.md,sha256=Od69x-RS85Hph0-WwWACwal4yUd67XkEn4APEfHhHFw,2649
9
- pyproject.toml,sha256=wcznXGDLca8bvxpzz4gq3qCV0D7E9st-N9uyKRSo028,2358
9
+ pyproject.toml,sha256=Jsa50E-GnKAaTBdJplPyg-B7sHQsIPL2GS-IHwugDFA,2358
10
10
  .git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
11
11
  .gitlab/issue_templates/bug_report_template.md,sha256=gAuyEwl7XlnebBrkiJ9AqffSNOywmr8vygUFWKTuQeI,386
12
12
  .gitlab/issue_templates/documentation_update_template.md,sha256=FHLdb3TS_D9aL4CYZCjyXSulbaW5mrN2CmwTaeLPbNw,860
@@ -18,11 +18,11 @@ bec_widgets/assets/terminal_icon.png,sha256=bJl7Tft4Fi2uxvuXI8o14uMHnI9eAWKSU2uf
18
18
  bec_widgets/cli/__init__.py,sha256=d0Q6Fn44e7wFfLabDOBxpcJ1DPKWlFunGYDUBmO-4hA,22
19
19
  bec_widgets/cli/auto_updates.py,sha256=DyBV3HnjMSH-cvVkYNcDiYKVf0Xut4Qy2qGQqkW47Bw,4833
20
20
  bec_widgets/cli/client.py,sha256=MMDy1edr1j9klgVGZZmD1dzLxi74gp75Jf4FNed9al8,60084
21
- bec_widgets/cli/client_utils.py,sha256=zq1gPW7t4n9Nsn4MLkdUeKwwl-9nUcf5UjuN8lZr9iY,12281
21
+ bec_widgets/cli/client_utils.py,sha256=cDhabblwaP88a0jlVpbn_RWWKVbsyjhmmGtMh9gesEw,12388
22
22
  bec_widgets/cli/generate_cli.py,sha256=Ea5px9KblUlcGg-1JbJBTIU7laGg2n8PM7Efw9WVVzM,5889
23
23
  bec_widgets/cli/rpc_register.py,sha256=QxXUZu5XNg00Yf5O3UHWOXg3-f_pzKjjoZYMOa-MOJc,2216
24
24
  bec_widgets/cli/rpc_wigdet_handler.py,sha256=6kQng2DyS6rhLJqSJ7xa0kdgSxp-35A2upcf833dJRE,1483
25
- bec_widgets/cli/server.py,sha256=2EJvkQDzrDTsZjRPs7g2v_iPTspGqxzY34tRAnvjxjY,7281
25
+ bec_widgets/cli/server.py,sha256=FZkqsjUHIkCUFMKUFm7ls_eslXvhIFzLpINEYxeWP5s,7722
26
26
  bec_widgets/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  bec_widgets/examples/jupyter_console/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  bec_widgets/examples/jupyter_console/jupyter_console_window.py,sha256=oOVCTY68bAn9HDFd5W_xNqQhI7Nsm7TaqYYLqLWXo2w,5520
@@ -34,6 +34,9 @@ bec_widgets/examples/plugin_example_pyside/taskmenuextension.pyproject,sha256=O_
34
34
  bec_widgets/examples/plugin_example_pyside/tictactoe.py,sha256=s3rCurXloVcmMdzZiSzDS7Lgj0Qe6x8-wkxCeiXYX80,4904
35
35
  bec_widgets/examples/plugin_example_pyside/tictactoeplugin.py,sha256=BBt3MD8oDLUMCCY3mioJa1QRR0WQdW6DuvVmK1Taovk,1734
36
36
  bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py,sha256=LNwplI6deUdKY6FOhUuWBanotxk9asF2G-6k7lFfA8Y,2301
37
+ bec_widgets/qt_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
+ bec_widgets/qt_utils/settings_dialog.py,sha256=rR_Zk4RGTnI4dz5OEzCc13lVpxlOKuwOf4_7wqXSbRw,3373
39
+ bec_widgets/qt_utils/toolbar.py,sha256=r6H4A8_3s2AItmdV8JS9UMSM4hV8Fx_162grYL2Pwzw,2111
37
40
  bec_widgets/utils/__init__.py,sha256=1930ji1Jj6dVuY81Wd2kYBhHYNV-2R0bN_L4o9zBj1U,533
38
41
  bec_widgets/utils/bec_connector.py,sha256=JScGWHEt4kh6C-C0O_JV_bOuiQlBjYTAPJktqgeAj70,9534
39
42
  bec_widgets/utils/bec_designer.py,sha256=ak3G8FdojUPjVBBwdPXw7tN5P2Uxr-SSoQt394jXeAA,4308
@@ -102,7 +105,7 @@ bec_widgets/widgets/figure/plots/image/image.py,sha256=8J-20r12FD9_Wtv-YSzJsWdq3
102
105
  bec_widgets/widgets/figure/plots/image/image_item.py,sha256=TyarahdWEn0jgalj5fqSAmcznXSbENkqHrrlL2GVdU4,10558
103
106
  bec_widgets/widgets/figure/plots/image/image_processor.py,sha256=GeTtWjbldy6VejMwPGQgM-o3d6bmLglCjdoktu19xfA,5262
104
107
  bec_widgets/widgets/figure/plots/motor_map/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
105
- bec_widgets/widgets/figure/plots/motor_map/motor_map.py,sha256=QYeXKcb87DPti19MaAFFr6LGma4F7GijaxnC1vx8Kng,18013
108
+ bec_widgets/widgets/figure/plots/motor_map/motor_map.py,sha256=FH3ZSYThGco98jS29r9EGcIh5fYx8e5eOs_nYJNrr48,18210
106
109
  bec_widgets/widgets/figure/plots/waveform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
110
  bec_widgets/widgets/figure/plots/waveform/waveform.py,sha256=e401GGvUF11rZKf4onB4zYlcUWIv8Zsnm4GcysaXi6U,29226
108
111
  bec_widgets/widgets/figure/plots/waveform/waveform_curve.py,sha256=yQZGPKs--3X_9Qg2pv0GUiL5WLBQVC3z_PJKRnsHqPU,8293
@@ -111,16 +114,16 @@ bec_widgets/widgets/jupyter_console/jupyter_console.py,sha256=ioLYJL31RdBoAOGFSS
111
114
  bec_widgets/widgets/motor_map/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
115
  bec_widgets/widgets/motor_map/bec_motor_map_widget.pyproject,sha256=NAI8s5gRKz80ED4KY7OgD2OgSH5HEsjt2ux2BYp66yg,63
113
116
  bec_widgets/widgets/motor_map/bec_motor_map_widget_plugin.py,sha256=PIfDE78Zqvsu7-xwoh9DdYkH8a7eOUFSaEl0bNrHYHc,1292
114
- bec_widgets/widgets/motor_map/motor_map_widget.py,sha256=bmXNRlEZfDC-YMxeC2QwmWX3ftESg4KQpOSxVnOYy1Q,7103
117
+ bec_widgets/widgets/motor_map/motor_map_widget.py,sha256=92v90z6IrxORzxYrPIt1dFXZL8cJg9viFcGzNHGOeBw,7243
115
118
  bec_widgets/widgets/motor_map/register_bec_motor_map_widget.py,sha256=qRG8PtWGjso0pWbvj_DXKnbUfmQzfGqPSrnAozXfM7o,492
116
119
  bec_widgets/widgets/motor_map/assets/connection.svg,sha256=czIb1BnshmxJnER8ssU3WcLENrFSIUfMwberajWOGAk,341
117
120
  bec_widgets/widgets/motor_map/assets/history.svg,sha256=bd6p5saxRiNRpd5OsSwIOvRWvel0WFEHul9zw4y9vH0,392
118
121
  bec_widgets/widgets/motor_map/assets/motor_map.png,sha256=xd6GFBM8gAb5c-xRgswxTZFtvo449yK57-EbkFvidOI,9746
119
122
  bec_widgets/widgets/motor_map/assets/settings.svg,sha256=ro30oa9CF1YijOXoUl-hz2DilJcqTy4rlTrMYhggumQ,1473
120
123
  bec_widgets/widgets/motor_map/motor_map_dialog/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
121
- bec_widgets/widgets/motor_map/motor_map_dialog/motor_map_settings.py,sha256=Y6ZkBGxsBy_cQ24K9YO8NbOZWpbJqAGC1H2-kDPdMfM,2931
124
+ bec_widgets/widgets/motor_map/motor_map_dialog/motor_map_settings.py,sha256=terJ5NHRzAsiUwdqg-hTbkQ5gadnRT0Ol0_Fm66kPF4,2011
122
125
  bec_widgets/widgets/motor_map/motor_map_dialog/motor_map_settings.ui,sha256=nv5vPftt6vcIl60OOZLRvwD29rdHVWOoGmz168BnwKw,2685
123
- bec_widgets/widgets/motor_map/motor_map_dialog/motor_map_toolbar.py,sha256=qVvNgGVPTbJ4j6GDWsdV61I__t5rcuiv-818I0Yc9XU,2201
126
+ bec_widgets/widgets/motor_map/motor_map_dialog/motor_map_toolbar.py,sha256=R6pmxhB_1ouc6f6cNQQvbxTHb3Ko3K_gTGs5WIYgnRI,2194
124
127
  bec_widgets/widgets/ring_progress_bar/__init__.py,sha256=_uoJKnDM2YAeUBfwc5WLbIHSJj7zm_FAurSKP3WRaCw,47
125
128
  bec_widgets/widgets/ring_progress_bar/ring.py,sha256=19zFj-6ZrIPLXYqvs5EPcrmDWnfnSLlEOmzJffL4d3A,11241
126
129
  bec_widgets/widgets/ring_progress_bar/ring_progress_bar.py,sha256=sU4Dur2XzBVfDYAYazI6pjOZOhzggoQIuc9VD3PWgac,24073
@@ -140,8 +143,6 @@ bec_widgets/widgets/toggle/register_toggle_switch.py,sha256=8pVtkeEeiDOjV4OPoZq1
140
143
  bec_widgets/widgets/toggle/toggle.py,sha256=JzCGYoyHBrlBWCoyL94QX4zSLyEwWbCNHMegjlnSy2E,4442
141
144
  bec_widgets/widgets/toggle/toggle_switch.pyproject,sha256=Msa-AS5H5XlqW65r8GlX2AxD8FnFnDyDgGnbKcXqKOw,24
142
145
  bec_widgets/widgets/toggle/toggle_switch_plugin.py,sha256=8e8JjUx6T5CIx7ixWLwVdT9JOJTbQ8aWwd3_9VAc_Mw,1177
143
- bec_widgets/widgets/toolbar/__init__.py,sha256=d-TP4_cr_VbpwreMM4ePnfZ5YXsEPQ45ibEf75nuGoE,36
144
- bec_widgets/widgets/toolbar/toolbar.py,sha256=rloOsr3TVGHLI4OJNKYWEBb1Z9_yJ0ZYIyf9RlPlxwY,3194
145
146
  bec_widgets/widgets/vscode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
146
147
  bec_widgets/widgets/vscode/vscode.py,sha256=ZyJJCJapYrGhqgudEt8JQn723DDqLdwjsXxXa5q3EkU,2544
147
148
  bec_widgets/widgets/website/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -202,7 +203,7 @@ tests/end-2-end/test_bec_figure_rpc_e2e.py,sha256=mjg29huqTivLnukG_XyoMtjOy2P_7J
202
203
  tests/end-2-end/test_rpc_register_e2e.py,sha256=blhMiW7HVHX1kGm5dg8Sv0PeCuJ0gnBz3evznQFz_B8,1619
203
204
  tests/end-2-end/test_scan_control_e2e.py,sha256=u7oLgFyltkMW2apSZKDukMIXvYrbhHrU32p4mBdn8VE,2276
204
205
  tests/unit_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
205
- tests/unit_tests/client_mocks.py,sha256=FEb875yuDu-LGvWsyzqCSruqVV63d4TTjovvJpKcvwA,4255
206
+ tests/unit_tests/client_mocks.py,sha256=BTBpA75CqeJhqYCI5M5XSX2-e4wMoHxi2rnc1PnmsuE,4549
206
207
  tests/unit_tests/conftest.py,sha256=KrnktXPWmZhnKNue-xGWOLD1XGEvdz9Vf7V2eO3XQ3A,596
207
208
  tests/unit_tests/test_bec_connector.py,sha256=zGDfNHwLFZTbpyX6-yc7Pwzr2jWO_HGZ8T4NFCNo4IE,2444
208
209
  tests/unit_tests/test_bec_dispatcher.py,sha256=rYPiRizHaswhGZw55IBMneDFxmPiCCLAZQBqjEkpdyY,3992
@@ -212,20 +213,23 @@ tests/unit_tests/test_bec_image.py,sha256=mjvcrHgOF_FCj6WbUyxvZH9HL63QGA5C0PNZ5d
212
213
  tests/unit_tests/test_bec_motor_map.py,sha256=dSYopbZS8lGD9cB26Kwmqw5zBoKCZs8t7DEEr50ug-g,8532
213
214
  tests/unit_tests/test_bec_queue.py,sha256=u-uc-iZeGAS8P90o6Cxy5oz_60zHpirGAu04OgQPDXw,4598
214
215
  tests/unit_tests/test_bec_status_box.py,sha256=gZdjyy9DNuUP9UwleTLj2Dp5HUImiqnkHjXWiqL0Q-o,4868
215
- tests/unit_tests/test_client_utils.py,sha256=eViJ1Tz-HX9TkMvQH6W8cO-c3_1I8bUc4_Yen6LOc0E,830
216
+ tests/unit_tests/test_client_utils.py,sha256=CBdWIVJ_UiyFzTJnX3XJm4PGw2uXhFvRCP_Y9ifckbw,2630
216
217
  tests/unit_tests/test_color_validation.py,sha256=xbFbtFDia36XLgaNrX2IwvAX3IDC_Odpj5BGoJSgiIE,2389
217
218
  tests/unit_tests/test_crosshair.py,sha256=3OMAJ2ZaISYXMOtkXf1rPdy94vCr8njeLi6uHblBL9Q,5045
218
- tests/unit_tests/test_device_input_base.py,sha256=ooA7lL-tqW-Cvd6mMx_xlupunsGw93PYFDc6CgiQPgg,2430
219
- tests/unit_tests/test_device_input_widgets.py,sha256=4zbN64p4BZcAGGc1gTB3QhFGYeBU7lQ6MtTblu1ZXrQ,5809
219
+ tests/unit_tests/test_device_input_base.py,sha256=r1tI7BFAhpv7V7gf_n5gjusyrBFOfuCqIkdVg7YA7vY,2444
220
+ tests/unit_tests/test_device_input_widgets.py,sha256=Q40xNKGvJT2dvNttRH68WZi0au8PwpUgBi2EB4LXfC8,5841
220
221
  tests/unit_tests/test_generate_cli_client.py,sha256=ng-eV5iF7Dhm-6YpxYo99CMY0KgqoaZBQNkMeKULDBU,3355
221
222
  tests/unit_tests/test_generate_plugin.py,sha256=9603ucZChM-pYpHadzsR94U1Zec1KZT34WedX9qzgMo,4464
223
+ tests/unit_tests/test_motor_map_widget.py,sha256=3nbINg3NYvWUrrGGMRPs8SDtePjXhoehSY_CShFGvEI,7507
222
224
  tests/unit_tests/test_plot_base.py,sha256=vWbt-QO5blSoE5C1392MIFha9A9JnUsx_D_9yTqhcRo,3845
223
225
  tests/unit_tests/test_plugin_utils.py,sha256=ayksWdrFY7kOiA0wVEjKFmFCF3UhH3lG8tzPVOpwysk,528
224
226
  tests/unit_tests/test_ring_progress_bar.py,sha256=hDlqkQho7FR7HAfM4Zrr4q1m773a3_rQ8CbM1GqDDSE,12252
225
227
  tests/unit_tests/test_rpc_register.py,sha256=hECjZEimd440mwRrO0rg7L3PKN7__3DgjmESN6wx3bo,1179
228
+ tests/unit_tests/test_rpc_server.py,sha256=MvstcvqUsnGAzUxw8Et1xXXikk_VIxFPwDZD0v1QGkg,1500
226
229
  tests/unit_tests/test_rpc_widget_handler.py,sha256=ceQ3BPnBIFY2Hy-sDPw0wxxREVTTphILts0gvX9qoUw,234
227
230
  tests/unit_tests/test_scan_control.py,sha256=Wr6KcE8av4sEIOx5VgYbzVCem3Jgb4Kzx_oOuvwlmkE,13459
228
231
  tests/unit_tests/test_scan_control_group_box.py,sha256=HNqjP10B_NonikspNwKz9upJU-t7xf6hwBerNhbC-uo,5563
232
+ tests/unit_tests/test_setting_dialog.py,sha256=QbHWwLa1VGFwYie-SN3rjS3ICo7A44S4AKN2qeNvIKY,3137
229
233
  tests/unit_tests/test_stop_button.py,sha256=tpQanzBUyl7qLXjbMUQqm3U3vShbKKARcnLpgsu3P0E,789
230
234
  tests/unit_tests/test_text_box_widget.py,sha256=cT0uEHt_6d-FwST0A_wE9sFW9E3F_nJbKhuBAeU4yHg,1862
231
235
  tests/unit_tests/test_toggle.py,sha256=Amzgres7te0tTQIDR2WMKSx9Kce44TxMpIPR6HZygXQ,832
@@ -239,8 +243,8 @@ tests/unit_tests/test_configs/config_device_no_entry.yaml,sha256=hdvue9KLc_kfNzG
239
243
  tests/unit_tests/test_configs/config_scan.yaml,sha256=vo484BbWOjA_e-h6bTjSV9k7QaQHrlAvx-z8wtY-P4E,1915
240
244
  tests/unit_tests/test_msgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
241
245
  tests/unit_tests/test_msgs/available_scans_message.py,sha256=m_z97hIrjHXXMa2Ex-UvsPmTxOYXfjxyJaGkIY6StTY,46532
242
- bec_widgets-0.82.0.dist-info/METADATA,sha256=LT1qiOoINPiD0fx8bR3xpuvsb8e3_dscPXfGK1NVQNs,1309
243
- bec_widgets-0.82.0.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
244
- bec_widgets-0.82.0.dist-info/entry_points.txt,sha256=3otEkCdDB9LZJuBLzG4pFLK5Di0CVybN_12IsZrQ-58,166
245
- bec_widgets-0.82.0.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
246
- bec_widgets-0.82.0.dist-info/RECORD,,
246
+ bec_widgets-0.82.2.dist-info/METADATA,sha256=9lGCW5x2H2CEYe7DJsHs1LlXB0Pn_hr7efNeRFUGzgo,1309
247
+ bec_widgets-0.82.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
248
+ bec_widgets-0.82.2.dist-info/entry_points.txt,sha256=3otEkCdDB9LZJuBLzG4pFLK5Di0CVybN_12IsZrQ-58,166
249
+ bec_widgets-0.82.2.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
250
+ bec_widgets-0.82.2.dist-info/RECORD,,
pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "bec_widgets"
7
- version = "0.82.0"
7
+ version = "0.82.2"
8
8
  description = "BEC Widgets"
9
9
  requires-python = ">=3.10"
10
10
  classifiers = [
@@ -72,6 +72,13 @@ class FakePositioner(FakeDevice):
72
72
  return MagicMock(get=MagicMock(return_value=self.read_value))
73
73
 
74
74
 
75
+ class Positioner(FakePositioner):
76
+ """just placeholder for testing embeded isinstance check in DeviceCombobox"""
77
+
78
+ def __init__(self, name="test", limits=None, read_value=1.0):
79
+ super().__init__(name, limits, read_value)
80
+
81
+
75
82
  class DMMock:
76
83
  def __init__(self):
77
84
  self.devices = DeviceContainer()
@@ -95,6 +102,7 @@ DEVICES = [
95
102
  FakeDevice("bpm3a"),
96
103
  FakeDevice("bpm3i"),
97
104
  FakeDevice("eiger"),
105
+ Positioner("test", limits=[-10, 10], read_value=2.0),
98
106
  ]
99
107
 
100
108
 
@@ -3,6 +3,7 @@ from unittest import mock
3
3
  import pytest
4
4
 
5
5
  from bec_widgets.cli.client import BECFigure
6
+ from bec_widgets.cli.client_utils import BECGuiClientMixin, _start_plot_process
6
7
 
7
8
  from .client_mocks import FakeDevice
8
9
 
@@ -27,3 +28,49 @@ def test_rpc_call_accepts_device_as_input(cli_figure):
27
28
  fig, mock_rpc_call = cli_figure
28
29
  fig.plot(x_name=dev1, y_name=dev2)
29
30
  mock_rpc_call.assert_called_with("plot", x_name="samx", y_name="bpm4i")
31
+
32
+
33
+ @pytest.mark.parametrize(
34
+ "config, call_config",
35
+ [
36
+ (None, None),
37
+ ("/path/to/config.yml", "/path/to/config.yml"),
38
+ ({"key": "value"}, '{"key": "value"}'),
39
+ ],
40
+ )
41
+ def test_client_utils_start_plot_process(config, call_config):
42
+ with mock.patch("bec_widgets.cli.client_utils.subprocess.Popen") as mock_popen:
43
+ _start_plot_process("gui_id", BECFigure, config)
44
+ command = ["bec-gui-server", "--id", "gui_id", "--gui_class", "BECFigure"]
45
+ if call_config:
46
+ command.extend(["--config", call_config])
47
+ mock_popen.assert_called_once_with(
48
+ command,
49
+ text=True,
50
+ start_new_session=True,
51
+ stdout=mock.ANY,
52
+ stderr=mock.ANY,
53
+ env=mock.ANY,
54
+ )
55
+
56
+
57
+ def test_client_utils_passes_client_config_to_server(bec_dispatcher):
58
+ """
59
+ Test that the client config is passed to the server. This ensures that
60
+ changes to the client config (either through config files or plugins) are
61
+ reflected in the server.
62
+ """
63
+ mixin = BECGuiClientMixin()
64
+ mixin._client = bec_dispatcher.client
65
+ mixin._gui_id = "gui_id"
66
+ mixin.gui_is_alive = mock.MagicMock()
67
+ mixin.gui_is_alive.side_effect = [True]
68
+
69
+ with mock.patch("bec_widgets.cli.client_utils._start_plot_process") as mock_start_plot:
70
+ with mock.patch.object(mixin, "_start_update_script") as mock_start_update:
71
+ mock_start_plot.return_value = [mock.MagicMock(), mock.MagicMock()]
72
+ mixin.show()
73
+ mock_start_plot.assert_called_once_with(
74
+ "gui_id", BECGuiClientMixin, mixin._client._service_config.config
75
+ )
76
+ mock_start_update.assert_called_once()
@@ -63,4 +63,4 @@ def test_device_input_base_get_device_list(device_input_base):
63
63
 
64
64
  def test_device_input_base_get_filters(device_input_base):
65
65
  filters = device_input_base.get_available_filters()
66
- assert filters == {"FakePositioner", "FakeDevice"}
66
+ assert filters == {"FakePositioner", "FakeDevice", "Positioner"}
@@ -67,6 +67,7 @@ def test_device_input_combobox_init(device_input_combobox):
67
67
  "bpm3a",
68
68
  "bpm3i",
69
69
  "eiger",
70
+ "test",
70
71
  ]
71
72
 
72
73
 
@@ -153,6 +154,7 @@ def test_device_input_line_edit_init(device_input_line_edit):
153
154
  "bpm3a",
154
155
  "bpm3i",
155
156
  "eiger",
157
+ "test",
156
158
  ]
157
159
 
158
160
 
@@ -0,0 +1,196 @@
1
+ from unittest.mock import MagicMock, patch
2
+
3
+ import pytest
4
+
5
+ from bec_widgets.widgets.motor_map.motor_map_dialog.motor_map_settings import MotorMapSettings
6
+ from bec_widgets.widgets.motor_map.motor_map_widget import BECMotorMapWidget
7
+
8
+ from .client_mocks import mocked_client
9
+
10
+
11
+ @pytest.fixture
12
+ def motor_map_widget(qtbot, mocked_client):
13
+ widget = BECMotorMapWidget(client=mocked_client())
14
+ widget.toolbar.widgets["motor_x"].device_combobox.set_device_filter("FakePositioner")
15
+ widget.toolbar.widgets["motor_y"].device_combobox.set_device_filter("FakePositioner")
16
+ qtbot.addWidget(widget)
17
+ qtbot.waitExposed(widget)
18
+ yield widget
19
+ widget.close()
20
+
21
+
22
+ @pytest.fixture
23
+ def mock_motor_map(motor_map_widget):
24
+ motor_map_mock = MagicMock()
25
+ motor_map_widget.map = motor_map_mock
26
+ return motor_map_mock
27
+
28
+
29
+ def test_motor_map_widget_init(motor_map_widget):
30
+ assert motor_map_widget is not None
31
+ assert motor_map_widget.client is not None
32
+ assert isinstance(motor_map_widget, BECMotorMapWidget)
33
+ assert motor_map_widget.config.widget_class == "BECMotorMapWidget"
34
+
35
+ # check initial state of toolbar actions
36
+ assert motor_map_widget.toolbar.widgets["connect"].action.isEnabled() == True
37
+ assert motor_map_widget.toolbar.widgets["config"].action.isEnabled() == False
38
+ assert motor_map_widget.toolbar.widgets["history"].action.isEnabled() == False
39
+ assert (
40
+ motor_map_widget.toolbar.widgets["motor_x"].device_combobox.config.device_filter
41
+ == "FakePositioner"
42
+ )
43
+ assert (
44
+ motor_map_widget.toolbar.widgets["motor_y"].device_combobox.config.device_filter
45
+ == "FakePositioner"
46
+ )
47
+ assert motor_map_widget.map.motor_x is None
48
+ assert motor_map_widget.map.motor_y is None
49
+
50
+
51
+ ###################################
52
+ # Toolbar Actions
53
+ ###################################
54
+
55
+
56
+ def test_motor_map_widget_change_motors_enable_toolbar(motor_map_widget):
57
+ motor_map_widget.change_motors("samx", "samy")
58
+ assert motor_map_widget.map.motor_x == "samx"
59
+ assert motor_map_widget.map.motor_y == "samy"
60
+ assert motor_map_widget.toolbar.widgets["motor_x"].device_combobox.currentText() == "samx"
61
+ assert motor_map_widget.toolbar.widgets["motor_y"].device_combobox.currentText() == "samy"
62
+ assert motor_map_widget.toolbar.widgets["config"].action.isEnabled() == True
63
+ assert motor_map_widget.toolbar.widgets["history"].action.isEnabled() == True
64
+
65
+
66
+ ###################################
67
+ # Wrapper methods for MotorMap
68
+ ###################################
69
+
70
+
71
+ def test_change_motors(motor_map_widget, mock_motor_map):
72
+ motor_map_widget.change_motors("motor_x", "motor_y", "motor_x_entry", "motor_y_entry", True)
73
+ mock_motor_map.change_motors.assert_called_once_with(
74
+ "motor_x", "motor_y", "motor_x_entry", "motor_y_entry", True
75
+ )
76
+
77
+
78
+ def test_get_data(motor_map_widget, mock_motor_map):
79
+ motor_map_widget.get_data()
80
+ mock_motor_map.get_data.assert_called_once()
81
+
82
+
83
+ def test_reset_history(motor_map_widget, mock_motor_map):
84
+ motor_map_widget.reset_history()
85
+ mock_motor_map.reset_history.assert_called_once()
86
+
87
+
88
+ def test_set_color(motor_map_widget, mock_motor_map):
89
+ motor_map_widget.set_color("blue")
90
+ mock_motor_map.set_color.assert_called_once_with("blue")
91
+
92
+
93
+ def test_set_max_points(motor_map_widget, mock_motor_map):
94
+ motor_map_widget.set_max_points(100)
95
+ mock_motor_map.set_max_points.assert_called_once_with(100)
96
+
97
+
98
+ def test_set_precision(motor_map_widget, mock_motor_map):
99
+ motor_map_widget.set_precision(2)
100
+ mock_motor_map.set_precision.assert_called_once_with(2)
101
+
102
+
103
+ def test_set_num_dim_points(motor_map_widget, mock_motor_map):
104
+ motor_map_widget.set_num_dim_points(50)
105
+ mock_motor_map.set_num_dim_points.assert_called_once_with(50)
106
+
107
+
108
+ def test_set_background_value(motor_map_widget, mock_motor_map):
109
+ motor_map_widget.set_background_value(128)
110
+ mock_motor_map.set_background_value.assert_called_once_with(128)
111
+
112
+
113
+ def test_set_scatter_size(motor_map_widget, mock_motor_map):
114
+ motor_map_widget.set_scatter_size(10)
115
+ mock_motor_map.set_scatter_size.assert_called_once_with(10)
116
+
117
+
118
+ ###################################
119
+ # MotorMap Dialog
120
+ ###################################
121
+
122
+
123
+ def test_motor_map_widget_clicked(motor_map_widget, qtbot):
124
+ motor_map_widget.toolbar.widgets["motor_x"].device_combobox.setCurrentText("samx")
125
+ motor_map_widget.toolbar.widgets["motor_y"].device_combobox.setCurrentText("samy")
126
+ motor_map_widget.toolbar.widgets["connect"].action.trigger()
127
+
128
+ qtbot.wait(200)
129
+
130
+ assert motor_map_widget.map.motor_x == "samx"
131
+ assert motor_map_widget.map.motor_y == "samy"
132
+ assert motor_map_widget.toolbar.widgets["config"].action.isEnabled() == True
133
+ assert motor_map_widget.toolbar.widgets["history"].action.isEnabled() == True
134
+
135
+
136
+ @pytest.fixture
137
+ def motor_map_settings(qtbot):
138
+ widget = MotorMapSettings()
139
+ qtbot.addWidget(widget)
140
+ qtbot.waitExposed(widget)
141
+ yield widget
142
+ widget.close()
143
+
144
+
145
+ def test_display_current_settings(motor_map_settings):
146
+ config = {
147
+ "max_points": 100,
148
+ "num_dim_points": 50,
149
+ "precision": 2,
150
+ "scatter_size": 10,
151
+ "background_value": 128,
152
+ "color": (255, 0, 0, 255),
153
+ }
154
+
155
+ with patch("bec_widgets.utils.widget_io.WidgetIO.set_value") as mock_set_value:
156
+ with patch.object(motor_map_settings.ui.color, "setColor") as mock_set_color:
157
+ motor_map_settings.display_current_settings(config)
158
+ mock_set_value.assert_any_call(motor_map_settings.ui.max_points, config["max_points"])
159
+ mock_set_value.assert_any_call(
160
+ motor_map_settings.ui.trace_dim, config["num_dim_points"]
161
+ )
162
+ mock_set_value.assert_any_call(motor_map_settings.ui.precision, config["precision"])
163
+ mock_set_value.assert_any_call(
164
+ motor_map_settings.ui.scatter_size, config["scatter_size"]
165
+ )
166
+ mock_set_value.assert_any_call(
167
+ motor_map_settings.ui.background_value, 50
168
+ ) # 128/255*100 = 50
169
+ mock_set_color.assert_called_once_with(config["color"])
170
+
171
+
172
+ def test_accept_changes(motor_map_settings):
173
+ with patch(
174
+ "bec_widgets.utils.widget_io.WidgetIO.get_value", side_effect=[100, 50, 2, 10, 50]
175
+ ) as mock_get_value:
176
+ with patch.object(
177
+ motor_map_settings.ui.color, "get_color", return_value=(255, 0, 0, 255)
178
+ ) as mock_get_color:
179
+ mock_target_widget = MagicMock()
180
+ motor_map_settings.set_target_widget(mock_target_widget)
181
+
182
+ motor_map_settings.accept_changes()
183
+
184
+ mock_get_value.assert_any_call(motor_map_settings.ui.max_points)
185
+ mock_get_value.assert_any_call(motor_map_settings.ui.trace_dim)
186
+ mock_get_value.assert_any_call(motor_map_settings.ui.precision)
187
+ mock_get_value.assert_any_call(motor_map_settings.ui.scatter_size)
188
+ mock_get_value.assert_any_call(motor_map_settings.ui.background_value)
189
+ mock_get_color.assert_called_once()
190
+
191
+ mock_target_widget.set_max_points.assert_called_once_with(100)
192
+ mock_target_widget.set_num_dim_points.assert_called_once_with(50)
193
+ mock_target_widget.set_precision.assert_called_once_with(2)
194
+ mock_target_widget.set_scatter_size.assert_called_once_with(10)
195
+ mock_target_widget.set_background_value.assert_called_once_with(127)
196
+ mock_target_widget.set_color.assert_called_once_with((255, 0, 0, 255))
@@ -0,0 +1,42 @@
1
+ from unittest import mock
2
+
3
+ import pytest
4
+ from bec_lib.service_config import ServiceConfig
5
+
6
+ from bec_widgets.cli.server import _start_server
7
+ from bec_widgets.widgets.figure import BECFigure
8
+
9
+
10
+ @pytest.fixture
11
+ def mocked_cli_server():
12
+ with mock.patch("bec_widgets.cli.server.BECWidgetsCLIServer") as mock_server:
13
+ with mock.patch("bec_widgets.cli.server.ServiceConfig") as mock_config:
14
+ with mock.patch("bec_lib.logger.bec_logger.configure") as mock_logger:
15
+ yield mock_server, mock_config, mock_logger
16
+
17
+
18
+ def test_rpc_server_start_server_without_service_config(mocked_cli_server):
19
+ """
20
+ Test that the server is started with the correct arguments.
21
+ """
22
+ mock_server, mock_config, _ = mocked_cli_server
23
+
24
+ _start_server("gui_id", BECFigure, None)
25
+ mock_server.assert_called_once_with(gui_id="gui_id", config=mock_config(), gui_class=BECFigure)
26
+
27
+
28
+ @pytest.mark.parametrize(
29
+ "config, call_config",
30
+ [
31
+ ("/path/to/config.yml", {"config_path": "/path/to/config.yml"}),
32
+ ({"key": "value"}, {"config": {"key": "value"}}),
33
+ ],
34
+ )
35
+ def test_rpc_server_start_server_with_service_config(mocked_cli_server, config, call_config):
36
+ """
37
+ Test that the server is started with the correct arguments.
38
+ """
39
+ mock_server, mock_config, _ = mocked_cli_server
40
+ config = mock_config(**call_config)
41
+ _start_server("gui_id", BECFigure, config)
42
+ mock_server.assert_called_once_with(gui_id="gui_id", config=config, gui_class=BECFigure)
@@ -0,0 +1,97 @@
1
+ from unittest.mock import MagicMock, patch
2
+
3
+ import pytest
4
+ from qtpy.QtWidgets import QWidget
5
+
6
+ from bec_widgets.qt_utils.settings_dialog import SettingsDialog, SettingWidget
7
+
8
+ ###################################
9
+ # SettingWidget base class tests
10
+ ###################################
11
+
12
+
13
+ @pytest.fixture
14
+ def setting_widget(qtbot):
15
+ widget = SettingWidget()
16
+ qtbot.addWidget(widget)
17
+ qtbot.waitExposed(widget)
18
+ yield widget
19
+ widget.close()
20
+
21
+
22
+ def test_setting_widget_initialization(setting_widget):
23
+ assert setting_widget.target_widget is None
24
+
25
+
26
+ def test_setting_widget_set_target_widget(setting_widget):
27
+ mock_target = MagicMock(spec=QWidget)
28
+ setting_widget.set_target_widget(mock_target)
29
+ assert setting_widget.target_widget == mock_target
30
+
31
+
32
+ def test_setting_widget_accept_changes(setting_widget):
33
+ with patch.object(setting_widget, "accept_changes") as mock_accept_changes:
34
+ setting_widget.accept_changes()
35
+ mock_accept_changes.assert_called_once()
36
+
37
+
38
+ def test_setting_widget_display_current_settings(setting_widget):
39
+ config_dict = {"setting1": "value1", "setting2": "value2"}
40
+ with patch.object(setting_widget, "display_current_settings") as mock_display_current_settings:
41
+ setting_widget.display_current_settings(config_dict)
42
+ mock_display_current_settings.assert_called_once_with(config_dict)
43
+
44
+
45
+ ###################################
46
+ # SettingsDialog tests
47
+ ###################################
48
+ @pytest.fixture
49
+ def settings_dialog(qtbot):
50
+ parent_widget = QWidget()
51
+ settings_widget = SettingWidget()
52
+ settings_widget.set_target_widget = MagicMock()
53
+ settings_widget.display_current_settings = MagicMock()
54
+ settings_widget.accept_changes = MagicMock()
55
+
56
+ dialog = SettingsDialog(
57
+ parent=parent_widget,
58
+ settings_widget=settings_widget,
59
+ window_title="Test Settings",
60
+ config={"setting1": "value1", "setting2": "value2"},
61
+ )
62
+ qtbot.addWidget(dialog)
63
+ qtbot.waitExposed(dialog)
64
+ yield dialog, parent_widget, settings_widget
65
+ dialog.close()
66
+
67
+
68
+ def test_settings_dialog_initialization(settings_dialog):
69
+ dialog, parent_widget, settings_widget = settings_dialog
70
+
71
+ assert dialog.windowTitle() == "Test Settings"
72
+ settings_widget.set_target_widget.assert_called_once_with(parent_widget)
73
+ settings_widget.display_current_settings.assert_called_once_with(
74
+ {"setting1": "value1", "setting2": "value2"}
75
+ )
76
+
77
+
78
+ def test_settings_dialog_accept(settings_dialog, qtbot):
79
+ dialog, _, settings_widget = settings_dialog
80
+
81
+ dialog.button_box.buttons()[0].click() # OK Button
82
+ settings_widget.accept_changes.assert_called_once()
83
+
84
+
85
+ def test_settings_dialog_reject(settings_dialog, qtbot):
86
+ dialog, _, _ = settings_dialog
87
+
88
+ with patch.object(dialog, "reject", wraps=dialog.reject) as mock_reject:
89
+ dialog.button_box.buttons()[1].click() # Cancel Button
90
+ mock_reject.assert_called_once()
91
+
92
+
93
+ def test_settings_dialog_apply_changes(settings_dialog, qtbot):
94
+ dialog, _, settings_widget = settings_dialog
95
+
96
+ dialog.apply_button.click()
97
+ settings_widget.accept_changes.assert_called_once()
@@ -1 +0,0 @@
1
- from .toolbar import ModularToolBar