bec-widgets 0.46.7__tar.gz → 0.48.0__tar.gz

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 (108) hide show
  1. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/PKG-INFO +1 -1
  2. bec_widgets-0.48.0/bec_widgets/cli/__init__.py +2 -0
  3. bec_widgets-0.48.0/bec_widgets/cli/auto_updates.py +118 -0
  4. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/cli/client_utils.py +11 -56
  5. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/__init__.py +1 -0
  6. bec_widgets-0.48.0/bec_widgets/utils/container_utils.py +45 -0
  7. bec_widgets-0.48.0/bec_widgets/utils/thread_checker.py +37 -0
  8. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/figure/figure.py +14 -35
  9. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets.egg-info/PKG-INFO +1 -1
  10. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets.egg-info/SOURCES.txt +3 -0
  11. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/setup.py +1 -1
  12. bec_widgets-0.46.7/bec_widgets/cli/__init__.py +0 -1
  13. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/LICENSE +0 -0
  14. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/README.md +0 -0
  15. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/__init__.py +0 -0
  16. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/cli/client.py +0 -0
  17. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/cli/generate_cli.py +0 -0
  18. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/cli/server.py +0 -0
  19. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/__init__.py +0 -0
  20. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/eiger_plot/__init__.py +0 -0
  21. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/eiger_plot/eiger_plot.py +0 -0
  22. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/eiger_plot/eiger_plot.ui +0 -0
  23. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/jupyter_console/__init__.py +0 -0
  24. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/jupyter_console/jupyter_console_window.py +0 -0
  25. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/jupyter_console/jupyter_console_window.ui +0 -0
  26. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/mca_readout/__init__.py +0 -0
  27. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/mca_readout/mca_plot.py +0 -0
  28. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/mca_readout/mca_sim.py +0 -0
  29. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/motor_movement/__init__.py +0 -0
  30. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/motor_movement/config_example.yaml +0 -0
  31. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/motor_movement/csax_bec_config.yaml +0 -0
  32. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/motor_movement/csaxs_config.yaml +0 -0
  33. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/motor_movement/motor_control_compilations.py +0 -0
  34. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/motor_movement/motor_controller.ui +0 -0
  35. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/motor_movement/motor_example.py +0 -0
  36. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/stream_plot/__init__.py +0 -0
  37. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/stream_plot/line_plot.ui +0 -0
  38. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/examples/stream_plot/stream_plot.py +0 -0
  39. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/simulations/__init__.py +0 -0
  40. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/bec_connector.py +0 -0
  41. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/bec_dispatcher.py +0 -0
  42. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/bec_table.py +0 -0
  43. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/colors.py +0 -0
  44. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/crosshair.py +0 -0
  45. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/ctrl_c.py +0 -0
  46. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/entry_validator.py +0 -0
  47. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/rpc_decorator.py +0 -0
  48. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/validator_delegate.py +0 -0
  49. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/widget_io.py +0 -0
  50. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/utils/yaml_dialog.py +0 -0
  51. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/validation/__init__.py +0 -0
  52. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/validation/monitor_config_validator.py +0 -0
  53. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/__init__.py +0 -0
  54. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/editor/__init__.py +0 -0
  55. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/editor/editor.py +0 -0
  56. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/figure/__init__.py +0 -0
  57. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/monitor/__init__.py +0 -0
  58. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/monitor/config_dialog.py +0 -0
  59. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/monitor/config_dialog.ui +0 -0
  60. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/monitor/monitor.py +0 -0
  61. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/monitor/tab_template.ui +0 -0
  62. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/motor_control/__init__.py +0 -0
  63. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/motor_control/motor_control.py +0 -0
  64. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/motor_control/motor_control_absolute.ui +0 -0
  65. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/motor_control/motor_control_relative.ui +0 -0
  66. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/motor_control/motor_control_selection.ui +0 -0
  67. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/motor_control/motor_control_table.ui +0 -0
  68. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/motor_map/__init__.py +0 -0
  69. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/motor_map/motor_map.py +0 -0
  70. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/plots/__init__.py +0 -0
  71. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/plots/image.py +0 -0
  72. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/plots/motor_map.py +0 -0
  73. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/plots/plot_base.py +0 -0
  74. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/plots/waveform.py +0 -0
  75. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/scan_control/__init__.py +0 -0
  76. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/scan_control/scan_control.py +0 -0
  77. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/toolbar/__init__.py +0 -0
  78. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets/widgets/toolbar/toolbar.py +0 -0
  79. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets.egg-info/dependency_links.txt +0 -0
  80. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets.egg-info/requires.txt +0 -0
  81. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/bec_widgets.egg-info/top_level.txt +0 -0
  82. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/setup.cfg +0 -0
  83. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/__init__.py +0 -0
  84. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/__init__.py +0 -0
  85. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/client_mocks.py +0 -0
  86. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/conftest.py +0 -0
  87. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_bec_connector.py +0 -0
  88. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_bec_dispatcher.py +0 -0
  89. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_bec_figure.py +0 -0
  90. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_bec_monitor.py +0 -0
  91. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_bec_motor_map.py +0 -0
  92. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_client_utils.py +0 -0
  93. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_config_dialog.py +0 -0
  94. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_crosshair.py +0 -0
  95. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_editor.py +0 -0
  96. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_eiger_plot.py +0 -0
  97. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_generate_cli_client.py +0 -0
  98. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_motor_control.py +0 -0
  99. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_motor_map.py +0 -0
  100. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_msgs/__init__.py +0 -0
  101. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_msgs/available_scans_message.py +0 -0
  102. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_plot_base.py +0 -0
  103. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_scan_control.py +0 -0
  104. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_stream_plot.py +0 -0
  105. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_validator_errors.py +0 -0
  106. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_waveform1d.py +0 -0
  107. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_widget_io.py +0 -0
  108. {bec_widgets-0.46.7 → bec_widgets-0.48.0}/tests/unit_tests/test_yaml_dialog.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bec_widgets
