bec-widgets 1.20.0__py3-none-any.whl → 1.21.1__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 +16 -0
- PKG-INFO +2 -2
- bec_widgets/utils/bec_connector.py +54 -19
- bec_widgets/widgets/editors/scan_metadata/__init__.py +7 -0
- bec_widgets/widgets/editors/scan_metadata/_metadata_widgets.py +275 -0
- bec_widgets/widgets/editors/scan_metadata/_util.py +67 -0
- bec_widgets/widgets/editors/scan_metadata/additional_metadata_table.py +146 -0
- bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +196 -0
- {bec_widgets-1.20.0.dist-info → bec_widgets-1.21.1.dist-info}/METADATA +2 -2
- {bec_widgets-1.20.0.dist-info → bec_widgets-1.21.1.dist-info}/RECORD +14 -9
- pyproject.toml +2 -2
- {bec_widgets-1.20.0.dist-info → bec_widgets-1.21.1.dist-info}/WHEEL +0 -0
- {bec_widgets-1.20.0.dist-info → bec_widgets-1.21.1.dist-info}/entry_points.txt +0 -0
- {bec_widgets-1.20.0.dist-info → bec_widgets-1.21.1.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
3
|
|
4
|
+
## v1.21.1 (2025-02-17)
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
- **bec_connector**: Workers stored in reference to not be cleaned up with garbage collector
|
9
|
+
([`383936f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/383936ffc2bd7d2e088d3367c76b14efa3d1732c))
|
10
|
+
|
11
|
+
|
12
|
+
## v1.21.0 (2025-02-17)
|
13
|
+
|
14
|
+
### Features
|
15
|
+
|
16
|
+
- Generated form for scan metadata
|
17
|
+
([`1708bd4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1708bd405f86b1353828b01fbf5f98383a19ec2a))
|
18
|
+
|
19
|
+
|
4
20
|
## v1.20.0 (2025-02-06)
|
5
21
|
|
6
22
|
### Features
|
PKG-INFO
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bec_widgets
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.21.1
|
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
|
@@ -16,7 +16,7 @@ Requires-Dist: black~=24.0
|
|
16
16
|
Requires-Dist: isort>=5.13.2,~=5.13
|
17
17
|
Requires-Dist: pydantic~=2.0
|
18
18
|
Requires-Dist: pyqtgraph~=0.13
|
19
|
-
Requires-Dist: pyside6
|
19
|
+
Requires-Dist: pyside6>=6.8
|
20
20
|
Requires-Dist: pyte
|
21
21
|
Requires-Dist: qtconsole>=5.5.1,~=5.5
|
22
22
|
Requires-Dist: qtpy~=2.4
|
@@ -111,7 +111,7 @@ class BECConnector:
|
|
111
111
|
|
112
112
|
# register widget to rpc register
|
113
113
|
# be careful: when registering, and the object is not a BECWidget,
|
114
|
-
# cleanup has to called manually since there is no 'closeEvent'
|
114
|
+
# cleanup has to be called manually since there is no 'closeEvent'
|
115
115
|
self.rpc_register = RPCRegister()
|
116
116
|
self.rpc_register.add_rpc(self)
|
117
117
|
|
@@ -119,6 +119,8 @@ class BECConnector:
|
|
119
119
|
self.error_utility = ErrorPopupUtility()
|
120
120
|
|
121
121
|
self._thread_pool = QThreadPool.globalInstance()
|
122
|
+
# Store references to running workers so they're not garbage collected prematurely.
|
123
|
+
self._workers = []
|
122
124
|
|
123
125
|
def submit_task(self, fn, *args, on_complete: pyqtSlot = None, **kwargs) -> Worker:
|
124
126
|
"""
|
@@ -147,11 +149,14 @@ class BECConnector:
|
|
147
149
|
>>> def on_complete():
|
148
150
|
>>> print("Task complete")
|
149
151
|
>>> self.submit_task(my_function, 1, 2, on_complete=on_complete)
|
150
|
-
|
151
152
|
"""
|
152
153
|
worker = Worker(fn, *args, **kwargs)
|
153
154
|
if on_complete:
|
154
155
|
worker.signals.completed.connect(on_complete)
|
156
|
+
# Keep a reference to the worker so it is not garbage collected.
|
157
|
+
self._workers.append(worker)
|
158
|
+
# When the worker is done, remove it from our list.
|
159
|
+
worker.signals.completed.connect(lambda: self._workers.remove(worker))
|
155
160
|
self._thread_pool.start(worker)
|
156
161
|
return worker
|
157
162
|
|
@@ -183,10 +188,10 @@ class BECConnector:
|
|
183
188
|
@_config_dict.setter
|
184
189
|
def _config_dict(self, config: BaseModel) -> None:
|
185
190
|
"""
|
186
|
-
|
191
|
+
Set the configuration of the widget.
|
187
192
|
|
188
|
-
|
189
|
-
|
193
|
+
Args:
|
194
|
+
config (BaseModel): The new configuration model.
|
190
195
|
"""
|
191
196
|
self.config = config
|
192
197
|
|
@@ -195,8 +200,8 @@ class BECConnector:
|
|
195
200
|
Apply the configuration to the widget.
|
196
201
|
|
197
202
|
Args:
|
198
|
-
config(dict): Configuration settings.
|
199
|
-
generate_new_id(bool): If True, generate a new GUI ID for the widget.
|
203
|
+
config (dict): Configuration settings.
|
204
|
+
generate_new_id (bool): If True, generate a new GUI ID for the widget.
|
200
205
|
"""
|
201
206
|
self.config = ConnectionConfig(**config)
|
202
207
|
if generate_new_id is True:
|
@@ -212,8 +217,8 @@ class BECConnector:
|
|
212
217
|
Load the configuration of the widget from YAML.
|
213
218
|
|
214
219
|
Args:
|
215
|
-
path(str): Path to the configuration file for non-GUI dialog mode.
|
216
|
-
gui(bool): If True, use the GUI dialog to load the configuration file.
|
220
|
+
path (str | None): Path to the configuration file for non-GUI dialog mode.
|
221
|
+
gui (bool): If True, use the GUI dialog to load the configuration file.
|
217
222
|
"""
|
218
223
|
if gui is True:
|
219
224
|
config = load_yaml_gui(self)
|
@@ -232,8 +237,8 @@ class BECConnector:
|
|
232
237
|
Save the configuration of the widget to YAML.
|
233
238
|
|
234
239
|
Args:
|
235
|
-
path(str): Path to save the configuration file for non-GUI dialog mode.
|
236
|
-
gui(bool): If True, use the GUI dialog to save the configuration file.
|
240
|
+
path (str | None): Path to save the configuration file for non-GUI dialog mode.
|
241
|
+
gui (bool): If True, use the GUI dialog to save the configuration file.
|
237
242
|
"""
|
238
243
|
if gui is True:
|
239
244
|
save_yaml_gui(self, self._config_dict)
|
@@ -241,7 +246,6 @@ class BECConnector:
|
|
241
246
|
if path is None:
|
242
247
|
path = os.getcwd()
|
243
248
|
file_path = os.path.join(path, f"{self.__class__.__name__}_config.yaml")
|
244
|
-
|
245
249
|
save_yaml(file_path, self._config_dict)
|
246
250
|
|
247
251
|
@pyqtSlot(str)
|
@@ -250,7 +254,7 @@ class BECConnector:
|
|
250
254
|
Set the GUI ID for the widget.
|
251
255
|
|
252
256
|
Args:
|
253
|
-
gui_id(str): GUI ID
|
257
|
+
gui_id (str): GUI ID.
|
254
258
|
"""
|
255
259
|
self.config.gui_id = gui_id
|
256
260
|
self.gui_id = gui_id
|
@@ -271,7 +275,7 @@ class BECConnector:
|
|
271
275
|
"""Update the client and device manager from BEC and create object for BEC shortcuts.
|
272
276
|
|
273
277
|
Args:
|
274
|
-
client: BEC client
|
278
|
+
client: BEC client.
|
275
279
|
"""
|
276
280
|
self.client = client
|
277
281
|
self.get_bec_shortcuts()
|
@@ -282,12 +286,10 @@ class BECConnector:
|
|
282
286
|
Update the configuration for the widget.
|
283
287
|
|
284
288
|
Args:
|
285
|
-
config(ConnectionConfig): Configuration settings.
|
289
|
+
config (ConnectionConfig | dict): Configuration settings.
|
286
290
|
"""
|
287
291
|
if isinstance(config, dict):
|
288
292
|
config = ConnectionConfig(**config)
|
289
|
-
# TODO add error handler
|
290
|
-
|
291
293
|
self.config = config
|
292
294
|
|
293
295
|
def get_config(self, dict_output: bool = True) -> dict | BaseModel:
|
@@ -295,12 +297,45 @@ class BECConnector:
|
|
295
297
|
Get the configuration of the widget.
|
296
298
|
|
297
299
|
Args:
|
298
|
-
dict_output(bool): If True, return the configuration as a dictionary.
|
300
|
+
dict_output (bool): If True, return the configuration as a dictionary.
|
301
|
+
If False, return the configuration as a pydantic model.
|
299
302
|
|
300
303
|
Returns:
|
301
|
-
dict: The configuration of the
|
304
|
+
dict | BaseModel: The configuration of the widget.
|
302
305
|
"""
|
303
306
|
if dict_output:
|
304
307
|
return self.config.model_dump()
|
305
308
|
else:
|
306
309
|
return self.config
|
310
|
+
|
311
|
+
|
312
|
+
# --- Example usage of BECConnector: running a simple task ---
|
313
|
+
if __name__ == "__main__": # pragma: no cover
|
314
|
+
import sys
|
315
|
+
|
316
|
+
# Create a QApplication instance (required for QThreadPool)
|
317
|
+
app = QApplication(sys.argv)
|
318
|
+
|
319
|
+
connector = BECConnector()
|
320
|
+
|
321
|
+
def print_numbers():
|
322
|
+
"""
|
323
|
+
Task function that prints numbers 1 to 10 with a 0.5 second delay between each.
|
324
|
+
"""
|
325
|
+
for i in range(1, 11):
|
326
|
+
print(i)
|
327
|
+
time.sleep(0.5)
|
328
|
+
|
329
|
+
def task_complete():
|
330
|
+
"""
|
331
|
+
Called when the task is complete.
|
332
|
+
"""
|
333
|
+
print("Task complete")
|
334
|
+
# Exit the application after the task completes.
|
335
|
+
app.quit()
|
336
|
+
|
337
|
+
# Submit the task using the connector's submit_task method.
|
338
|
+
connector.submit_task(print_numbers, on_complete=task_complete)
|
339
|
+
|
340
|
+
# Start the Qt event loop.
|
341
|
+
sys.exit(app.exec_())
|
@@ -0,0 +1,7 @@
|
|
1
|
+
from bec_widgets.widgets.editors.scan_metadata.additional_metadata_table import (
|
2
|
+
AdditionalMetadataTable,
|
3
|
+
AdditionalMetadataTableModel,
|
4
|
+
)
|
5
|
+
from bec_widgets.widgets.editors.scan_metadata.scan_metadata import ScanMetadata
|
6
|
+
|
7
|
+
__all__ = ["ScanMetadata", "AdditionalMetadataTable", "AdditionalMetadataTableModel"]
|
@@ -0,0 +1,275 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import abstractmethod
|
4
|
+
from decimal import Decimal
|
5
|
+
from typing import TYPE_CHECKING, Callable, get_args
|
6
|
+
|
7
|
+
from bec_lib.logger import bec_logger
|
8
|
+
from bec_qthemes import material_icon
|
9
|
+
from pydantic import BaseModel, Field
|
10
|
+
from qtpy.QtCore import Signal # type: ignore
|
11
|
+
from qtpy.QtWidgets import (
|
12
|
+
QApplication,
|
13
|
+
QButtonGroup,
|
14
|
+
QCheckBox,
|
15
|
+
QDoubleSpinBox,
|
16
|
+
QGridLayout,
|
17
|
+
QHBoxLayout,
|
18
|
+
QLabel,
|
19
|
+
QLayout,
|
20
|
+
QLineEdit,
|
21
|
+
QRadioButton,
|
22
|
+
QSpinBox,
|
23
|
+
QToolButton,
|
24
|
+
QWidget,
|
25
|
+
)
|
26
|
+
|
27
|
+
from bec_widgets.widgets.editors.scan_metadata._util import (
|
28
|
+
clearable_required,
|
29
|
+
field_default,
|
30
|
+
field_limits,
|
31
|
+
field_maxlen,
|
32
|
+
field_minlen,
|
33
|
+
field_precision,
|
34
|
+
)
|
35
|
+
|
36
|
+
if TYPE_CHECKING:
|
37
|
+
from pydantic.fields import FieldInfo
|
38
|
+
|
39
|
+
logger = bec_logger.logger
|
40
|
+
|
41
|
+
|
42
|
+
class ClearableBoolEntry(QWidget):
|
43
|
+
stateChanged = Signal()
|
44
|
+
|
45
|
+
def __init__(self, parent: QWidget | None = None) -> None:
|
46
|
+
super().__init__(parent)
|
47
|
+
self._layout = QHBoxLayout()
|
48
|
+
self._layout.setContentsMargins(0, 0, 0, 0)
|
49
|
+
self.setLayout(self._layout)
|
50
|
+
self._layout.setSizeConstraint(QLayout.SizeConstraint.SetFixedSize)
|
51
|
+
self._entry = QButtonGroup()
|
52
|
+
self._true = QRadioButton("true", parent=self)
|
53
|
+
self._false = QRadioButton("false", parent=self)
|
54
|
+
for button in [self._true, self._false]:
|
55
|
+
self._layout.addWidget(button)
|
56
|
+
self._entry.addButton(button)
|
57
|
+
button.toggled.connect(self.stateChanged)
|
58
|
+
|
59
|
+
def clear(self):
|
60
|
+
self._entry.setExclusive(False)
|
61
|
+
self._true.setChecked(False)
|
62
|
+
self._false.setChecked(False)
|
63
|
+
self._entry.setExclusive(True)
|
64
|
+
|
65
|
+
def isChecked(self) -> bool | None:
|
66
|
+
if not self._true.isChecked() and not self._false.isChecked():
|
67
|
+
return None
|
68
|
+
return self._true.isChecked()
|
69
|
+
|
70
|
+
def setChecked(self, value: bool | None):
|
71
|
+
if value is None:
|
72
|
+
self.clear()
|
73
|
+
elif value:
|
74
|
+
self._true.setChecked(True)
|
75
|
+
self._false.setChecked(False)
|
76
|
+
else:
|
77
|
+
self._true.setChecked(False)
|
78
|
+
self._false.setChecked(True)
|
79
|
+
|
80
|
+
def setToolTip(self, tooltip: str):
|
81
|
+
self._true.setToolTip(tooltip)
|
82
|
+
self._false.setToolTip(tooltip)
|
83
|
+
|
84
|
+
|
85
|
+
class MetadataWidget(QWidget):
|
86
|
+
|
87
|
+
valueChanged = Signal()
|
88
|
+
|
89
|
+
def __init__(self, info: FieldInfo, parent: QWidget | None = None) -> None:
|
90
|
+
super().__init__(parent)
|
91
|
+
self._info = info
|
92
|
+
self._layout = QHBoxLayout()
|
93
|
+
self._layout.setContentsMargins(0, 0, 0, 0)
|
94
|
+
self._layout.setSizeConstraint(QLayout.SizeConstraint.SetMaximumSize)
|
95
|
+
self._default = field_default(self._info)
|
96
|
+
self._desc = self._info.description
|
97
|
+
self.setLayout(self._layout)
|
98
|
+
self._add_main_widget()
|
99
|
+
if clearable_required(info):
|
100
|
+
self._add_clear_button()
|
101
|
+
|
102
|
+
@abstractmethod
|
103
|
+
def getValue(self): ...
|
104
|
+
|
105
|
+
@abstractmethod
|
106
|
+
def setValue(self, value): ...
|
107
|
+
|
108
|
+
@abstractmethod
|
109
|
+
def _add_main_widget(self) -> None:
|
110
|
+
"""Add the main data entry widget to self._main_widget and appply any
|
111
|
+
constraints from the field info"""
|
112
|
+
|
113
|
+
def _describe(self, pad=" "):
|
114
|
+
return pad + (self._desc if self._desc else "")
|
115
|
+
|
116
|
+
def _add_clear_button(self):
|
117
|
+
self._clear_button = QToolButton()
|
118
|
+
self._clear_button.setIcon(
|
119
|
+
material_icon(icon_name="close", size=(10, 10), convert_to_pixmap=False)
|
120
|
+
)
|
121
|
+
self._layout.addWidget(self._clear_button)
|
122
|
+
# the widget added in _add_main_widget must implement .clear() if value is not required
|
123
|
+
self._clear_button.setToolTip("Clear value or reset to default.")
|
124
|
+
self._clear_button.clicked.connect(self._main_widget.clear) # type: ignore
|
125
|
+
|
126
|
+
def _value_changed(self, *_, **__):
|
127
|
+
self.valueChanged.emit()
|
128
|
+
|
129
|
+
|
130
|
+
class StrMetadataField(MetadataWidget):
|
131
|
+
def __init__(self, info: FieldInfo, parent: QWidget | None = None) -> None:
|
132
|
+
super().__init__(info, parent)
|
133
|
+
self._main_widget.textChanged.connect(self._value_changed)
|
134
|
+
|
135
|
+
def _add_main_widget(self) -> None:
|
136
|
+
self._main_widget = QLineEdit()
|
137
|
+
self._layout.addWidget(self._main_widget)
|
138
|
+
min_length, max_length = field_minlen(self._info), field_maxlen(self._info)
|
139
|
+
if max_length:
|
140
|
+
self._main_widget.setMaxLength(max_length)
|
141
|
+
self._main_widget.setToolTip(
|
142
|
+
f"(length min: {min_length} max: {max_length}){self._describe()}"
|
143
|
+
)
|
144
|
+
if self._default:
|
145
|
+
self._main_widget.setText(self._default)
|
146
|
+
self._add_clear_button()
|
147
|
+
|
148
|
+
def getValue(self):
|
149
|
+
if self._main_widget.text() == "":
|
150
|
+
return self._default
|
151
|
+
return self._main_widget.text()
|
152
|
+
|
153
|
+
def setValue(self, value: str):
|
154
|
+
if value is None:
|
155
|
+
self._main_widget.setText("")
|
156
|
+
self._main_widget.setText(value)
|
157
|
+
|
158
|
+
|
159
|
+
class IntMetadataField(MetadataWidget):
|
160
|
+
def __init__(self, info: FieldInfo, parent: QWidget | None = None) -> None:
|
161
|
+
super().__init__(info, parent)
|
162
|
+
self._main_widget.textChanged.connect(self._value_changed)
|
163
|
+
|
164
|
+
def _add_main_widget(self) -> None:
|
165
|
+
self._main_widget = QSpinBox()
|
166
|
+
self._layout.addWidget(self._main_widget)
|
167
|
+
min_, max_ = field_limits(self._info, int)
|
168
|
+
self._main_widget.setMinimum(min_)
|
169
|
+
self._main_widget.setMaximum(max_)
|
170
|
+
self._main_widget.setToolTip(f"(range {min_} to {max_}){self._describe()}")
|
171
|
+
if self._default is not None:
|
172
|
+
self._main_widget.setValue(self._default)
|
173
|
+
self._add_clear_button()
|
174
|
+
else:
|
175
|
+
self._main_widget.clear()
|
176
|
+
|
177
|
+
def getValue(self):
|
178
|
+
if self._main_widget.text() == "":
|
179
|
+
return self._default
|
180
|
+
return self._main_widget.value()
|
181
|
+
|
182
|
+
def setValue(self, value: int):
|
183
|
+
if value is None:
|
184
|
+
self._main_widget.clear()
|
185
|
+
self._main_widget.setValue(value)
|
186
|
+
|
187
|
+
|
188
|
+
class FloatDecimalMetadataField(MetadataWidget):
|
189
|
+
def __init__(self, info: FieldInfo, parent: QWidget | None = None) -> None:
|
190
|
+
super().__init__(info, parent)
|
191
|
+
self._main_widget.textChanged.connect(self._value_changed)
|
192
|
+
|
193
|
+
def _add_main_widget(self) -> None:
|
194
|
+
self._main_widget = QDoubleSpinBox()
|
195
|
+
self._layout.addWidget(self._main_widget)
|
196
|
+
min_, max_ = field_limits(self._info, int)
|
197
|
+
self._main_widget.setMinimum(min_)
|
198
|
+
self._main_widget.setMaximum(max_)
|
199
|
+
precision = field_precision(self._info)
|
200
|
+
if precision:
|
201
|
+
self._main_widget.setDecimals(precision)
|
202
|
+
minstr = f"{float(min_):.3f}" if abs(min_) <= 1000 else f"{float(min_):.3e}"
|
203
|
+
maxstr = f"{float(max_):.3f}" if abs(max_) <= 1000 else f"{float(max_):.3e}"
|
204
|
+
self._main_widget.setToolTip(f"(range {minstr} to {maxstr}){self._describe()}")
|
205
|
+
if self._default is not None:
|
206
|
+
self._main_widget.setValue(self._default)
|
207
|
+
self._add_clear_button()
|
208
|
+
else:
|
209
|
+
self._main_widget.clear()
|
210
|
+
|
211
|
+
def getValue(self):
|
212
|
+
if self._main_widget.text() == "":
|
213
|
+
return self._default
|
214
|
+
return self._main_widget.value()
|
215
|
+
|
216
|
+
def setValue(self, value: float):
|
217
|
+
if value is None:
|
218
|
+
self._main_widget.clear()
|
219
|
+
self._main_widget.setValue(value)
|
220
|
+
|
221
|
+
|
222
|
+
class BoolMetadataField(MetadataWidget):
|
223
|
+
def __init__(self, info: FieldInfo, parent: QWidget | None = None) -> None:
|
224
|
+
super().__init__(info, parent)
|
225
|
+
self._main_widget.stateChanged.connect(self._value_changed)
|
226
|
+
|
227
|
+
def _add_main_widget(self) -> None:
|
228
|
+
if clearable_required(self._info):
|
229
|
+
self._main_widget = ClearableBoolEntry()
|
230
|
+
else:
|
231
|
+
self._main_widget = QCheckBox()
|
232
|
+
self._layout.addWidget(self._main_widget)
|
233
|
+
self._main_widget.setToolTip(self._describe(""))
|
234
|
+
self._main_widget.setChecked(self._default) # type: ignore # if there is no default then it will be ClearableBoolEntry and can be set with None
|
235
|
+
|
236
|
+
def getValue(self):
|
237
|
+
return self._main_widget.isChecked()
|
238
|
+
|
239
|
+
def setValue(self, value):
|
240
|
+
self._main_widget.setChecked(value)
|
241
|
+
|
242
|
+
|
243
|
+
def widget_from_type(annotation: type | None) -> Callable[[FieldInfo], MetadataWidget]:
|
244
|
+
if annotation in [str, str | None]:
|
245
|
+
return StrMetadataField
|
246
|
+
if annotation in [int, int | None]:
|
247
|
+
return IntMetadataField
|
248
|
+
if annotation in [float, float | None, Decimal, Decimal | None]:
|
249
|
+
return FloatDecimalMetadataField
|
250
|
+
if annotation in [bool, bool | None]:
|
251
|
+
return BoolMetadataField
|
252
|
+
else:
|
253
|
+
logger.warning(f"Type {annotation} is not (yet) supported in metadata form creation.")
|
254
|
+
return StrMetadataField
|
255
|
+
|
256
|
+
|
257
|
+
if __name__ == "__main__": # pragma: no cover
|
258
|
+
|
259
|
+
class TestModel(BaseModel):
|
260
|
+
value1: str | None = Field(None)
|
261
|
+
value2: bool | None = Field(None)
|
262
|
+
value3: bool = Field(True)
|
263
|
+
value4: int = Field(123)
|
264
|
+
value5: int | None = Field()
|
265
|
+
|
266
|
+
app = QApplication([])
|
267
|
+
w = QWidget()
|
268
|
+
layout = QGridLayout()
|
269
|
+
w.setLayout(layout)
|
270
|
+
for i, (field_name, info) in enumerate(TestModel.model_fields.items()):
|
271
|
+
layout.addWidget(QLabel(field_name), i, 0)
|
272
|
+
layout.addWidget(widget_from_type(info.annotation)(info), i, 1)
|
273
|
+
|
274
|
+
w.show()
|
275
|
+
app.exec()
|
@@ -0,0 +1,67 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import sys
|
4
|
+
from decimal import Decimal
|
5
|
+
from math import inf, nextafter
|
6
|
+
from typing import TYPE_CHECKING, TypeVar, get_args
|
7
|
+
|
8
|
+
from annotated_types import Ge, Gt, Le, Lt
|
9
|
+
from bec_lib.logger import bec_logger
|
10
|
+
from pydantic_core import PydanticUndefined
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from pydantic.fields import FieldInfo
|
14
|
+
|
15
|
+
logger = bec_logger.logger
|
16
|
+
|
17
|
+
|
18
|
+
_MININT = -2147483648
|
19
|
+
_MAXINT = 2147483647
|
20
|
+
_MINFLOAT = -sys.float_info.max
|
21
|
+
_MAXFLOAT = sys.float_info.max
|
22
|
+
|
23
|
+
T = TypeVar("T", int, float, Decimal)
|
24
|
+
|
25
|
+
|
26
|
+
def field_limits(info: FieldInfo, type_: type[T]) -> tuple[T, T]:
|
27
|
+
_min = _MININT if type_ is int else _MINFLOAT
|
28
|
+
_max = _MAXINT if type_ is int else _MAXFLOAT
|
29
|
+
for md in info.metadata:
|
30
|
+
if isinstance(md, Ge):
|
31
|
+
_min = type_(md.ge) # type: ignore
|
32
|
+
if isinstance(md, Gt):
|
33
|
+
_min = type_(md.gt) + 1 if type_ is int else nextafter(type_(md.gt), inf) # type: ignore
|
34
|
+
if isinstance(md, Lt):
|
35
|
+
_max = type_(md.lt) - 1 if type_ is int else nextafter(type_(md.lt), -inf) # type: ignore
|
36
|
+
if isinstance(md, Le):
|
37
|
+
_max = type_(md.le) # type: ignore
|
38
|
+
return _min, _max # type: ignore
|
39
|
+
|
40
|
+
|
41
|
+
def _get_anno(info: FieldInfo, annotation: str, default):
|
42
|
+
for md in info.metadata:
|
43
|
+
if hasattr(md, annotation):
|
44
|
+
return getattr(md, annotation)
|
45
|
+
return default
|
46
|
+
|
47
|
+
|
48
|
+
def field_precision(info: FieldInfo):
|
49
|
+
return _get_anno(info, "decimal_places", 307)
|
50
|
+
|
51
|
+
|
52
|
+
def field_maxlen(info: FieldInfo):
|
53
|
+
return _get_anno(info, "max_length", None)
|
54
|
+
|
55
|
+
|
56
|
+
def field_minlen(info: FieldInfo):
|
57
|
+
return _get_anno(info, "min_length", None)
|
58
|
+
|
59
|
+
|
60
|
+
def field_default(info: FieldInfo):
|
61
|
+
if info.default is PydanticUndefined:
|
62
|
+
return
|
63
|
+
return info.default
|
64
|
+
|
65
|
+
|
66
|
+
def clearable_required(info: FieldInfo):
|
67
|
+
return type(None) in get_args(info.annotation) or info.is_required()
|
@@ -0,0 +1,146 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt, Signal # type: ignore
|
6
|
+
from qtpy.QtWidgets import (
|
7
|
+
QApplication,
|
8
|
+
QHBoxLayout,
|
9
|
+
QLabel,
|
10
|
+
QPushButton,
|
11
|
+
QTableView,
|
12
|
+
QVBoxLayout,
|
13
|
+
QWidget,
|
14
|
+
)
|
15
|
+
|
16
|
+
from bec_widgets.qt_utils.error_popups import SafeSlot
|
17
|
+
|
18
|
+
|
19
|
+
class AdditionalMetadataTableModel(QAbstractTableModel):
|
20
|
+
def __init__(self, data):
|
21
|
+
super().__init__()
|
22
|
+
self._data: list[list[str]] = data
|
23
|
+
self._disallowed_keys: list[str] = []
|
24
|
+
|
25
|
+
def headerData(
|
26
|
+
self, section: int, orientation: Qt.Orientation, role: int = Qt.ItemDataRole()
|
27
|
+
) -> Any:
|
28
|
+
if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
|
29
|
+
return "Key" if section == 0 else "Value"
|
30
|
+
return super().headerData(section, orientation, role)
|
31
|
+
|
32
|
+
def rowCount(self, index: QModelIndex = QModelIndex()):
|
33
|
+
return 0 if index.isValid() else len(self._data)
|
34
|
+
|
35
|
+
def columnCount(self, index: QModelIndex = QModelIndex()):
|
36
|
+
return 0 if index.isValid() else 2
|
37
|
+
|
38
|
+
def data(self, index, role=Qt.ItemDataRole):
|
39
|
+
if index.isValid():
|
40
|
+
if role == Qt.ItemDataRole.DisplayRole or role == Qt.ItemDataRole.EditRole:
|
41
|
+
return str(self._data[index.row()][index.column()])
|
42
|
+
|
43
|
+
def setData(self, index, value, role):
|
44
|
+
if role == Qt.ItemDataRole.EditRole:
|
45
|
+
if value in self._disallowed_keys or value in self._other_keys(index.row()):
|
46
|
+
return False
|
47
|
+
self._data[index.row()][index.column()] = str(value)
|
48
|
+
return True
|
49
|
+
return False
|
50
|
+
|
51
|
+
def update_disallowed_keys(self, keys: list[str]):
|
52
|
+
self._disallowed_keys = keys
|
53
|
+
for i, item in enumerate(self._data):
|
54
|
+
if item[0] in self._disallowed_keys:
|
55
|
+
self._data[i][0] = ""
|
56
|
+
self.dataChanged.emit(self.index(i, 0), self.index(i, 0))
|
57
|
+
|
58
|
+
def _other_keys(self, row: int):
|
59
|
+
return [r[0] for r in self._data[:row] + self._data[row + 1 :]]
|
60
|
+
|
61
|
+
def flags(self, _):
|
62
|
+
return Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsEditable
|
63
|
+
|
64
|
+
def insertRows(self, row, number, index):
|
65
|
+
"""We only support adding one at a time for now"""
|
66
|
+
if row != self.rowCount() or number != 1:
|
67
|
+
return False
|
68
|
+
self.beginInsertRows(QModelIndex(), 0, 0)
|
69
|
+
self._data.append(["", ""])
|
70
|
+
self.endInsertRows()
|
71
|
+
return True
|
72
|
+
|
73
|
+
def removeRows(self, row, number, index):
|
74
|
+
"""This can only be consecutive, so instead of trying to be clever, only support removing one at a time"""
|
75
|
+
if number != 1:
|
76
|
+
return False
|
77
|
+
self.beginRemoveRows(QModelIndex(), row, row)
|
78
|
+
del self._data[row]
|
79
|
+
self.endRemoveRows()
|
80
|
+
return True
|
81
|
+
|
82
|
+
@SafeSlot()
|
83
|
+
def add_row(self):
|
84
|
+
self.insertRow(self.rowCount())
|
85
|
+
|
86
|
+
@SafeSlot(list)
|
87
|
+
def delete_rows(self, rows: list[int]):
|
88
|
+
# delete from the end so indices stay correct
|
89
|
+
for row in sorted(rows, reverse=True):
|
90
|
+
self.removeRows(row, 1, QModelIndex())
|
91
|
+
|
92
|
+
def dump_dict(self):
|
93
|
+
if self._data == [[]]:
|
94
|
+
return {}
|
95
|
+
return dict(self._data)
|
96
|
+
|
97
|
+
|
98
|
+
class AdditionalMetadataTable(QWidget):
|
99
|
+
|
100
|
+
delete_rows = Signal(list)
|
101
|
+
|
102
|
+
def __init__(self, initial_data: list[list[str]]):
|
103
|
+
super().__init__()
|
104
|
+
|
105
|
+
self._layout = QHBoxLayout()
|
106
|
+
self.setLayout(self._layout)
|
107
|
+
self._table_model = AdditionalMetadataTableModel(initial_data)
|
108
|
+
self._table_view = QTableView()
|
109
|
+
self._table_view.setModel(self._table_model)
|
110
|
+
self._table_view.horizontalHeader().setStretchLastSection(True)
|
111
|
+
self._layout.addWidget(self._table_view)
|
112
|
+
|
113
|
+
self._buttons = QVBoxLayout()
|
114
|
+
self._layout.addLayout(self._buttons)
|
115
|
+
self._add_button = QPushButton("+")
|
116
|
+
self._add_button.setToolTip("add a new row")
|
117
|
+
self._remove_button = QPushButton("-")
|
118
|
+
self._remove_button.setToolTip("delete rows containing any selected cells")
|
119
|
+
self._buttons.addWidget(self._add_button)
|
120
|
+
self._buttons.addWidget(self._remove_button)
|
121
|
+
self._add_button.clicked.connect(self._table_model.add_row)
|
122
|
+
self._remove_button.clicked.connect(self.delete_selected_rows)
|
123
|
+
self.delete_rows.connect(self._table_model.delete_rows)
|
124
|
+
|
125
|
+
def delete_selected_rows(self):
|
126
|
+
cells: list[QModelIndex] = self._table_view.selectionModel().selectedIndexes()
|
127
|
+
row_indices = list({r.row() for r in cells})
|
128
|
+
if row_indices:
|
129
|
+
self.delete_rows.emit(row_indices)
|
130
|
+
|
131
|
+
def dump_dict(self):
|
132
|
+
return self._table_model.dump_dict()
|
133
|
+
|
134
|
+
def update_disallowed_keys(self, keys: list[str]):
|
135
|
+
self._table_model.update_disallowed_keys(keys)
|
136
|
+
|
137
|
+
|
138
|
+
if __name__ == "__main__": # pragma: no cover
|
139
|
+
from bec_widgets.utils.colors import set_theme
|
140
|
+
|
141
|
+
app = QApplication([])
|
142
|
+
set_theme("dark")
|
143
|
+
|
144
|
+
window = AdditionalMetadataTable([["key1", "value1"], ["key2", "value2"], ["key3", "value3"]])
|
145
|
+
window.show()
|
146
|
+
app.exec()
|
@@ -0,0 +1,196 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from decimal import Decimal
|
4
|
+
from typing import TYPE_CHECKING
|
5
|
+
|
6
|
+
from bec_lib.logger import bec_logger
|
7
|
+
from bec_lib.metadata_schema import get_metadata_schema_for_scan
|
8
|
+
from bec_qthemes import material_icon
|
9
|
+
from pydantic import Field, ValidationError
|
10
|
+
from qtpy.QtWidgets import (
|
11
|
+
QApplication,
|
12
|
+
QComboBox,
|
13
|
+
QGridLayout,
|
14
|
+
QLabel,
|
15
|
+
QLayout,
|
16
|
+
QVBoxLayout,
|
17
|
+
QWidget,
|
18
|
+
)
|
19
|
+
|
20
|
+
from bec_widgets.qt_utils.compact_popup import CompactPopupWidget
|
21
|
+
from bec_widgets.qt_utils.error_popups import SafeSlot
|
22
|
+
from bec_widgets.utils.bec_widget import BECWidget
|
23
|
+
from bec_widgets.widgets.editors.scan_metadata._metadata_widgets import widget_from_type
|
24
|
+
from bec_widgets.widgets.editors.scan_metadata.additional_metadata_table import (
|
25
|
+
AdditionalMetadataTable,
|
26
|
+
)
|
27
|
+
|
28
|
+
if TYPE_CHECKING:
|
29
|
+
from pydantic.fields import FieldInfo
|
30
|
+
|
31
|
+
logger = bec_logger.logger
|
32
|
+
|
33
|
+
|
34
|
+
class ScanMetadata(BECWidget, QWidget):
|
35
|
+
"""Dynamically generates a form for inclusion of metadata for a scan. Uses the
|
36
|
+
metadata schema registry supplied in the plugin repo to find pydantic models
|
37
|
+
associated with the scan type. Sets limits for numerical values if specified."""
|
38
|
+
|
39
|
+
def __init__(
|
40
|
+
self,
|
41
|
+
parent=None,
|
42
|
+
client=None,
|
43
|
+
scan_name: str | None = None,
|
44
|
+
initial_extras: list[list[str]] | None = None,
|
45
|
+
):
|
46
|
+
super().__init__(client=client)
|
47
|
+
QWidget.__init__(self, parent=parent)
|
48
|
+
|
49
|
+
self.set_schema(scan_name)
|
50
|
+
|
51
|
+
self._layout = QVBoxLayout()
|
52
|
+
self._layout.setSizeConstraint(QLayout.SizeConstraint.SetFixedSize)
|
53
|
+
self.setLayout(self._layout)
|
54
|
+
self._layout.addWidget(QLabel("<b>Required scan metadata:</b>"))
|
55
|
+
self._md_grid = QWidget()
|
56
|
+
self._layout.addWidget(self._md_grid)
|
57
|
+
self._grid_container = QVBoxLayout()
|
58
|
+
self._md_grid.setLayout(self._grid_container)
|
59
|
+
self._new_grid_layout()
|
60
|
+
self._grid_container.addLayout(self._md_grid_layout)
|
61
|
+
self._layout.addWidget(QLabel("<b>Additional metadata:</b>"))
|
62
|
+
self._additional_metadata = AdditionalMetadataTable(initial_extras or [])
|
63
|
+
self._layout.addWidget(self._additional_metadata)
|
64
|
+
|
65
|
+
self._validity = CompactPopupWidget()
|
66
|
+
self._validity.compact_view = True # type: ignore
|
67
|
+
self._validity.label = "Validity" # type: ignore
|
68
|
+
self._validity.compact_show_popup.setIcon(
|
69
|
+
material_icon(icon_name="info", size=(10, 10), convert_to_pixmap=False)
|
70
|
+
)
|
71
|
+
self._validity_message = QLabel("Not yet validated")
|
72
|
+
self._validity.addWidget(self._validity_message)
|
73
|
+
self._layout.addWidget(self._validity)
|
74
|
+
|
75
|
+
self.populate()
|
76
|
+
|
77
|
+
@SafeSlot(str)
|
78
|
+
def update_with_new_scan(self, scan_name: str):
|
79
|
+
self.set_schema(scan_name)
|
80
|
+
self.populate()
|
81
|
+
self.validate_form()
|
82
|
+
|
83
|
+
def validate_form(self, *_):
|
84
|
+
try:
|
85
|
+
self._md_schema.model_validate(self.get_full_model_dict())
|
86
|
+
self._validity.set_global_state("success")
|
87
|
+
self._validity_message.setText("No errors!")
|
88
|
+
except ValidationError as e:
|
89
|
+
self._validity.set_global_state("emergency")
|
90
|
+
self._validity_message.setText(str(e))
|
91
|
+
|
92
|
+
def get_full_model_dict(self):
|
93
|
+
"""Get the entered metadata as a dict"""
|
94
|
+
return self._additional_metadata.dump_dict() | self._dict_from_grid()
|
95
|
+
|
96
|
+
def set_schema(self, scan_name: str | None = None):
|
97
|
+
self._scan_name = scan_name or ""
|
98
|
+
self._md_schema = get_metadata_schema_for_scan(self._scan_name)
|
99
|
+
|
100
|
+
def populate(self):
|
101
|
+
self._clear_grid()
|
102
|
+
self._populate()
|
103
|
+
|
104
|
+
def _populate(self):
|
105
|
+
self._additional_metadata.update_disallowed_keys(list(self._md_schema.model_fields.keys()))
|
106
|
+
for i, (field_name, info) in enumerate(self._md_schema.model_fields.items()):
|
107
|
+
self._add_griditem(field_name, info, i)
|
108
|
+
|
109
|
+
def _add_griditem(self, field_name: str, info: FieldInfo, row: int):
|
110
|
+
grid = self._md_grid_layout
|
111
|
+
label = QLabel(info.title or field_name)
|
112
|
+
label.setProperty("_model_field_name", field_name)
|
113
|
+
label.setToolTip(info.description or field_name)
|
114
|
+
grid.addWidget(label, row, 0)
|
115
|
+
widget = widget_from_type(info.annotation)(info)
|
116
|
+
widget.valueChanged.connect(self.validate_form)
|
117
|
+
grid.addWidget(widget, row, 1)
|
118
|
+
|
119
|
+
def _dict_from_grid(self) -> dict[str, str | int | float | Decimal | bool]:
|
120
|
+
grid = self._md_grid_layout
|
121
|
+
return {
|
122
|
+
grid.itemAtPosition(i, 0).widget().property("_model_field_name"): grid.itemAtPosition(i, 1).widget().getValue() # type: ignore # we only add 'MetadataWidget's here
|
123
|
+
for i in range(grid.rowCount())
|
124
|
+
}
|
125
|
+
|
126
|
+
def _clear_grid(self):
|
127
|
+
while self._md_grid_layout.count():
|
128
|
+
item = self._md_grid_layout.takeAt(0)
|
129
|
+
widget = item.widget()
|
130
|
+
if widget is not None:
|
131
|
+
widget.deleteLater()
|
132
|
+
self._md_grid_layout.deleteLater()
|
133
|
+
self._new_grid_layout()
|
134
|
+
self._grid_container.addLayout(self._md_grid_layout)
|
135
|
+
self._md_grid.adjustSize()
|
136
|
+
self.adjustSize()
|
137
|
+
|
138
|
+
def _new_grid_layout(self):
|
139
|
+
self._md_grid_layout = QGridLayout()
|
140
|
+
self._md_grid_layout.setContentsMargins(0, 0, 0, 0)
|
141
|
+
self._md_grid_layout.setSizeConstraint(QLayout.SizeConstraint.SetFixedSize)
|
142
|
+
|
143
|
+
|
144
|
+
if __name__ == "__main__": # pragma: no cover
|
145
|
+
from unittest.mock import patch
|
146
|
+
|
147
|
+
from bec_lib.metadata_schema import BasicScanMetadata
|
148
|
+
|
149
|
+
from bec_widgets.utils.colors import set_theme
|
150
|
+
|
151
|
+
class ExampleSchema1(BasicScanMetadata):
|
152
|
+
abc: int = Field(gt=0, lt=2000, description="Heating temperature abc", title="A B C")
|
153
|
+
foo: str = Field(max_length=12, description="Sample database code", default="DEF123")
|
154
|
+
xyz: Decimal = Field(decimal_places=4)
|
155
|
+
baz: bool
|
156
|
+
|
157
|
+
class ExampleSchema2(BasicScanMetadata):
|
158
|
+
checkbox_up_top: bool
|
159
|
+
checkbox_again: bool = Field(
|
160
|
+
title="Checkbox Again", description="this one defaults to True", default=True
|
161
|
+
)
|
162
|
+
different_items: int | None = Field(
|
163
|
+
None, description="This is just one different item...", gt=-100, lt=0
|
164
|
+
)
|
165
|
+
length_limited_string: str = Field(max_length=32)
|
166
|
+
float_with_2dp: Decimal = Field(decimal_places=2)
|
167
|
+
|
168
|
+
class ExampleSchema3(BasicScanMetadata):
|
169
|
+
optional_with_regex: str | None = Field(None, pattern=r"^\d+-\d+$")
|
170
|
+
|
171
|
+
with patch(
|
172
|
+
"bec_lib.metadata_schema._get_metadata_schema_registry",
|
173
|
+
lambda: {"scan1": ExampleSchema1, "scan2": ExampleSchema2, "scan3": ExampleSchema3},
|
174
|
+
):
|
175
|
+
|
176
|
+
app = QApplication([])
|
177
|
+
w = QWidget()
|
178
|
+
selection = QComboBox()
|
179
|
+
selection.addItems(["grid_scan", "scan1", "scan2", "scan3"])
|
180
|
+
|
181
|
+
layout = QVBoxLayout()
|
182
|
+
w.setLayout(layout)
|
183
|
+
|
184
|
+
scan_metadata = ScanMetadata(
|
185
|
+
scan_name="grid_scan",
|
186
|
+
initial_extras=[["key1", "value1"], ["key2", "value2"], ["key3", "value3"]],
|
187
|
+
)
|
188
|
+
selection.currentTextChanged.connect(scan_metadata.update_with_new_scan)
|
189
|
+
|
190
|
+
layout.addWidget(selection)
|
191
|
+
layout.addWidget(scan_metadata)
|
192
|
+
|
193
|
+
set_theme("dark")
|
194
|
+
window = w
|
195
|
+
window.show()
|
196
|
+
app.exec()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bec_widgets
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.21.1
|
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
|
@@ -16,7 +16,7 @@ Requires-Dist: black~=24.0
|
|
16
16
|
Requires-Dist: isort>=5.13.2,~=5.13
|
17
17
|
Requires-Dist: pydantic~=2.0
|
18
18
|
Requires-Dist: pyqtgraph~=0.13
|
19
|
-
Requires-Dist: pyside6
|
19
|
+
Requires-Dist: pyside6>=6.8
|
20
20
|
Requires-Dist: pyte
|
21
21
|
Requires-Dist: qtconsole>=5.5.1,~=5.5
|
22
22
|
Requires-Dist: qtpy~=2.4
|
@@ -2,11 +2,11 @@
|
|
2
2
|
.gitlab-ci.yml,sha256=PuL-FmkTHm7qs467Mh9D8quWcEj4tgEA-UUGDieMuWk,8774
|
3
3
|
.pylintrc,sha256=eeY8YwSI74oFfq6IYIbCqnx3Vk8ZncKaatv96n_Y8Rs,18544
|
4
4
|
.readthedocs.yaml,sha256=aSOc277LqXcsTI6lgvm_JY80lMlr69GbPKgivua2cS0,603
|
5
|
-
CHANGELOG.md,sha256=
|
5
|
+
CHANGELOG.md,sha256=qVi-dFtB-ju5mentmg4ahj_kSueH8vlafbLKDRlthoE,228122
|
6
6
|
LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
7
|
-
PKG-INFO,sha256=
|
7
|
+
PKG-INFO,sha256=GMLaHQPKoRSDHYu-VDZfXVqKsqonrWI6S9FZCII_rgs,1173
|
8
8
|
README.md,sha256=KgdKusjlvEvFtdNZCeDMO91y77MWK2iDcYMDziksOr4,2553
|
9
|
-
pyproject.toml,sha256=
|
9
|
+
pyproject.toml,sha256=i0ObNhX_Ynef5F7F_mDIJCnr1jR7u6ucibkkkJy4D5M,2540
|
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
|
@@ -59,7 +59,7 @@ bec_widgets/qt_utils/toolbar.py,sha256=YY_-UGc7uZhahYn7xnTvBGbalmTkpTa4WLikpsHwn
|
|
59
59
|
bec_widgets/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
60
60
|
bec_widgets/tests/utils.py,sha256=GbQtN7qf9n-8FoAfNddZ4aAqA7oBo_hGAlnKELd6Xzw,6943
|
61
61
|
bec_widgets/utils/__init__.py,sha256=1930ji1Jj6dVuY81Wd2kYBhHYNV-2R0bN_L4o9zBj1U,533
|
62
|
-
bec_widgets/utils/bec_connector.py,sha256=
|
62
|
+
bec_widgets/utils/bec_connector.py,sha256=r2m6AtLooYQkYUGQdolUzpDfjBsKOSK-OiKJsxBMWd8,11445
|
63
63
|
bec_widgets/utils/bec_designer.py,sha256=XBy38NbNMoRDpvRx5lGP2XnJNG34YKZ7I-ARFkn-gzs,5017
|
64
64
|
bec_widgets/utils/bec_dispatcher.py,sha256=OFmkx9vOz4pA4Sdc14QreyDZ870QYskJ4B5daVVeYg4,6325
|
65
65
|
bec_widgets/utils/bec_signal_proxy.py,sha256=soKdA4pJL8S0d-93C0QqcIUxLA4rfb1-B1jyRXHmMxk,3011
|
@@ -220,6 +220,11 @@ bec_widgets/widgets/editors/console/console_plugin.py,sha256=EvFTruYDVHiS4pHIwZn
|
|
220
220
|
bec_widgets/widgets/editors/console/register_console.py,sha256=zoF-i3R9sRGzb85sdoxVunebYOfOD53fkCELTPtrFRc,471
|
221
221
|
bec_widgets/widgets/editors/jupyter_console/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
222
222
|
bec_widgets/widgets/editors/jupyter_console/jupyter_console.py,sha256=-e7HQOECeH5eDrJYh4BFIzRL78LDkooU4otabyN0aX4,2343
|
223
|
+
bec_widgets/widgets/editors/scan_metadata/__init__.py,sha256=IhDv6xQ7tpSbdaAQDOenrb0IU3wfSoElV9k6ajIM1IY,315
|
224
|
+
bec_widgets/widgets/editors/scan_metadata/_metadata_widgets.py,sha256=2WZf0Ej_R3ZQJ9QLvxCrVAoOgNPT5unh9OuhyYiUVyY,9294
|
225
|
+
bec_widgets/widgets/editors/scan_metadata/_util.py,sha256=8qn2clcJqi9nvPSvZzO9ornHxn_PGw4Z_O_1XpSmq8E,1861
|
226
|
+
bec_widgets/widgets/editors/scan_metadata/additional_metadata_table.py,sha256=XysmHU8B6WqLOLjQQBRG3sbN6_KaralvrIlrylkJ56E,5105
|
227
|
+
bec_widgets/widgets/editors/scan_metadata/scan_metadata.py,sha256=fo9CvYFUWbuypYUcdzTXlUJ1rIIIVCIFPX1ejbJGL8Q,7263
|
223
228
|
bec_widgets/widgets/editors/text_box/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
224
229
|
bec_widgets/widgets/editors/text_box/register_text_box.py,sha256=xRgVugvjLhX3iKb-vaAxflE6pWpal7pVFWDaUSUZLyE,467
|
225
230
|
bec_widgets/widgets/editors/text_box/text_box.py,sha256=F_BdWKPwEjltgfAsPmGHipOs5sPtI4o0Y0EO68s3Og0,4311
|
@@ -350,8 +355,8 @@ bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py,sha256=Z
|
|
350
355
|
bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.pyproject,sha256=Lbi9zb6HNlIq14k6hlzR-oz6PIFShBuF7QxE6d87d64,34
|
351
356
|
bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button_plugin.py,sha256=CzChz2SSETYsR8-36meqWnsXCT-FIy_J_xeU5coWDY8,1350
|
352
357
|
bec_widgets/widgets/utility/visual/dark_mode_button/register_dark_mode_button.py,sha256=rMpZ1CaoucwobgPj1FuKTnt07W82bV1GaSYdoqcdMb8,521
|
353
|
-
bec_widgets-1.
|
354
|
-
bec_widgets-1.
|
355
|
-
bec_widgets-1.
|
356
|
-
bec_widgets-1.
|
357
|
-
bec_widgets-1.
|
358
|
+
bec_widgets-1.21.1.dist-info/METADATA,sha256=GMLaHQPKoRSDHYu-VDZfXVqKsqonrWI6S9FZCII_rgs,1173
|
359
|
+
bec_widgets-1.21.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
360
|
+
bec_widgets-1.21.1.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
|
361
|
+
bec_widgets-1.21.1.dist-info/licenses/LICENSE,sha256=YRKe85CBRyP7UpEAWwU8_qSIyuy5-l_9C-HKg5Qm8MQ,1511
|
362
|
+
bec_widgets-1.21.1.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 = "1.
|
7
|
+
version = "1.21.1"
|
8
8
|
description = "BEC Widgets"
|
9
9
|
requires-python = ">=3.10"
|
10
10
|
classifiers = [
|
@@ -20,7 +20,7 @@ dependencies = [
|
|
20
20
|
"isort~=5.13, >=5.13.2", # needed for bw-generate-cli
|
21
21
|
"pydantic~=2.0",
|
22
22
|
"pyqtgraph~=0.13",
|
23
|
-
"PySide6
|
23
|
+
"PySide6>=6.8",
|
24
24
|
"pyte", # needed for vt100 console
|
25
25
|
"qtconsole~=5.5, >=5.5.1", # needed for jupyter console
|
26
26
|
"qtpy~=2.4",
|
File without changes
|
File without changes
|
File without changes
|