bec-widgets 2.10.3__py3-none-any.whl → 2.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. CHANGELOG.md +84 -0
  2. PKG-INFO +1 -1
  3. bec_widgets/cli/client.py +5 -5
  4. bec_widgets/tests/utils.py +2 -2
  5. bec_widgets/utils/clickable_label.py +13 -0
  6. bec_widgets/utils/colors.py +7 -4
  7. bec_widgets/utils/expandable_frame.py +58 -7
  8. bec_widgets/utils/forms_from_types/forms.py +107 -28
  9. bec_widgets/utils/forms_from_types/items.py +88 -12
  10. bec_widgets/utils/forms_from_types/styles.py +21 -0
  11. bec_widgets/utils/generate_designer_plugin.py +10 -21
  12. bec_widgets/widgets/control/scan_control/scan_control.py +1 -1
  13. bec_widgets/widgets/editors/dict_backed_table.py +31 -11
  14. bec_widgets/widgets/editors/scan_metadata/_util.py +7 -4
  15. bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +10 -4
  16. bec_widgets/widgets/plots/image/image.py +128 -856
  17. bec_widgets/widgets/plots/image/image_base.py +1062 -0
  18. bec_widgets/widgets/plots/image/image_item.py +7 -6
  19. bec_widgets/widgets/services/device_browser/device_browser.py +61 -29
  20. bec_widgets/widgets/services/device_browser/device_item/device_item.py +97 -19
  21. bec_widgets/widgets/services/device_browser/util.py +11 -0
  22. {bec_widgets-2.10.3.dist-info → bec_widgets-2.12.0.dist-info}/METADATA +1 -1
  23. {bec_widgets-2.10.3.dist-info → bec_widgets-2.12.0.dist-info}/RECORD +27 -23
  24. pyproject.toml +1 -1
  25. {bec_widgets-2.10.3.dist-info → bec_widgets-2.12.0.dist-info}/WHEEL +0 -0
  26. {bec_widgets-2.10.3.dist-info → bec_widgets-2.12.0.dist-info}/entry_points.txt +0 -0
  27. {bec_widgets-2.10.3.dist-info → bec_widgets-2.12.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md CHANGED
@@ -1,6 +1,90 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v2.12.0 (2025-06-04)
5
+
6
+ ### Bug Fixes
7
+
8
+ - Exclude metadata from RPC
9
+ ([`718116a`](https://github.com/bec-project/bec_widgets/commit/718116afc3a724658c4cd57b76e93249a66a9ebd))
10
+
11
+ - Grid formatting in TypedForm
12
+ ([`5949121`](https://github.com/bec-project/bec_widgets/commit/594912136e2118de1a4de5213c2f668952f28a84))
13
+
14
+ - Make generate plugin robust to multiline init
15
+ ([`a10e6f7`](https://github.com/bec-project/bec_widgets/commit/a10e6f7820309d590e832f2bca44ca1db8ef72a1))
16
+
17
+ instead of str.find, use multiline regex with whitespace
18
+
19
+ - **device browser**: Mocks and utils for tests
20
+ ([`e0e26c2`](https://github.com/bec-project/bec_widgets/commit/e0e26c205bf930d680e01910f87489decc7fbcdb))
21
+
22
+ ### Features
23
+
24
+ - (#493) add dict to dynamic form types
25
+ ([`92d1d64`](https://github.com/bec-project/bec_widgets/commit/92d1d6435d6e8c05851804eb76605a4abeec01bb))
26
+
27
+ - (#493) add helpers to dynamic form widgets
28
+ ([`a25c1a8`](https://github.com/bec-project/bec_widgets/commit/a25c1a8039078c92789b717b3f8a553c75814c33))
29
+
30
+ - (#493) device browser to display config
31
+ ([`5188b38`](https://github.com/bec-project/bec_widgets/commit/5188b38c86f543d2abc742411b64fa127c6c0c16))
32
+
33
+ - Add clickable label util
34
+ ([`2dda58f`](https://github.com/bec-project/bec_widgets/commit/2dda58f7d2adf1f41c6ce4fad02d55bd9aa200fa))
35
+
36
+
37
+ ## v2.11.0 (2025-06-04)
38
+
39
+ ### Bug Fixes
40
+
41
+ - **image item**: Propagate remove call to parent class
42
+ ([`e211e4d`](https://github.com/bec-project/bec_widgets/commit/e211e4d7161cc4fc4b2f7cd18f058e070f5b4b7a))
43
+
44
+ - **image layer**: Add layer main if it does not exist
45
+ ([`7eb2f54`](https://github.com/bec-project/bec_widgets/commit/7eb2f54e0ed556e0c30a4e14ded75e32dcf3d531))
46
+
47
+ - **image_item**: Do not disconnect the monitor from within the image item
48
+ ([`4c0bd97`](https://github.com/bec-project/bec_widgets/commit/4c0bd977fc2b82680bbace763f5ffb19ed664f72))
49
+
50
+ ### Features
51
+
52
+ - **image_layer**: Add default name for image layers
53
+ ([`4a343b2`](https://github.com/bec-project/bec_widgets/commit/4a343b204112c53e593e9bb43642d21f268dfa85))
54
+
55
+ ### Refactoring
56
+
57
+ - **image**: Disconnect when layer is removed
58
+ ([`8a299a8`](https://github.com/bec-project/bec_widgets/commit/8a299a8268f3c21bbdc6629ad1f1f50a0aa0948b))
59
+
60
+ - **image**: Introduce image base and image layer; rename vrange to v_range
61
+ ([`10f292d`](https://github.com/bec-project/bec_widgets/commit/10f292def9d1551bca0d8f63c0a94799c08ff507))
62
+
63
+ - **image**: Move image item creation to layer manager
64
+ ([`c2b0c8c`](https://github.com/bec-project/bec_widgets/commit/c2b0c8c4336302ec4a7807c31b3f3b78a413c1aa))
65
+
66
+ - **image**: Removed access to image item config
67
+ ([`99ecf6a`](https://github.com/bec-project/bec_widgets/commit/99ecf6a18f2e87d68f3de3abf56d97f7e6467912))
68
+
69
+ - **image_base**: Move default color map to image layer
70
+ ([`92b89e7`](https://github.com/bec-project/bec_widgets/commit/92b89e72750fc0ab72ea51f865032133c49a7f18))
71
+
72
+ - **image_base**: Renamed layers to layer_manager and added public methods for accessing the layer
73
+ manager
74
+ ([`92dade0`](https://github.com/bec-project/bec_widgets/commit/92dade09508ff3940e0b5dc99917302d61b03bc8))
75
+
76
+ - **image_item**: Emit object name with removed signal
77
+ ([`a4f3117`](https://github.com/bec-project/bec_widgets/commit/a4f311794132c6c24370cb2f5b5e0725b12587fd))
78
+
79
+ - **image_item**: Removed outdated image item config
80
+ ([`3e789ca`](https://github.com/bec-project/bec_widgets/commit/3e789ca35b6d0cf2d8ae9677bc65b7f0ca4eabc7))
81
+
82
+ ### Testing
83
+
84
+ - Improve error message for widgets that are not properly cleaned up
85
+ ([`7c47505`](https://github.com/bec-project/bec_widgets/commit/7c47505c5a147885ca2e854e13c1eb3fddaf5489))
86
+
87
+
4
88
  ## v2.10.3 (2025-06-04)
5
89
 
6
90
  ### Bug Fixes
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bec_widgets
3
- Version: 2.10.3
3
+ Version: 2.12.0
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
bec_widgets/cli/client.py CHANGED
@@ -1252,16 +1252,16 @@ class Image(RPCBase):
1252
1252
 
1253
1253
  @property
1254
1254
  @rpc_call
1255
- def vrange(self) -> "tuple":
1255
+ def v_range(self) -> "QPointF":
1256
1256
  """
1257
- Get the vrange of the image.
1257
+ Set the v_range of the main image.
1258
1258
  """
1259
1259
 
1260
- @vrange.setter
1260
+ @v_range.setter
1261
1261
  @rpc_call
1262
- def vrange(self) -> "tuple":
1262
+ def v_range(self) -> "QPointF":
1263
1263
  """
1264
- Get the vrange of the image.
1264
+ Set the v_range of the main image.
1265
1265
  """
1266
1266
 
1267
1267
  @property
@@ -184,8 +184,8 @@ class FakePositioner(BECPositioner):
184
184
  class Positioner(FakePositioner):
185
185
  """just placeholder for testing embedded isinstance check in DeviceCombobox"""
186
186
 
187
- def __init__(self, name="test", limits=None, read_value=1.0):
188
- super().__init__(name, limits, read_value)
187
+ def __init__(self, name="test", limits=None, read_value=1.0, enabled=True):
188
+ super().__init__(name, limits=limits, read_value=read_value, enabled=enabled)
189
189
 
190
190
 
191
191
  class Device(FakeDevice):
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from qtpy.QtCore import Signal
4
+ from qtpy.QtGui import QMouseEvent
5
+ from qtpy.QtWidgets import QLabel
6
+
7
+
8
+ class ClickableLabel(QLabel):
9
+ clicked = Signal()
10
+
11
+ def mouseReleaseEvent(self, ev: QMouseEvent) -> None:
12
+ self.clicked.emit()
13
+ return super().mouseReleaseEvent(ev)
@@ -15,12 +15,15 @@ if TYPE_CHECKING: # pragma: no cover
15
15
  from bec_qthemes._main import AccentColors
16
16
 
17
17
 
18
- def get_theme_palette():
18
+ def get_theme_name():
19
19
  if QApplication.instance() is None or not hasattr(QApplication.instance(), "theme"):
20
- theme = "dark"
20
+ return "dark"
21
21
  else:
22
- theme = QApplication.instance().theme.theme
23
- return bec_qthemes.load_palette(theme)
22
+ return QApplication.instance().theme.theme
23
+
24
+
25
+ def get_theme_palette():
26
+ return bec_qthemes.load_palette(get_theme_name())
24
27
 
25
28
 
26
29
  def get_accent_colors() -> AccentColors | None:
@@ -1,7 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from bec_qthemes import material_icon
4
+ from qtpy.QtCore import Signal
4
5
  from qtpy.QtWidgets import (
6
+ QApplication,
5
7
  QFrame,
6
8
  QHBoxLayout,
7
9
  QLabel,
@@ -12,15 +14,20 @@ from qtpy.QtWidgets import (
12
14
  QWidget,
13
15
  )
14
16
 
17
+ from bec_widgets.utils.clickable_label import ClickableLabel
15
18
  from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
16
19
 
17
20
 
18
21
  class ExpandableGroupFrame(QFrame):
19
22
 
23
+ expansion_state_changed = Signal()
24
+
20
25
  EXPANDED_ICON_NAME: str = "collapse_all"
21
26
  COLLAPSED_ICON_NAME: str = "expand_all"
22
27
 
23
- def __init__(self, title: str, parent: QWidget | None = None, expanded: bool = True) -> None:
28
+ def __init__(
29
+ self, parent: QWidget | None = None, title: str = "", expanded: bool = True, icon: str = ""
30
+ ) -> None:
24
31
  super().__init__(parent=parent)
25
32
  self._expanded = expanded
26
33
 
@@ -29,19 +36,28 @@ class ExpandableGroupFrame(QFrame):
29
36
  self._layout = QVBoxLayout()
30
37
  self._layout.setContentsMargins(0, 0, 0, 0)
31
38
  self.setLayout(self._layout)
39
+
32
40
  self._title_layout = QHBoxLayout()
33
41
  self._layout.addLayout(self._title_layout)
34
- self._expansion_button = QToolButton()
35
- self._update_icon()
36
- self._title = QLabel(f"<b>{title}</b>")
37
- self._title_layout.addWidget(self._expansion_button)
42
+
43
+ self._title = ClickableLabel(f"<b>{title}</b>")
44
+ self._title_icon = ClickableLabel()
45
+ self._title_layout.addWidget(self._title_icon)
38
46
  self._title_layout.addWidget(self._title)
47
+ self.icon_name = icon
48
+
49
+ self._title_layout.addStretch(1)
50
+
51
+ self._expansion_button = QToolButton()
52
+ self._update_expansion_icon()
53
+ self._title_layout.addWidget(self._expansion_button, stretch=1)
39
54
 
40
55
  self._contents = QWidget(self)
41
56
  self._layout.addWidget(self._contents)
42
57
 
43
58
  self._expansion_button.clicked.connect(self.switch_expanded_state)
44
59
  self.expanded = self._expanded # type: ignore
60
+ self.expansion_state_changed.emit()
45
61
 
46
62
  def set_layout(self, layout: QLayout) -> None:
47
63
  self._contents.setLayout(layout)
@@ -50,7 +66,8 @@ class ExpandableGroupFrame(QFrame):
50
66
  @SafeSlot()
51
67
  def switch_expanded_state(self):
52
68
  self.expanded = not self.expanded # type: ignore
53
- self._update_icon()
69
+ self._update_expansion_icon()
70
+ self.expansion_state_changed.emit()
54
71
 
55
72
  @SafeProperty(bool)
56
73
  def expanded(self): # type: ignore
@@ -61,8 +78,9 @@ class ExpandableGroupFrame(QFrame):
61
78
  self._expanded = expanded
62
79
  self._contents.setVisible(expanded)
63
80
  self.updateGeometry()
81
+ self.adjustSize()
64
82
 
65
- def _update_icon(self):
83
+ def _update_expansion_icon(self):
66
84
  self._expansion_button.setIcon(
67
85
  material_icon(icon_name=self.EXPANDED_ICON_NAME, size=(10, 10), convert_to_pixmap=False)
68
86
  if self.expanded
@@ -70,3 +88,36 @@ class ExpandableGroupFrame(QFrame):
70
88
  icon_name=self.COLLAPSED_ICON_NAME, size=(10, 10), convert_to_pixmap=False
71
89
  )
72
90
  )
91
+
92
+ @SafeProperty(str)
93
+ def icon_name(self): # type: ignore
94
+ return self._title_icon_name
95
+
96
+ @icon_name.setter
97
+ def icon_name(self, icon_name: str):
98
+ self._title_icon_name = icon_name
99
+ self._set_title_icon(self._title_icon_name)
100
+
101
+ def _set_title_icon(self, icon_name: str):
102
+ if icon_name:
103
+ self._title_icon.setVisible(True)
104
+ self._title_icon.setPixmap(
105
+ material_icon(icon_name=icon_name, size=(20, 20), convert_to_pixmap=True)
106
+ )
107
+ else:
108
+ self._title_icon.setVisible(False)
109
+
110
+
111
+ # Application example
112
+ if __name__ == "__main__": # pragma: no cover
113
+
114
+ app = QApplication([])
115
+ frame = ExpandableGroupFrame()
116
+ layout = QVBoxLayout()
117
+ frame.set_layout(layout)
118
+ layout.addWidget(QLabel("test1"))
119
+ layout.addWidget(QLabel("test2"))
120
+ layout.addWidget(QLabel("test3"))
121
+
122
+ frame.show()
123
+ app.exec()
@@ -2,70 +2,99 @@ from __future__ import annotations
2
2
 
3
3
  from decimal import Decimal
4
4
  from types import NoneType
5
+ from typing import NamedTuple
5
6
 
6
7
  from bec_lib.logger import bec_logger
7
8
  from bec_qthemes import material_icon
8
9
  from pydantic import BaseModel, ValidationError
9
10
  from qtpy.QtCore import Signal # type: ignore
10
- from qtpy.QtWidgets import QGridLayout, QLabel, QLayout, QVBoxLayout, QWidget
11
+ from qtpy.QtWidgets import QGridLayout, QLabel, QLayout, QSizePolicy, QVBoxLayout, QWidget
11
12
 
12
13
  from bec_widgets.utils.bec_widget import BECWidget
13
14
  from bec_widgets.utils.compact_popup import CompactPopupWidget
14
- from bec_widgets.utils.forms_from_types.items import FormItemSpec, widget_from_type
15
+ from bec_widgets.utils.error_popups import SafeProperty
16
+ from bec_widgets.utils.forms_from_types.items import (
17
+ DynamicFormItem,
18
+ DynamicFormItemType,
19
+ FormItemSpec,
20
+ widget_from_type,
21
+ )
15
22
 
16
23
  logger = bec_logger.logger
17
24
 
18
25
 
26
+ class GridRow(NamedTuple):
27
+ i: int
28
+ label: QLabel
29
+ widget: DynamicFormItem
30
+
31
+
19
32
  class TypedForm(BECWidget, QWidget):
20
33
  PLUGIN = True
21
34
  ICON_NAME = "list_alt"
22
35
 
23
36
  value_changed = Signal()
24
37
 
25
- RPC = False
38
+ RPC = True
39
+ USER_ACCESS = ["enabled", "enabled.setter"]
26
40
 
27
41
  def __init__(
28
42
  self,
29
43
  parent=None,
30
44
  items: list[tuple[str, type]] | None = None,
31
45
  form_item_specs: list[FormItemSpec] | None = None,
46
+ enabled: bool = True,
47
+ pretty_display: bool = False,
32
48
  client=None,
33
49
  **kwargs,
34
50
  ):
35
51
  """Widget with a list of form items based on a list of types.
36
52
 
37
53
  Args:
38
- items (list[tuple[str, type]]): list of tuples of a name for the field and its type.
39
- Should be a type supported by the logic in items.py
40
- form_item_specs (list[FormItemSpec]): list of form item specs, equivalent to items.
41
- only one of items or form_item_specs should be
42
- supplied.
43
-
54
+ items (list[tuple[str, type]]): list of tuples of a name for the field and its type.
55
+ Should be a type supported by the logic in items.py
56
+ form_item_specs (list[FormItemSpec]): list of form item specs, equivalent to items.
57
+ only one of items or form_item_specs should be
58
+ supplied.
59
+ enabled (bool, optional): whether fields are enabled for editing.
60
+ pretty_display (bool, optional): Whether to use a pretty display for the widget. Defaults to False. If True, disables the widget, doesn't add a clear button, and adapts the stylesheet for non-editable display.
44
61
  """
45
- if (items is not None and form_item_specs is not None) or (
46
- items is None and form_item_specs is None
47
- ):
48
- raise ValueError("Must specify one and only one of items and form_item_specs")
62
+ if items is not None and form_item_specs is not None:
63
+ logger.error(
64
+ "Must specify one and only one of items and form_item_specs! Ignoring `items`."
65
+ )
66
+ items = None
67
+ if items is None and form_item_specs is None:
68
+ logger.error("Must specify one and only one of items and form_item_specs!")
69
+ items = []
49
70
  super().__init__(parent=parent, client=client, **kwargs)
50
71
  self._items = (
51
72
  form_item_specs
52
73
  if form_item_specs is not None
53
74
  else [
54
- FormItemSpec(name=name, item_type=item_type)
75
+ FormItemSpec(name=name, item_type=item_type, pretty_display=pretty_display)
55
76
  for name, item_type in items # type: ignore
56
77
  ]
57
78
  )
79
+ self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
58
80
  self._layout = QVBoxLayout()
59
81
  self._layout.setContentsMargins(0, 0, 0, 0)
60
82
  self.setLayout(self._layout)
61
83
 
84
+ self._enabled: bool = enabled
85
+
62
86
  self._form_grid_container = QWidget(parent=self)
87
+ self._form_grid_container.setSizePolicy(
88
+ QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding
89
+ )
63
90
  self._form_grid = QWidget(parent=self._form_grid_container)
91
+ self._form_grid.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
64
92
  self._layout.addWidget(self._form_grid_container)
65
93
  self._form_grid_container.setLayout(QVBoxLayout())
66
94
  self._form_grid.setLayout(self._new_grid_layout())
67
95
 
68
96
  self.populate()
97
+ self.enabled = self._enabled # type: ignore # QProperty
69
98
 
70
99
  def populate(self):
71
100
  self._clear_grid()
@@ -80,17 +109,20 @@ class TypedForm(BECWidget, QWidget):
80
109
  grid.addWidget(label, row, 0)
81
110
  widget = widget_from_type(item.item_type)(parent=self, spec=item)
82
111
  widget.valueChanged.connect(self.value_changed)
112
+ widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
83
113
  grid.addWidget(widget, row, 1)
84
114
 
85
- def _dict_from_grid(self) -> dict[str, str | int | float | Decimal | bool]:
115
+ def enumerate_form_widgets(self):
116
+ """Return a generator over the rows of the form, with the row number, the label widget (to
117
+ which the field name is attached as a property), and the entry widget"""
86
118
  grid: QGridLayout = self._form_grid.layout() # type: ignore
119
+ for i in range(grid.rowCount()):
120
+ yield GridRow(i, grid.itemAtPosition(i, 0).widget(), grid.itemAtPosition(i, 1).widget())
121
+
122
+ def _dict_from_grid(self) -> dict[str, DynamicFormItemType]:
87
123
  return {
88
- grid.itemAtPosition(i, 0)
89
- .widget()
90
- .property("_model_field_name"): grid.itemAtPosition(i, 1)
91
- .widget()
92
- .getValue() # type: ignore # we only add 'DynamicFormItem's here
93
- for i in range(grid.rowCount())
124
+ row.label.property("_model_field_name"): row.widget.getValue()
125
+ for row in self.enumerate_form_widgets()
94
126
  }
95
127
 
96
128
  def _clear_grid(self):
@@ -103,10 +135,13 @@ class TypedForm(BECWidget, QWidget):
103
135
  old_layout.deleteLater()
104
136
  self._form_grid.deleteLater()
105
137
  self._form_grid = QWidget()
106
-
138
+ self._form_grid.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
107
139
  self._form_grid.setLayout(self._new_grid_layout())
108
140
  self._form_grid_container.layout().addWidget(self._form_grid)
109
141
 
142
+ self.update_size()
143
+
144
+ def update_size(self):
110
145
  self._form_grid.adjustSize()
111
146
  self._form_grid_container.adjustSize()
112
147
  self.adjustSize()
@@ -114,23 +149,52 @@ class TypedForm(BECWidget, QWidget):
114
149
  def _new_grid_layout(self):
115
150
  new_grid = QGridLayout()
116
151
  new_grid.setContentsMargins(0, 0, 0, 0)
117
- new_grid.setSizeConstraint(QLayout.SizeConstraint.SetFixedSize)
118
152
  return new_grid
119
153
 
154
+ @property
155
+ def widget_dict(self):
156
+ return {
157
+ row.label.property("_model_field_name"): row.widget
158
+ for row in self.enumerate_form_widgets()
159
+ }
160
+
161
+ @SafeProperty(bool)
162
+ def enabled(self):
163
+ return self._enabled
164
+
165
+ @enabled.setter
166
+ def enabled(self, value: bool):
167
+ self._enabled = value
168
+ self.setEnabled(value)
169
+
120
170
 
121
171
  class PydanticModelForm(TypedForm):
122
172
  metadata_updated = Signal(dict)
123
173
  metadata_cleared = Signal(NoneType)
124
174
 
125
- def __init__(self, parent=None, metadata_model: type[BaseModel] = None, client=None, **kwargs):
175
+ def __init__(
176
+ self,
177
+ parent=None,
178
+ data_model: type[BaseModel] | None = None,
179
+ enabled: bool = True,
180
+ pretty_display: bool = False,
181
+ client=None,
182
+ **kwargs,
183
+ ):
126
184
  """
127
185
  A form generated from a pydantic model.
128
186
 
129
187
  Args:
130
- metadata_model (type[BaseModel]): the model class for which to generate a form.
188
+ data_model (type[BaseModel]): the model class for which to generate a form.
189
+ enabled (bool): whether fields are enabled for editing.
190
+ pretty_display (bool, optional): Whether to use a pretty display for the widget. Defaults to False. If True, disables the widget, doesn't add a clear button, and adapts the stylesheet for non-editable display.
191
+
131
192
  """
132
- self._md_schema = metadata_model
133
- super().__init__(parent=parent, form_item_specs=self._form_item_specs(), client=client)
193
+ self._pretty_display = pretty_display
194
+ self._md_schema = data_model
195
+ super().__init__(
196
+ parent=parent, form_item_specs=self._form_item_specs(), enabled=enabled, client=client
197
+ )
134
198
 
135
199
  self._validity = CompactPopupWidget()
136
200
  self._validity.compact_view = True # type: ignore
@@ -147,9 +211,24 @@ class PydanticModelForm(TypedForm):
147
211
  self._md_schema = schema
148
212
  self.populate()
149
213
 
214
+ def set_data(self, data: BaseModel):
215
+ """Fill the data for the form.
216
+
217
+ Args:
218
+ data (BaseModel): the data to enter into the form. Must be the same type as the
219
+ currently set schema, raises TypeError otherwise."""
220
+ if not self._md_schema:
221
+ raise ValueError("Schema not set - can't set data")
222
+ if not isinstance(data, self._md_schema):
223
+ raise TypeError(f"Supplied data {data} not of type {self._md_schema}")
224
+ for form_item in self.enumerate_form_widgets():
225
+ form_item.widget.setValue(getattr(data, form_item.label.property("_model_field_name")))
226
+
150
227
  def _form_item_specs(self):
151
228
  return [
152
- FormItemSpec(name=name, info=info, item_type=info.annotation)
229
+ FormItemSpec(
230
+ name=name, info=info, item_type=info.annotation, pretty_display=self._pretty_display
231
+ )
153
232
  for name, info in self._md_schema.model_fields.items()
154
233
  ]
155
234