3
- Version: 0.46.7
3
+ Version: 0.48.0
4
4
  Summary: BEC Widgets
5
5
  Home-page: https://gitlab.psi.ch/bec/bec-widgets
6
6
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec-widgets/issues
@@ -0,0 +1,2 @@
1
+ from .auto_updates import AutoUpdates, ScanInfo
2
+ from .client import BECFigure
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from pydantic import BaseModel
6
+
7
+ if TYPE_CHECKING:
8
+ from .client import BECFigure
9
+
10
+
11
+ class ScanInfo(BaseModel):
12
+ scan_id: str
13
+ scan_number: int
14
+ scan_name: str
15
+ scan_report_devices: list
16
+ monitored_devices: list
17
+ status: str
18
+
19
+
20
+ class AutoUpdates:
21
+ def __init__(self, figure: BECFigure, enabled: bool = True):
22
+ self.enabled = enabled
23
+ self.figure = figure
24
+
25
+ @staticmethod
26
+ def get_scan_info(msg) -> ScanInfo:
27
+ """
28
+ Update the script with the given data.
29
+ """
30
+ info = msg.info
31
+ status = msg.status
32
+ scan_id = msg.scan_id
33
+ scan_number = info.get("scan_number", 0)
34
+ scan_name = info.get("scan_name", "Unknown")
35
+ scan_report_devices = info.get("scan_report_devices", [])
36
+ monitored_devices = info.get("readout_priority", {}).get("monitored", [])
37
+ monitored_devices = [dev for dev in monitored_devices if dev not in scan_report_devices]
38
+ return ScanInfo(
39
+ scan_id=scan_id,
40
+ scan_number=scan_number,
41
+ scan_name=scan_name,
42
+ scan_report_devices=scan_report_devices,
43
+ monitored_devices=monitored_devices,
44
+ status=status,
45
+ )
46
+
47
+ def run(self, msg):
48
+ """
49
+ Run the update function if enabled.
50
+ """
51
+ if not self.enabled:
52
+ return
53
+ if msg.status != "open":
54
+ return
55
+ info = self.get_scan_info(msg)
56
+ self.handler(info)
57
+
58
+ @staticmethod
59
+ def get_selected_device(monitored_devices, selected_device):
60
+ """
61
+ Get the selected device for the plot. If no device is selected, the first
62
+ device in the monitored devices list is selected.
63
+ """
64
+ if selected_device:
65
+ return selected_device
66
+ if len(monitored_devices) > 0:
67
+ sel_device = monitored_devices[0]
68
+ return sel_device
69
+ return None
70
+
71
+ def handler(self, info: ScanInfo) -> None:
72
+ """
73
+ Default update function.
74
+ """
75
+ if info.scan_name == "line_scan" and info.scan_report_devices:
76
+ self.simple_line_scan(info)
77
+ return
78
+ if info.scan_name == "grid_scan" and info.scan_report_devices:
79
+ self.simple_grid_scan(info)
80
+ return
81
+ if info.scan_report_devices:
82
+ self.best_effort(info)
83
+ return
84
+
85
+ def simple_line_scan(self, info: ScanInfo) -> None:
86
+ """
87
+ Simple line scan.
88
+ """
89
+ dev_x = info.scan_report_devices[0]
90
+ dev_y = self.get_selected_device(info.monitored_devices, self.figure.selected_device)
91
+ if not dev_y:
92
+ return
93
+ self.figure.clear_all()
94
+ plt = self.figure.plot(dev_x, dev_y)
95
+ plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
96
+
97
+ def simple_grid_scan(self, info: ScanInfo) -> None:
98
+ """
99
+ Simple grid scan.
100
+ """
101
+ dev_x = info.scan_report_devices[0]
102
+ dev_y = info.scan_report_devices[1]
103
+ dev_z = self.get_selected_device(info.monitored_devices, self.figure.selected_device)
104
+ self.figure.clear_all()
105
+ plt = self.figure.plot(dev_x, dev_y, dev_z, label=f"Scan {info.scan_number}")
106
+ plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
107
+
108
+ def best_effort(self, info: ScanInfo) -> None:
109
+ """
110
+ Best effort scan.
111
+ """
112
+ dev_x = info.scan_report_devices[0]
113
+ dev_y = self.get_selected_device(info.monitored_devices, self.figure.selected_device)
114
+ if not dev_y:
115
+ return
116
+ self.figure.clear_all()
117
+ plt = self.figure.plot(dev_x, dev_y, label=f"Scan {info.scan_number}")
118
+ plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import importlib
4
+ import importlib.metadata as imd
4
5
  import os
