bec-widgets 1.25.0__py3-none-any.whl → 2.0.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.
- .gitlab-ci.yml +11 -6
- CHANGELOG.md +650 -0
- PKG-INFO +3 -3
- bec_widgets/__init__.py +4 -0
- bec_widgets/applications/bw_launch.py +23 -0
- bec_widgets/applications/launch_window.py +430 -0
- bec_widgets/assets/app_icons/auto_update.png +0 -0
- bec_widgets/assets/app_icons/ui_loader_tile.png +0 -0
- bec_widgets/cli/__init__.py +0 -1
- bec_widgets/cli/client.py +1779 -2064
- bec_widgets/cli/client_utils.py +346 -174
- bec_widgets/cli/generate_cli.py +143 -37
- bec_widgets/cli/rpc/rpc_base.py +152 -21
- bec_widgets/cli/rpc/rpc_register.py +113 -6
- bec_widgets/cli/rpc/rpc_widget_handler.py +13 -11
- bec_widgets/cli/server.py +125 -239
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +97 -145
- bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py +1 -1
- bec_widgets/utils/bec_connector.py +190 -21
- bec_widgets/utils/bec_designer.py +7 -0
- bec_widgets/utils/bec_dispatcher.py +71 -4
- bec_widgets/utils/bec_plugin_helper.py +89 -0
- bec_widgets/utils/bec_signal_proxy.py +1 -1
- bec_widgets/utils/bec_widget.py +26 -10
- bec_widgets/utils/colors.py +1 -1
- bec_widgets/{qt_utils → utils}/compact_popup.py +2 -0
- bec_widgets/utils/container_utils.py +37 -12
- bec_widgets/utils/crosshair.py +25 -8
- bec_widgets/utils/entry_validator.py +3 -1
- bec_widgets/{qt_utils → utils}/error_popups.py +18 -0
- bec_widgets/{qt_utils → utils}/expandable_frame.py +2 -2
- bec_widgets/utils/forms_from_types/forms.py +182 -0
- bec_widgets/{widgets/editors/scan_metadata/_metadata_widgets.py → utils/forms_from_types/items.py} +41 -30
- bec_widgets/utils/generate_designer_plugin.py +40 -36
- bec_widgets/utils/linear_region_selector.py +2 -0
- bec_widgets/utils/name_utils.py +16 -0
- bec_widgets/{qt_utils → utils}/palette_viewer.py +2 -2
- bec_widgets/utils/plot_indicator_items.py +2 -5
- bec_widgets/utils/plugin_utils.py +47 -1
- bec_widgets/{qt_utils → utils}/round_frame.py +14 -14
- bec_widgets/utils/rpc_server.py +277 -0
- bec_widgets/utils/serialization.py +44 -0
- bec_widgets/{qt_utils → utils}/settings_dialog.py +26 -1
- bec_widgets/{qt_utils → utils}/side_panel.py +17 -10
- bec_widgets/{qt_utils → utils}/toolbar.py +69 -25
- bec_widgets/utils/ui_loader.py +8 -8
- bec_widgets/utils/widget_io.py +166 -25
- bec_widgets/widgets/containers/auto_update/auto_updates.py +364 -0
- bec_widgets/widgets/containers/dock/dock.py +157 -49
- bec_widgets/widgets/containers/dock/dock_area.py +186 -138
- bec_widgets/widgets/containers/layout_manager/layout_manager.py +2 -1
- bec_widgets/widgets/containers/main_window/addons/web_links.py +15 -0
- bec_widgets/widgets/containers/main_window/main_window.py +189 -41
- bec_widgets/widgets/control/buttons/button_abort/button_abort.py +3 -4
- bec_widgets/widgets/control/buttons/button_reset/button_reset.py +3 -4
- bec_widgets/widgets/control/buttons/button_resume/button_resume.py +3 -3
- bec_widgets/widgets/control/buttons/stop_button/stop_button.py +18 -7
- bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py +22 -3
- bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py +37 -18
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py +28 -4
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.ui +27 -4
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py +5 -2
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.ui +97 -31
- bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui +11 -4
- bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py +2 -3
- bec_widgets/widgets/control/device_input/base_classes/device_input_base.py +29 -4
- bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +1 -0
- bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +2 -2
- bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py +2 -2
- bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py +1 -2
- bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py +1 -2
- bec_widgets/widgets/control/scan_control/scan_control.py +7 -5
- bec_widgets/widgets/control/scan_control/scan_group_box.py +28 -5
- bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py +1 -2
- bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py +3 -4
- bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog_vertical.ui +14 -8
- bec_widgets/widgets/editors/console/console.py +1 -1
- bec_widgets/widgets/editors/{scan_metadata/additional_metadata_table.py → dict_backed_table.py} +29 -6
- bec_widgets/widgets/editors/scan_metadata/__init__.py +0 -7
- bec_widgets/widgets/editors/scan_metadata/_util.py +1 -1
- bec_widgets/widgets/{plots/motor_map/register_bec_motor_map_widget.py → editors/scan_metadata/register_scan_metadata.py} +2 -4
- bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +42 -136
- bec_widgets/widgets/editors/scan_metadata/scan_metadata.pyproject +1 -0
- bec_widgets/widgets/{plots/multi_waveform/bec_multi_waveform_widget_plugin.py → editors/scan_metadata/scan_metadata_plugin.py} +9 -9
- bec_widgets/widgets/editors/text_box/text_box.py +2 -3
- bec_widgets/widgets/editors/website/website.py +2 -2
- bec_widgets/widgets/games/minesweeper.py +3 -2
- bec_widgets/widgets/plots/image/image.py +960 -0
- bec_widgets/widgets/plots/image/image.pyproject +1 -0
- bec_widgets/widgets/plots/image/image_item.py +279 -0
- bec_widgets/widgets/plots/{motor_map/bec_motor_map_widget_plugin.py → image/image_plugin.py} +11 -13
- bec_widgets/widgets/{containers/figure/plots → plots}/image/image_processor.py +31 -64
- bec_widgets/widgets/plots/image/{register_bec_image_widget.py → register_image.py} +2 -2
- bec_widgets/widgets/plots/image/toolbar_bundles/image_selection.py +59 -0
- bec_widgets/widgets/plots/image/toolbar_bundles/processing.py +79 -0
- bec_widgets/widgets/plots/motor_map/motor_map.py +832 -0
- bec_widgets/widgets/plots/motor_map/motor_map.pyproject +1 -0
- bec_widgets/widgets/plots/motor_map/motor_map_plugin.py +54 -0
- bec_widgets/widgets/plots/{multi_waveform/register_bec_multi_waveform_widget.py → motor_map/register_motor_map.py} +2 -4
- bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py +129 -0
- bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui +120 -0
- bec_widgets/widgets/plots/motor_map/toolbar_bundles/motor_selection.py +70 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform.py +508 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform.pyproject +1 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_plugin.py +54 -0
- bec_widgets/widgets/plots/multi_waveform/register_multi_waveform.py +15 -0
- bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py +144 -0
- bec_widgets/widgets/plots/multi_waveform/settings/multi_waveform_controls.ui +164 -0
- bec_widgets/widgets/plots/multi_waveform/toolbar_bundles/monitor_selection.py +65 -0
- bec_widgets/widgets/{plots_next_gen → plots}/plot_base.py +321 -40
- bec_widgets/widgets/plots/{waveform/register_bec_waveform_widget.py → scatter_waveform/register_scatter_waveform.py} +3 -3
- bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py +197 -0
- bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py +553 -0
- bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.pyproject +1 -0
- bec_widgets/widgets/plots/{image/bec_image_widget_plugin.py → scatter_waveform/scatter_waveform_plugin.py} +9 -13
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py +138 -0
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_horizontal.ui +195 -0
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_vertical.ui +204 -0
- bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings.py +8 -8
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/mouse_interactions.py +4 -18
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/plot_export.py +14 -3
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/roi_bundle.py +6 -1
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/save_state.py +2 -2
- bec_widgets/widgets/{containers/figure/plots/waveform/waveform_curve.py → plots/waveform/curve.py} +119 -49
- bec_widgets/widgets/plots/waveform/register_waveform.py +15 -0
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_setting.py +125 -0
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +576 -0
- bec_widgets/widgets/plots/waveform/utils/__init__.py +0 -0
- bec_widgets/widgets/plots/waveform/utils/roi_manager.py +84 -0
- bec_widgets/widgets/plots/waveform/waveform.py +1794 -0
- bec_widgets/widgets/plots/waveform/waveform.pyproject +1 -0
- bec_widgets/widgets/plots/waveform/{bec_waveform_widget_plugin.py → waveform_plugin.py} +9 -13
- bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py +1 -2
- bec_widgets/widgets/progress/ring_progress_bar/ring.py +11 -10
- bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py +24 -14
- bec_widgets/widgets/services/bec_queue/bec_queue.py +13 -11
- bec_widgets/widgets/services/bec_status_box/bec_status_box.py +3 -4
- bec_widgets/widgets/services/device_browser/device_browser.py +5 -2
- bec_widgets/widgets/services/device_browser/device_item/device_item.py +1 -1
- bec_widgets/widgets/utility/logpanel/logpanel.py +36 -17
- bec_widgets/widgets/utility/spinbox/decimal_spinbox.py +3 -3
- bec_widgets/widgets/utility/spinner/spinner.py +2 -2
- bec_widgets/widgets/utility/visual/color_button/color_button.py +1 -1
- bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py +4 -6
- bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py +4 -8
- {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/METADATA +3 -3
- {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/RECORD +169 -154
- pyproject.toml +3 -3
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +0 -198
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +0 -615
- bec_widgets/applications/bec_app.py +0 -84
- bec_widgets/cli/auto_updates.py +0 -168
- bec_widgets/widgets/containers/figure/__init__.py +0 -1
- bec_widgets/widgets/containers/figure/figure.py +0 -796
- bec_widgets/widgets/containers/figure/plots/axis_settings.py +0 -91
- bec_widgets/widgets/containers/figure/plots/axis_settings.ui +0 -256
- bec_widgets/widgets/containers/figure/plots/image/image.py +0 -772
- bec_widgets/widgets/containers/figure/plots/image/image_item.py +0 -337
- bec_widgets/widgets/containers/figure/plots/motor_map/motor_map.py +0 -525
- bec_widgets/widgets/containers/figure/plots/multi_waveform/multi_waveform.py +0 -340
- bec_widgets/widgets/containers/figure/plots/plot_base.py +0 -505
- bec_widgets/widgets/containers/figure/plots/waveform/waveform.py +0 -1563
- bec_widgets/widgets/plots/image/bec_image_widget.pyproject +0 -1
- bec_widgets/widgets/plots/image/image_widget.py +0 -515
- bec_widgets/widgets/plots/motor_map/bec_motor_map_widget.pyproject +0 -1
- bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.py +0 -56
- bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.ui +0 -108
- bec_widgets/widgets/plots/motor_map/motor_map_widget.py +0 -234
- bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject +0 -1
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui +0 -99
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py +0 -536
- bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject +0 -1
- bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py +0 -336
- bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui +0 -372
- bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py +0 -25
- bec_widgets/widgets/plots/waveform/waveform_widget.py +0 -751
- /bec_widgets/{qt_utils → utils}/collapsible_panel_manager.py +0 -0
- /bec_widgets/{applications/alignment → utils/forms_from_types}/__init__.py +0 -0
- /bec_widgets/{qt_utils → utils}/redis_message_waiter.py +0 -0
- /bec_widgets/{applications/alignment/alignment_1d → widgets/containers/auto_update}/__init__.py +0 -0
- /bec_widgets/{qt_utils → widgets/containers/main_window/addons}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots → plots/image/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/image → plots/motor_map/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/motor_map → plots/motor_map/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/multi_waveform → plots/multi_waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/waveform → plots/multi_waveform/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{motor_map/motor_map_dialog → scatter_waveform}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups → scatter_waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups/curve_dialog → setting_menus}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_horizontal.ui +0 -0
- /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_vertical.ui +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups/dap_summary_dialog → toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen/setting_menus → plots/waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen/toolbar_bundles → plots/waveform/settings/curve_settings}/__init__.py +0 -0
- {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/WHEEL +0 -0
- {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-1.25.0.dist-info → bec_widgets-2.0.0.dist-info}/licenses/LICENSE +0 -0
bec_widgets/cli/generate_cli.py
CHANGED
@@ -2,17 +2,22 @@
|
|
2
2
|
from __future__ import annotations
|
3
3
|
|
4
4
|
import argparse
|
5
|
+
import importlib
|
5
6
|
import inspect
|
6
7
|
import os
|
7
8
|
import sys
|
9
|
+
from pathlib import Path
|
8
10
|
|
9
11
|
import black
|
10
12
|
import isort
|
13
|
+
from bec_lib.logger import bec_logger
|
11
14
|
from qtpy.QtCore import Property as QtProperty
|
12
15
|
|
13
|
-
from bec_widgets.utils.generate_designer_plugin import DesignerPluginGenerator
|
16
|
+
from bec_widgets.utils.generate_designer_plugin import DesignerPluginGenerator, plugin_filenames
|
14
17
|
from bec_widgets.utils.plugin_utils import BECClassContainer, get_custom_classes
|
15
18
|
|
19
|
+
logger = bec_logger.logger
|
20
|
+
|
16
21
|
if sys.version_info >= (3, 11):
|
17
22
|
from typing import get_overloads
|
18
23
|
else:
|
@@ -29,13 +34,28 @@ else:
|
|
29
34
|
|
30
35
|
|
31
36
|
class ClientGenerator:
|
32
|
-
def __init__(self):
|
33
|
-
self.
|
37
|
+
def __init__(self, base=False):
|
38
|
+
self._base = base
|
39
|
+
base_imports = (
|
40
|
+
"""import enum
|
41
|
+
import inspect
|
42
|
+
import traceback
|
43
|
+
from functools import reduce
|
44
|
+
from typing import Literal, Optional
|
45
|
+
"""
|
46
|
+
if self._base
|
47
|
+
else "\n"
|
48
|
+
)
|
49
|
+
self.header = f"""# This file was automatically generated by generate_cli.py
|
50
|
+
# type: ignore \n
|
34
51
|
from __future__ import annotations
|
35
|
-
|
36
|
-
from
|
52
|
+
{base_imports}
|
53
|
+
from bec_lib.logger import bec_logger
|
37
54
|
|
38
55
|
from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call
|
56
|
+
{"from bec_widgets.utils.bec_plugin_helper import get_all_plugin_widgets, get_plugin_client_module" if self._base else ""}
|
57
|
+
|
58
|
+
logger = bec_logger.logger
|
39
59
|
|
40
60
|
# pylint: skip-file"""
|
41
61
|
|
@@ -62,6 +82,7 @@ from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call
|
|
62
82
|
|
63
83
|
self.write_client_enum(rpc_top_level_classes)
|
64
84
|
for cls in connector_classes:
|
85
|
+
logger.debug(f"generating RPC client class for {cls.__name__}")
|
65
86
|
self.content += "\n\n"
|
66
87
|
self.generate_content_for_class(cls)
|
67
88
|
|
@@ -69,14 +90,50 @@ from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call
|
|
69
90
|
"""
|
70
91
|
Write the client enum to the content.
|
71
92
|
"""
|
93
|
+
if self._base:
|
94
|
+
self.content += """
|
95
|
+
class _WidgetsEnumType(str, enum.Enum):
|
96
|
+
\"\"\" Enum for the available widgets, to be generated programatically \"\"\"
|
97
|
+
...
|
98
|
+
"""
|
99
|
+
|
72
100
|
self.content += """
|
73
|
-
|
74
|
-
|
75
|
-
Enum for the available widgets.
|
76
|
-
\"\"\"
|
101
|
+
|
102
|
+
_Widgets = {
|
77
103
|
"""
|
78
104
|
for cls in published_classes:
|
79
|
-
self.content += f'{cls.__name__}
|
105
|
+
self.content += f'"{cls.__name__}": "{cls.__name__}",\n '
|
106
|
+
|
107
|
+
self.content += """}
|
108
|
+
"""
|
109
|
+
if self._base:
|
110
|
+
self.content += """
|
111
|
+
|
112
|
+
try:
|
113
|
+
_plugin_widgets = get_all_plugin_widgets()
|
114
|
+
plugin_client = get_plugin_client_module()
|
115
|
+
Widgets = _WidgetsEnumType("Widgets", {name: name for name in _plugin_widgets} | _Widgets)
|
116
|
+
|
117
|
+
if (_overlap := _Widgets.keys() & _plugin_widgets.keys()) != set():
|
118
|
+
for _widget in _overlap:
|
119
|
+
logger.warning(f"Detected duplicate widget {_widget} in plugin repo file: {inspect.getfile(_plugin_widgets[_widget])} !")
|
120
|
+
for plugin_name, plugin_class in inspect.getmembers(plugin_client, inspect.isclass):
|
121
|
+
if issubclass(plugin_class, RPCBase) and plugin_class is not RPCBase:
|
122
|
+
if plugin_name in globals():
|
123
|
+
conflicting_file = (
|
124
|
+
inspect.getfile(_plugin_widgets[plugin_name])
|
125
|
+
if plugin_name in _plugin_widgets
|
126
|
+
else f"{plugin_client}"
|
127
|
+
)
|
128
|
+
logger.warning(
|
129
|
+
f"Plugin widget {plugin_name} from {conflicting_file} conflicts with a built-in class!"
|
130
|
+
)
|
131
|
+
continue
|
132
|
+
if plugin_name not in _overlap:
|
133
|
+
globals()[plugin_name] = plugin_class
|
134
|
+
except ImportError as e:
|
135
|
+
logger.error(f"Failed loading plugins: \\n{reduce(add, traceback.format_exception(e))}")
|
136
|
+
"""
|
80
137
|
|
81
138
|
def generate_content_for_class(self, cls):
|
82
139
|
"""
|
@@ -95,9 +152,21 @@ class {class_name}(RPCBase):"""
|
|
95
152
|
self.content += f"""
|
96
153
|
class {class_name}(RPCBase):"""
|
97
154
|
|
155
|
+
if cls.__doc__:
|
156
|
+
# We only want the first line of the docstring
|
157
|
+
# But skip the first line if it's a blank line
|
158
|
+
first_line = cls.__doc__.split("\n")[0]
|
159
|
+
if first_line:
|
160
|
+
class_docs = first_line
|
161
|
+
else:
|
162
|
+
class_docs = cls.__doc__.split("\n")[1]
|
163
|
+
self.content += f"""
|
164
|
+
\"\"\"{class_docs}\"\"\"
|
165
|
+
"""
|
98
166
|
if not cls.USER_ACCESS:
|
99
167
|
self.content += """...
|
100
168
|
"""
|
169
|
+
|
101
170
|
for method in cls.USER_ACCESS:
|
102
171
|
is_property_setter = False
|
103
172
|
obj = getattr(cls, method, None)
|
@@ -176,41 +245,78 @@ def main():
|
|
176
245
|
"""
|
177
246
|
|
178
247
|
parser = argparse.ArgumentParser(description="Auto-generate the client for RPC widgets")
|
179
|
-
parser.add_argument(
|
248
|
+
parser.add_argument(
|
249
|
+
"--target",
|
250
|
+
action="store",
|
251
|
+
type=str,
|
252
|
+
help="Which package to generate plugin files for. Should be installed in the local environment (example: my_plugin_repo)",
|
253
|
+
)
|
180
254
|
|
181
255
|
args = parser.parse_args()
|
256
|
+
if args.target is None:
|
257
|
+
logger.error(
|
258
|
+
"You must provide a target - for safety, the default of running this on bec_widgets core has been removed. To generate the client for bec_widgets, run `bw-generate-cli --target bec_widgets`"
|
259
|
+
)
|
260
|
+
return
|
182
261
|
|
183
|
-
|
184
|
-
current_path = os.path.dirname(__file__)
|
185
|
-
client_path = os.path.join(current_path, "client.py")
|
262
|
+
logger.info(f"BEC Widget code generation tool started with args: {args}")
|
186
263
|
|
187
|
-
|
264
|
+
client_subdir = "cli" if args.target == "bec_widgets" else "widgets"
|
265
|
+
module_name = "bec_widgets" if args.target == "bec_widgets" else f"{args.target}.bec_widgets"
|
188
266
|
|
189
|
-
|
190
|
-
|
191
|
-
|
267
|
+
try:
|
268
|
+
module = importlib.import_module(module_name)
|
269
|
+
assert module.__file__ is not None
|
270
|
+
module_file = Path(module.__file__)
|
271
|
+
module_dir = module_file.parent if module_file.is_file() else module_file
|
272
|
+
except Exception as e:
|
273
|
+
logger.error(f"Failed to load module {module_name} for code generation: {e}")
|
274
|
+
return
|
192
275
|
|
193
|
-
|
194
|
-
plugin = DesignerPluginGenerator(cls)
|
195
|
-
if not hasattr(plugin, "info"):
|
196
|
-
continue
|
276
|
+
client_path = module_dir / client_subdir / "client.py"
|
197
277
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
278
|
+
rpc_classes = get_custom_classes(module_name)
|
279
|
+
logger.info(f"Obtained classes with RPC objects: {rpc_classes!r}")
|
280
|
+
|
281
|
+
generator = ClientGenerator(base=module_name == "bec_widgets")
|
282
|
+
logger.info(f"Generating client file at {client_path}")
|
283
|
+
generator.generate_client(rpc_classes)
|
284
|
+
generator.write(str(client_path))
|
285
|
+
|
286
|
+
if module_name != "bec_widgets":
|
287
|
+
non_overwrite_classes = list(clsinfo.name for clsinfo in get_custom_classes("bec_widgets"))
|
288
|
+
logger.info(
|
289
|
+
f"Not writing plugins which would conflict with builtin classes: {non_overwrite_classes}"
|
290
|
+
)
|
291
|
+
else:
|
292
|
+
non_overwrite_classes = []
|
293
|
+
|
294
|
+
for cls in rpc_classes.plugins:
|
295
|
+
logger.info(f"Writing bec-designer plugin files for {cls.__name__}...")
|
296
|
+
|
297
|
+
if cls.__name__ in non_overwrite_classes:
|
298
|
+
logger.error(
|
299
|
+
f"Not writing plugin files for {cls.__name__} because a built-in widget with that name exists"
|
300
|
+
)
|
301
|
+
|
302
|
+
plugin = DesignerPluginGenerator(cls)
|
303
|
+
if not hasattr(plugin, "info"):
|
304
|
+
continue
|
305
|
+
|
306
|
+
def _exists(file: str):
|
307
|
+
return os.path.exists(os.path.join(plugin.info.base_path, file))
|
308
|
+
|
309
|
+
if any(_exists(file) for file in plugin_filenames(plugin.info.plugin_name_snake)):
|
310
|
+
logger.debug(
|
311
|
+
f"Skipping generation of extra plugin files for {plugin.info.plugin_name_snake} - at least one file out of 'plugin.py', 'pyproject', and 'register_{plugin.info.plugin_name_snake}.py' already exists."
|
312
|
+
)
|
313
|
+
continue
|
314
|
+
|
315
|
+
plugin.run()
|
212
316
|
|
213
317
|
|
214
318
|
if __name__ == "__main__": # pragma: no cover
|
215
|
-
sys
|
319
|
+
import sys
|
320
|
+
|
321
|
+
sys.argv = ["bw-generate-cli", "--target", "csaxs_bec"]
|
216
322
|
main()
|
bec_widgets/cli/rpc/rpc_base.py
CHANGED
@@ -1,24 +1,28 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import inspect
|
3
4
|
import threading
|
4
5
|
import uuid
|
5
6
|
from functools import wraps
|
6
|
-
from typing import TYPE_CHECKING
|
7
|
+
from typing import TYPE_CHECKING, Any, cast
|
7
8
|
|
8
9
|
from bec_lib.client import BECClient
|
9
10
|
from bec_lib.endpoints import MessageEndpoints
|
10
11
|
from bec_lib.utils.import_utils import lazy_import, lazy_import_from
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
if TYPE_CHECKING:
|
13
|
+
if TYPE_CHECKING: # pragma: no cover
|
15
14
|
from bec_lib import messages
|
16
15
|
from bec_lib.connector import MessageObject
|
16
|
+
|
17
|
+
import bec_widgets.cli.client as client
|
18
|
+
from bec_widgets.cli.client_utils import BECGuiClient
|
17
19
|
else:
|
20
|
+
client = lazy_import("bec_widgets.cli.client") # avoid circular import
|
18
21
|
messages = lazy_import("bec_lib.messages")
|
19
|
-
# from bec_lib.connector import MessageObject
|
20
22
|
MessageObject = lazy_import_from("bec_lib.connector", ("MessageObject",))
|
21
23
|
|
24
|
+
# pylint: disable=protected-access
|
25
|
+
|
22
26
|
|
23
27
|
def rpc_call(func):
|
24
28
|
"""
|
@@ -35,6 +39,14 @@ def rpc_call(func):
|
|
35
39
|
def wrapper(self, *args, **kwargs):
|
36
40
|
# we could rely on a strict type check here, but this is more flexible
|
37
41
|
# moreover, it would anyway crash for objects...
|
42
|
+
caller_frame = inspect.currentframe().f_back # type: ignore
|
43
|
+
while caller_frame:
|
44
|
+
if "jedi" in caller_frame.f_globals:
|
45
|
+
# Jedi module is present, likely tab completion
|
46
|
+
# Do not run the RPC call
|
47
|
+
return None # func(*args, **kwargs)
|
48
|
+
caller_frame = caller_frame.f_back
|
49
|
+
|
38
50
|
out = []
|
39
51
|
for arg in args:
|
40
52
|
if hasattr(arg, "name"):
|
@@ -44,7 +56,7 @@ def rpc_call(func):
|
|
44
56
|
for key, val in kwargs.items():
|
45
57
|
if hasattr(val, "name"):
|
46
58
|
kwargs[key] = val.name
|
47
|
-
if not self.
|
59
|
+
if not self._root._gui_is_alive():
|
48
60
|
raise RuntimeError("GUI is not alive")
|
49
61
|
return self._run_rpc(func.__name__, *args, **kwargs)
|
50
62
|
|
@@ -60,24 +72,96 @@ class RPCResponseTimeoutError(Exception):
|
|
60
72
|
)
|
61
73
|
|
62
74
|
|
75
|
+
class DeletedWidgetError(Exception): ...
|
76
|
+
|
77
|
+
|
78
|
+
def check_for_deleted_widget(func):
|
79
|
+
@wraps(func)
|
80
|
+
def wrapper(self, *args, **kwargs):
|
81
|
+
if self._gui_id not in self._registry:
|
82
|
+
raise DeletedWidgetError(f"Widget with gui_id {self._gui_id} has been deleted")
|
83
|
+
return func(self, *args, **kwargs)
|
84
|
+
|
85
|
+
return wrapper
|
86
|
+
|
87
|
+
|
88
|
+
class RPCReference:
|
89
|
+
def __init__(self, registry: dict, gui_id: str) -> None:
|
90
|
+
self._registry = registry
|
91
|
+
self._gui_id = gui_id
|
92
|
+
self.object_name = self._registry[self._gui_id].object_name
|
93
|
+
|
94
|
+
@check_for_deleted_widget
|
95
|
+
def __getattr__(self, name):
|
96
|
+
if name in ["_registry", "_gui_id", "_is_deleted", "object_name"]:
|
97
|
+
return super().__getattribute__(name)
|
98
|
+
return self._registry[self._gui_id].__getattribute__(name)
|
99
|
+
|
100
|
+
def __setattr__(self, name, value):
|
101
|
+
if name in ["_registry", "_gui_id", "_is_deleted", "object_name"]:
|
102
|
+
return super().__setattr__(name, value)
|
103
|
+
if self._gui_id not in self._registry:
|
104
|
+
raise DeletedWidgetError(f"Widget with gui_id {self._gui_id} has been deleted")
|
105
|
+
self._registry[self._gui_id].__setattr__(name, value)
|
106
|
+
|
107
|
+
def __repr__(self):
|
108
|
+
if self._gui_id not in self._registry:
|
109
|
+
return f"<Deleted widget with gui_id {self._gui_id}>"
|
110
|
+
return self._registry[self._gui_id].__repr__()
|
111
|
+
|
112
|
+
def __str__(self):
|
113
|
+
if self._gui_id not in self._registry:
|
114
|
+
return f"<Deleted widget with gui_id {self._gui_id}>"
|
115
|
+
return self._registry[self._gui_id].__str__()
|
116
|
+
|
117
|
+
def __dir__(self):
|
118
|
+
if self._gui_id not in self._registry:
|
119
|
+
return []
|
120
|
+
return self._registry[self._gui_id].__dir__()
|
121
|
+
|
122
|
+
def _is_deleted(self) -> bool:
|
123
|
+
return self._gui_id not in self._registry
|
124
|
+
|
125
|
+
|
63
126
|
class RPCBase:
|
64
|
-
def __init__(
|
127
|
+
def __init__(
|
128
|
+
self,
|
129
|
+
gui_id: str | None = None,
|
130
|
+
config: dict | None = None,
|
131
|
+
object_name: str | None = None,
|
132
|
+
parent=None,
|
133
|
+
**kwargs,
|
134
|
+
) -> None:
|
65
135
|
self._client = BECClient() # BECClient is a singleton; here, we simply get the instance
|
66
136
|
self._config = config if config is not None else {}
|
67
137
|
self._gui_id = gui_id if gui_id is not None else str(uuid.uuid4())[:5]
|
138
|
+
self.object_name = object_name if object_name is not None else str(uuid.uuid4())[:5]
|
68
139
|
self._parent = parent
|
69
140
|
self._msg_wait_event = threading.Event()
|
70
141
|
self._rpc_response = None
|
71
142
|
super().__init__()
|
72
|
-
|
143
|
+
self._rpc_references: dict[str, str] = {}
|
73
144
|
|
74
145
|
def __repr__(self):
|
75
146
|
type_ = type(self)
|
76
147
|
qualname = type_.__qualname__
|
77
|
-
return f"<{qualname}
|
148
|
+
return f"<{qualname} with name: {self.object_name}>"
|
149
|
+
|
150
|
+
def remove(self):
|
151
|
+
"""
|
152
|
+
Remove the widget.
|
153
|
+
"""
|
154
|
+
obj = self._root._server_registry.get(self._gui_id)
|
155
|
+
if obj is None:
|
156
|
+
raise ValueError(f"Widget {self._gui_id} not found.")
|
157
|
+
if proxy := obj.get("container_proxy"):
|
158
|
+
assert isinstance(proxy, str)
|
159
|
+
self._run_rpc("remove", gui_id=proxy)
|
160
|
+
return
|
161
|
+
self._run_rpc("remove")
|
78
162
|
|
79
163
|
@property
|
80
|
-
def _root(self):
|
164
|
+
def _root(self) -> BECGuiClient:
|
81
165
|
"""
|
82
166
|
Get the root widget. This is the BECFigure widget that holds
|
83
167
|
the anchor gui_id.
|
@@ -86,9 +170,17 @@ class RPCBase:
|
|
86
170
|
# pylint: disable=protected-access
|
87
171
|
while parent._parent is not None:
|
88
172
|
parent = parent._parent
|
89
|
-
return parent
|
173
|
+
return parent # type: ignore
|
90
174
|
|
91
|
-
def _run_rpc(
|
175
|
+
def _run_rpc(
|
176
|
+
self,
|
177
|
+
method,
|
178
|
+
*args,
|
179
|
+
wait_for_rpc_response=True,
|
180
|
+
timeout=5,
|
181
|
+
gui_id: str | None = None,
|
182
|
+
**kwargs,
|
183
|
+
) -> Any:
|
92
184
|
"""
|
93
185
|
Run the RPC call.
|
94
186
|
|
@@ -96,6 +188,8 @@ class RPCBase:
|
|
96
188
|
method: The method to call.
|
97
189
|
args: The arguments to pass to the method.
|
98
190
|
wait_for_rpc_response: Whether to wait for the RPC response.
|
191
|
+
timeout: The timeout for the RPC response.
|
192
|
+
gui_id: The GUI ID to use for the RPC call. If None, the default GUI ID is used.
|
99
193
|
kwargs: The keyword arguments to pass to the method.
|
100
194
|
|
101
195
|
Returns:
|
@@ -104,10 +198,9 @@ class RPCBase:
|
|
104
198
|
request_id = str(uuid.uuid4())
|
105
199
|
rpc_msg = messages.GUIInstructionMessage(
|
106
200
|
action=method,
|
107
|
-
parameter={"args": args, "kwargs": kwargs, "gui_id": self._gui_id},
|
201
|
+
parameter={"args": args, "kwargs": kwargs, "gui_id": gui_id or self._gui_id},
|
108
202
|
metadata={"request_id": request_id},
|
109
203
|
)
|
110
|
-
|
111
204
|
# pylint: disable=protected-access
|
112
205
|
receiver = self._root._gui_id
|
113
206
|
if wait_for_rpc_response:
|
@@ -131,7 +224,11 @@ class RPCBase:
|
|
131
224
|
self._client.connector.unregister(
|
132
225
|
MessageEndpoints.gui_instruction_response(request_id), cb=self._on_rpc_response
|
133
226
|
)
|
134
|
-
|
227
|
+
|
228
|
+
# we can assume that the response is a RequestResponseMessage, updated by
|
229
|
+
# the _on_rpc_response method
|
230
|
+
assert isinstance(self._rpc_response, messages.RequestResponseMessage)
|
231
|
+
|
135
232
|
if not self._rpc_response.accepted:
|
136
233
|
raise ValueError(self._rpc_response.message["error"])
|
137
234
|
msg_result = self._rpc_response.message.get("result")
|
@@ -139,10 +236,10 @@ class RPCBase:
|
|
139
236
|
return self._create_widget_from_msg_result(msg_result)
|
140
237
|
|
141
238
|
@staticmethod
|
142
|
-
def _on_rpc_response(
|
143
|
-
msg =
|
144
|
-
parent._msg_wait_event.set()
|
239
|
+
def _on_rpc_response(msg_obj: MessageObject, parent: RPCBase) -> None:
|
240
|
+
msg = cast(messages.RequestResponseMessage, msg_obj.value)
|
145
241
|
parent._rpc_response = msg
|
242
|
+
parent._msg_wait_event.set()
|
146
243
|
|
147
244
|
def _create_widget_from_msg_result(self, msg_result):
|
148
245
|
if msg_result is None:
|
@@ -161,11 +258,21 @@ class RPCBase:
|
|
161
258
|
return msg_result
|
162
259
|
|
163
260
|
cls = getattr(client, cls)
|
164
|
-
#
|
165
|
-
|
261
|
+
# The namespace of the object will be updated dynamically on the client side
|
262
|
+
# Therefore it is important to check if the object is already in the registry
|
263
|
+
# If yes, we return the reference to the object, otherwise we create a new object
|
264
|
+
# pylint: disable=protected-access
|
265
|
+
if msg_result["gui_id"] in self._root._ipython_registry:
|
266
|
+
return RPCReference(self._root._ipython_registry, msg_result["gui_id"])
|
267
|
+
ret = cls(parent=self, **msg_result)
|
268
|
+
self._root._ipython_registry[ret._gui_id] = ret
|
269
|
+
self._refresh_references()
|
270
|
+
obj = RPCReference(self._root._ipython_registry, ret._gui_id)
|
271
|
+
return obj
|
272
|
+
# return ret
|
166
273
|
return msg_result
|
167
274
|
|
168
|
-
def
|
275
|
+
def _gui_is_alive(self):
|
169
276
|
"""
|
170
277
|
Check if the GUI is alive.
|
171
278
|
"""
|
@@ -175,3 +282,27 @@ class RPCBase:
|
|
175
282
|
if heart.status == messages.BECStatus.RUNNING:
|
176
283
|
return True
|
177
284
|
return False
|
285
|
+
|
286
|
+
def _refresh_references(self):
|
287
|
+
"""
|
288
|
+
Refresh the references.
|
289
|
+
"""
|
290
|
+
with self._root._lock:
|
291
|
+
references = {}
|
292
|
+
for key, val in self._root._server_registry.items():
|
293
|
+
parent_id = val["config"].get("parent_id")
|
294
|
+
if parent_id == self._gui_id:
|
295
|
+
references[key] = {
|
296
|
+
"gui_id": val["config"]["gui_id"],
|
297
|
+
"object_name": val["object_name"],
|
298
|
+
}
|
299
|
+
removed_references = set(self._rpc_references.keys()) - set(references.keys())
|
300
|
+
for key in removed_references:
|
301
|
+
delattr(self, self._rpc_references[key]["object_name"])
|
302
|
+
self._rpc_references = references
|
303
|
+
for key, val in references.items():
|
304
|
+
setattr(
|
305
|
+
self,
|
306
|
+
val["object_name"],
|
307
|
+
RPCReference(self._root._ipython_registry, val["gui_id"]),
|
308
|
+
)
|