5
6
  import select
6
7
  import subprocess
@@ -17,6 +18,7 @@ from bec_lib.device import DeviceBase
17
18
  from qtpy.QtCore import QCoreApplication
18
19
 
19
20
  import bec_widgets.cli.client as client
21
+ from bec_widgets.cli.auto_updates import AutoUpdates
20
22
  from bec_widgets.utils.bec_dispatcher import BECDispatcher
21
23
 
22
24
  if TYPE_CHECKING:
@@ -54,68 +56,22 @@ def rpc_call(func):
54
56
  return wrapper
55
57
 
56
58
 
57
- def get_selected_device(monitored_devices, selected_device):
58
- """
59
- Get the selected device for the plot. If no device is selected, the first
60
- device in the monitored devices list is selected.
61
- """
62
- if selected_device:
63
- return selected_device
64
- if len(monitored_devices) > 0:
65
- sel_device = monitored_devices[0]
66
- return sel_device
67
- return None
68
-
69
-
70
- def update_script(figure: BECFigure, msg):
71
- """
72
- Update the script with the given data.
73
- """
74
- info = msg.info
75
- status = msg.status
76
- scan_id = msg.scan_id
77
- scan_number = info.get("scan_number", 0)
78
- scan_name = info.get("scan_name", "Unknown")
79
- scan_report_devices = info.get("scan_report_devices", [])
80
- monitored_devices = info.get("readout_priority", {}).get("monitored", [])
81
- monitored_devices = [dev for dev in monitored_devices if dev not in scan_report_devices]
82
-
83
- if scan_name == "line_scan" and scan_report_devices:
84
- dev_x = scan_report_devices[0]
85
- dev_y = get_selected_device(monitored_devices, figure.selected_device)
86
- print(f"Selected device: {dev_y}")
87
- if not dev_y:
88
- return
89
- figure.clear_all()
90
- plt = figure.plot(dev_x, dev_y)
91
- plt.set(title=f"Scan {scan_number}", x_label=dev_x, y_label=dev_y)
92
- elif scan_name == "grid_scan" and scan_report_devices:
93
- print(f"Scan {scan_number} is running")
94
- dev_x = scan_report_devices[0]
95
- dev_y = scan_report_devices[1]
96
- dev_z = get_selected_device(monitored_devices, figure.selected_device)
97
- figure.clear_all()
98
- plt = figure.plot(dev_x, dev_y, dev_z, label=f"Scan {scan_number}")
99
- plt.set(title=f"Scan {scan_number}", x_label=dev_x, y_label=dev_y)
100
- elif scan_report_devices:
101
- dev_x = scan_report_devices[0]
102
- dev_y = get_selected_device(monitored_devices, figure.selected_device)
103
- if not dev_y:
104
- return
105
- figure.clear_all()
106
- plt = figure.plot(dev_x, dev_y, label=f"Scan {scan_number}")
107
- plt.set(title=f"Scan {scan_number}", x_label=dev_x, y_label=dev_y)
108
-
109
-
110
59
  class BECFigureClientMixin:
111
60
  def __init__(self, **kwargs) -> None:
112
61
  super().__init__(**kwargs)
113
62
  self._process = None
114
- self.update_script = update_script
63
+ self.update_script = self._get_update_script()
115
64
  self._target_endpoint = MessageEndpoints.scan_status()
116
65
  self._selected_device = None
117
66
  self.stderr_output = []
118
67
 
68
+ def _get_update_script(self) -> AutoUpdates:
69
+ eps = imd.entry_points(group="bec.widgets.auto_updates")
70
+ for ep in eps:
71
+ if ep.name == "plugin_widgets_update":
72
+ return ep.load()(figure=self)
73
+ return None
74
+
119
75
  @property
120
76
  def selected_device(self):
121
77
  """
@@ -147,8 +103,7 @@ class BECFigureClientMixin:
147
103
  if isinstance(msg, messages.ScanStatusMessage):
148
104
  if not self.gui_is_alive():
149
105
  return
150
- if msg.status == "open":
151
- self.update_script(self, msg)
106
+ self.update_script.run(msg)
152
107
 
153
108
  def show(self) -> None:
154
109
  """
@@ -2,6 +2,7 @@ from .bec_connector import BECConnector, ConnectionConfig
2
2
  from .bec_dispatcher import BECDispatcher
3
3
  from .bec_table import BECTable
4
4
  from .colors import Colors
5
+ from .container_utils import WidgetContainerUtils
5
6
  from .crosshair import Crosshair
6
7
  from .entry_validator import EntryValidator
7
8
  from .rpc_decorator import register_rpc_methods, rpc_public
@@ -0,0 +1,45 @@
1
+ import itertools
2
+ from typing import Type
3
+
4
+ from qtpy.QtWidgets import QWidget
5
+
6
+
7
+ class WidgetContainerUtils:
8
+
9
+ @staticmethod
10
+ def generate_unique_widget_id(container: dict, prefix: str = "widget") -> str:
11
+ """
12
+ Generate a unique widget ID.
13
+ Args:
14
+ container(dict): The container of widgets.
15
+ prefix(str): The prefix of the widget ID.
16
+
17
+ Returns:
18
+ widget_id(str): The unique widget ID.
19
+ """
20
+ existing_ids = set(container.keys())
21
+ for i in itertools.count(1):
22
+ widget_id = f"{prefix}_{i}"
23
+ if widget_id not in existing_ids:
24
+ return widget_id
25
+
26
+ @staticmethod
27
+ def find_first_widget_by_class(
28
+ container: dict, widget_class: Type[QWidget], can_fail: bool = True
29
+ ) -> QWidget | None:
30
+ """
31
+ Find the first widget of a given class in the figure.
32
+ Args:
33
+ container(dict): The container of widgets.
34
+ widget_class(Type): The class of the widget to find.
35
+ can_fail(bool): If True, the method will return None if no widget is found. If False, it will raise an error.
36
+ Returns:
37
+ widget: The widget of the given class.
38
+ """
39
+ for widget_id, widget in container.items():
40
+ if isinstance(widget, widget_class):
41
+ return widget
42
+ if can_fail:
43
+ return None
44
+ else:
45
+ raise ValueError(f"No widget of class {widget_class} found.")
@@ -0,0 +1,37 @@
1
+ import threading
2
+
3
+
4
+ class ThreadTracker:
5
+ def __init__(self, exclude_names=None):
6
+ self.exclude_names = exclude_names if exclude_names else []
7
+ self.initial_threads = self._capture_threads()
8
+
9
+ def _capture_threads(self):
10
+ return set(
11
+ th
12
+ for th in threading.enumerate()
13
+ if not any(ex_name in th.name for ex_name in self.exclude_names)
14
+ and th is not threading.main_thread()
15
+ )
16
+
17
+ def _thread_info(self, threads):
18
+ return ", \n".join(f"{th.name}(ID: {th.ident})" for th in threads)
19
+
20
+ def check_unfinished_threads(self):
21
+ current_threads = self._capture_threads()
22
+ additional_threads = current_threads - self.initial_threads
23
+ closed_threads = self.initial_threads - current_threads
24
+ if additional_threads:
25
+ raise Exception(
26
+ f"###### Initial threads ######:\n {self._thread_info(self.initial_threads)}\n"
27
+ f"###### Current threads ######:\n {self._thread_info(current_threads)}\n"
28
+ f"###### Closed threads ######:\n {self._thread_info(closed_threads)}\n"
29
+ f"###### Unfinished threads ######:\n {self._thread_info(additional_threads)}"
30
+ )
31
+ else:
32
+ print(
33
+ "All threads properly closed.\n"
34
+ f"###### Initial threads ######:\n {self._thread_info(self.initial_threads)}\n"
35
+ f"###### Current threads ######:\n {self._thread_info(current_threads)}\n"
36
+ f"###### Closed threads ######:\n {self._thread_info(closed_threads)}"
37
+ )
@@ -14,7 +14,7 @@ from pyqtgraph.Qt import uic
14
14
  from qtpy.QtCore import Signal as pyqtSignal
15
15
  from qtpy.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
16
16
 
17
- from bec_widgets.utils import BECConnector, BECDispatcher, ConnectionConfig
17
+ from bec_widgets.utils import BECConnector, BECDispatcher, ConnectionConfig, WidgetContainerUtils
18
18
  from bec_widgets.widgets.plots import (
19
19
  BECImageShow,
20
20
  BECMotorMap,
@@ -188,7 +188,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
188
188
  config(dict): Additional configuration for the widget.
189
189
  **axis_kwargs(dict): Additional axis properties to set on the widget after creation.
190
190
  """
191
- widget_id = self._generate_unique_widget_id()
191
+ widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
192
192
  waveform = self.add_widget(
193
193
  widget_type="Waveform1D",
194
194
  widget_id=widget_id,
@@ -278,7 +278,9 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
278
278
  Returns:
279
279
  BECWaveform: The waveform plot widget.
280
280
  """
281
- waveform = self._find_first_widget_by_class(BECWaveform, can_fail=True)
281
+ waveform = WidgetContainerUtils.find_first_widget_by_class(
282
+ self._widgets, BECWaveform, can_fail=True
283
+ )
282
284
  if waveform is not None:
283
285
  if axis_kwargs:
284
286
  waveform.set(**axis_kwargs)
@@ -355,7 +357,9 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
355
357
  Returns:
356
358
  BECImageShow: The image widget.
357
359
  """
358
- image = self._find_first_widget_by_class(BECImageShow, can_fail=True)
360
+ image = WidgetContainerUtils.find_first_widget_by_class(
361
+ self._widgets, BECImageShow, can_fail=True
362
+ )
359
363
  if image is not None:
360
364
  if axis_kwargs:
361
365
  image.set(**axis_kwargs)
@@ -410,7 +414,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
410
414
  BECImageShow: The image widget.
411
415
  """
412
416
 
413
- widget_id = self._generate_unique_widget_id()
417
+ widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
414
418
  if config is None:
415
419
  config = ImageConfig(
416
420
  widget_class="BECImageShow",
@@ -457,7 +461,9 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
457
461
  Returns:
458
462
  BECMotorMap: The motor map widget.
459
463
  """
460
- motor_map = self._find_first_widget_by_class(BECMotorMap, can_fail=True)
464
+ motor_map = WidgetContainerUtils.find_first_widget_by_class(
465
+ self._widgets, BECMotorMap, can_fail=True
466
+ )
461
467
  if motor_map is not None:
462
468
  if axis_kwargs:
463
469
  motor_map.set(**axis_kwargs)
@@ -491,7 +497,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
491
497
  Returns:
492
498
  BECMotorMap: The motor map widget.
493
499
  """
494
- widget_id = self._generate_unique_widget_id()
500
+ widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
495
501
  if config is None:
496
502
  config = MotorMapConfig(
497
503
  widget_class="BECMotorMap",
@@ -532,7 +538,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
532
538
  **axis_kwargs(dict): Additional axis properties to set on the widget after creation.
533
539
  """
534
540
  if not widget_id:
535
- widget_id = self._generate_unique_widget_id()
541
+ widget_id = WidgetContainerUtils.generate_unique_widget_id(self._widgets)
536
542
  if widget_id in self._widgets:
537
543
  raise ValueError(f"Widget with ID '{widget_id}' already exists.")
538
544
 
@@ -610,25 +616,6 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
610
616
  self.setBackground("k" if theme == "dark" else "w")
611
617
  self.config.theme = theme
612
618
 
613
- def _find_first_widget_by_class(
614
- self, widget_class: Type[BECPlotBase], can_fail: bool = True
615
- ) -> BECPlotBase | None:
616
- """
617
- Find the first widget of a given class in the figure.
618
- Args:
619
- widget_class(Type[BECPlotBase]): The class of the widget to find.
620
- can_fail(bool): If True, the method will return None if no widget is found. If False, it will raise an error.
621
- Returns:
622
- BECPlotBase: The widget of the given class.
623
- """
624
- for widget_id, widget in self._widgets.items():
625
- if isinstance(widget, widget_class):
626
- return widget
627
- if can_fail:
628
- return None
629
- else:
630
- raise ValueError(f"No widget of class {widget_class} found.")
631
-
632
619
  def _remove_by_coordinates(self, row: int, col: int) -> None:
633
620
  """
634
621
  Remove a widget from the figure by its coordinates.
@@ -695,14 +682,6 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
695
682
  row += 1
696
683
  return row, col
697
684
 
698
- def _generate_unique_widget_id(self):
699
- """Generate a unique widget ID."""
700
- existing_ids = set(self._widgets.keys())
701
- for i in itertools.count(1):
702
- widget_id = f"widget_{i}"
703
- if widget_id not in existing_ids:
704
- return widget_id
705
-
706
685
  def _change_grid(self, widget_id: str, row: int, col: int):
707
686
  """
708
687
  Change the grid to reflect the new position of the widget.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bec-widgets
3
- Version: 0.46.7
3
+ Version: 0.48.0
4
4
  Summary: BEC Widgets
5
5
  Home-page: https://gitlab.psi.ch/bec/bec-widgets
6
6
  Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec-widgets/issues
@@ -4,6 +4,7 @@ setup.cfg
4
4
  setup.py
5
5
  ./bec_widgets/__init__.py
6
6
  ./bec_widgets/cli/__init__.py
7
+ ./bec_widgets/cli/auto_updates.py
7
8
  ./bec_widgets/cli/client.py
8
9
  ./bec_widgets/cli/client_utils.py
9
10
  ./bec_widgets/cli/generate_cli.py
@@ -34,10 +35,12 @@ setup.py
34
35
  ./bec_widgets/utils/bec_dispatcher.py
35
36
  ./bec_widgets/utils/bec_table.py
36
37
  ./bec_widgets/utils/colors.py
38
+ ./bec_widgets/utils/container_utils.py
37
39
  ./bec_widgets/utils/crosshair.py
38
40
  ./bec_widgets/utils/ctrl_c.py
39
41
  ./bec_widgets/utils/entry_validator.py
40
42
  ./bec_widgets/utils/rpc_decorator.py
43
+ ./bec_widgets/utils/thread_checker.py
41
44
  ./bec_widgets/utils/validator_delegate.py
42
45
  ./bec_widgets/utils/widget_io.py
43
46
  ./bec_widgets/utils/yaml_dialog.py
@@ -1,7 +1,7 @@
1
1
  # pylint: disable= missing-module-docstring
2
2
  from setuptools import find_packages, setup
3
3
 
4
- __version__ = "0.46.7"
4
+ __version__ = "0.48.0"
5
5
 
6
6
  # Default to PyQt6 if no other Qt binding is installed
7
7
  QT_DEPENDENCY = "PyQt6>=6.0"
@@ -1 +0,0 @@
1
- from .client import BECFigure
File without changes
File without changes
File without changes