mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-13 19:21:50 +02:00
feat: generate combobox for literal str
This commit is contained in:
@ -109,7 +109,7 @@ class TypedForm(BECWidget, QWidget):
|
|||||||
label.setProperty("_model_field_name", item.name)
|
label.setProperty("_model_field_name", item.name)
|
||||||
label.setToolTip(item.info.description or item.name)
|
label.setToolTip(item.info.description or item.name)
|
||||||
grid.addWidget(label, row, 0)
|
grid.addWidget(label, row, 0)
|
||||||
widget = self._widget_from_type(item.item_type, self._widget_types)(parent=self, spec=item)
|
widget = self._widget_from_type(item, self._widget_types)(parent=self, spec=item)
|
||||||
widget.valueChanged.connect(self.value_changed)
|
widget.valueChanged.connect(self.value_changed)
|
||||||
widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
|
||||||
grid.addWidget(widget, row, 1)
|
grid.addWidget(widget, row, 1)
|
||||||
|
@ -4,13 +4,14 @@ import typing
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from types import GenericAlias, UnionType
|
from types import GenericAlias, UnionType
|
||||||
from typing import Callable, Final, Generic, Literal, NamedTuple, TypeVar
|
from typing import Callable, Final, Generic, Literal, NamedTuple, OrderedDict, TypeVar, get_args
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
from bec_qthemes import material_icon
|
from bec_qthemes import material_icon
|
||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||||
from pydantic.fields import FieldInfo
|
from pydantic.fields import FieldInfo
|
||||||
from pydantic_core import PydanticUndefined
|
from pydantic_core import PydanticUndefined
|
||||||
|
from PySide6.QtWidgets import QComboBox
|
||||||
from qtpy.QtCore import Signal # type: ignore
|
from qtpy.QtCore import Signal # type: ignore
|
||||||
from qtpy.QtWidgets import (
|
from qtpy.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
@ -435,39 +436,75 @@ class ListFormItem(DynamicFormItem):
|
|||||||
self._repop(value)
|
self._repop(value)
|
||||||
|
|
||||||
|
|
||||||
WidgetTypeRegistry = dict[
|
class StrLiteralFormItem(DynamicFormItem):
|
||||||
str, tuple[Callable[[type | UnionType | None], bool], type[DynamicFormItem]]
|
def _add_main_widget(self) -> None:
|
||||||
]
|
self._main_widget = QComboBox()
|
||||||
|
self._options = get_args(self._spec.info.annotation)
|
||||||
|
for opt in self._options:
|
||||||
|
self._main_widget.addItem(opt)
|
||||||
|
self._layout.addWidget(self._main_widget)
|
||||||
|
|
||||||
DEFAULT_WIDGET_TYPES: Final[WidgetTypeRegistry] = {
|
def getValue(self):
|
||||||
"str": (lambda anno: anno in [str, str | None, None], StrFormItem),
|
return self._main_widget.currentText()
|
||||||
"int": (lambda anno: anno in [int, int | None], IntFormItem),
|
|
||||||
|
def setValue(self, value: str | None):
|
||||||
|
if value is None:
|
||||||
|
self.clear()
|
||||||
|
for i in range(self._main_widget.count()):
|
||||||
|
if self._main_widget.itemText(i) == value:
|
||||||
|
self._main_widget.setCurrentIndex(i)
|
||||||
|
return
|
||||||
|
raise ValueError(f"Cannot set value: {value}, options are: {self._options}")
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._main_widget.setCurrentIndex(-1)
|
||||||
|
|
||||||
|
|
||||||
|
WidgetTypeRegistry = OrderedDict[str, tuple[Callable[[FormItemSpec], bool], type[DynamicFormItem]]]
|
||||||
|
|
||||||
|
DEFAULT_WIDGET_TYPES: Final[WidgetTypeRegistry] = OrderedDict() | {
|
||||||
|
# dict literals are ordered already but TypedForm subclasses may modify coppies of this dict
|
||||||
|
# and delete/insert keys or change the order
|
||||||
|
"literal_str": (
|
||||||
|
lambda spec: type(spec.info.annotation) is type(Literal[""])
|
||||||
|
and set(type(arg) for arg in get_args(spec.info.annotation)) == {str},
|
||||||
|
StrLiteralFormItem,
|
||||||
|
),
|
||||||
|
"str": (lambda spec: spec.item_type in [str, str | None, None], StrFormItem),
|
||||||
|
"int": (lambda spec: spec.item_type in [int, int | None], IntFormItem),
|
||||||
"float_decimal": (
|
"float_decimal": (
|
||||||
lambda anno: anno in [float, float | None, Decimal, Decimal | None],
|
lambda spec: spec.item_type in [float, float | None, Decimal, Decimal | None],
|
||||||
FloatDecimalFormItem,
|
FloatDecimalFormItem,
|
||||||
),
|
),
|
||||||
"bool": (lambda anno: anno in [bool, bool | None], BoolFormItem),
|
"bool": (lambda spec: spec.item_type in [bool, bool | None], BoolFormItem),
|
||||||
"dict": (
|
"dict": (
|
||||||
lambda anno: anno in [dict, dict | None]
|
lambda spec: spec.item_type in [dict, dict | None]
|
||||||
or (isinstance(anno, GenericAlias) and anno.__origin__ is dict),
|
or (isinstance(spec.item_type, GenericAlias) and spec.item_type.__origin__ is dict),
|
||||||
DictFormItem,
|
DictFormItem,
|
||||||
),
|
),
|
||||||
"list": (
|
"list": (
|
||||||
lambda anno: anno in [list, list | None]
|
lambda spec: spec.item_type in [list, list | None]
|
||||||
or (isinstance(anno, GenericAlias) and anno.__origin__ is list),
|
or (isinstance(spec.item_type, GenericAlias) and spec.item_type.__origin__ is list),
|
||||||
ListFormItem,
|
ListFormItem,
|
||||||
),
|
),
|
||||||
|
"set": (
|
||||||
|
lambda spec: spec.item_type in [set, set | None]
|
||||||
|
or (isinstance(spec.item_type, GenericAlias) and spec.item_type.__origin__ is set),
|
||||||
|
SetFormItem,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def widget_from_type(
|
def widget_from_type(
|
||||||
annotation: type | UnionType | None, widget_types: WidgetTypeRegistry | None = None
|
spec: FormItemSpec, widget_types: WidgetTypeRegistry | None = None
|
||||||
) -> type[DynamicFormItem]:
|
) -> type[DynamicFormItem]:
|
||||||
widget_types = widget_types or DEFAULT_WIDGET_TYPES
|
widget_types = widget_types or DEFAULT_WIDGET_TYPES
|
||||||
for predicate, widget_type in widget_types.values():
|
for predicate, widget_type in widget_types.values():
|
||||||
if predicate(annotation):
|
if predicate(spec):
|
||||||
return widget_type
|
return widget_type
|
||||||
logger.warning(f"Type {annotation} is not (yet) supported in metadata form creation.")
|
logger.warning(
|
||||||
|
f"Type {spec.item_type=} / {spec.info.annotation=} is not (yet) supported in dynamic form creation."
|
||||||
|
)
|
||||||
return StrFormItem
|
return StrFormItem
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,22 +126,28 @@ class DeviceConfigDialog(BECWidget, QDialog):
|
|||||||
logger.info("No changes made to device config")
|
logger.info("No changes made to device config")
|
||||||
return
|
return
|
||||||
logger.info(f"Sending request to update device config: {config}")
|
logger.info(f"Sending request to update device config: {config}")
|
||||||
try:
|
|
||||||
self._start_waiting_display()
|
self._start_waiting_display()
|
||||||
RID = self._config_helper.send_config_request(
|
|
||||||
action="update", config={self._device: config}, wait_for_response=False
|
def _communicate_update():
|
||||||
)
|
try:
|
||||||
reply = self._config_helper.wait_for_config_reply(
|
RID = self._config_helper.send_config_request(
|
||||||
RID, timeout=self._config_helper.suggested_timeout_s(config)
|
action="update", config={self._device: config}, wait_for_response=False
|
||||||
)
|
)
|
||||||
self._config_helper.handle_update_reply(reply, RID)
|
logger.info("Waiting for config reply")
|
||||||
self._stop_waiting_display()
|
reply = self._config_helper.wait_for_config_reply(
|
||||||
except Exception as e:
|
RID, timeout=self._config_helper.suggested_timeout_s(config)
|
||||||
self._stop_waiting_display()
|
)
|
||||||
logger.error(f"Error updating config: \n {''.join(traceback.format_exception(e))}")
|
logger.info("Handling config reply")
|
||||||
finally:
|
self._config_helper.handle_update_reply(reply, RID)
|
||||||
self._fetch_config()
|
except Exception as e:
|
||||||
self._fill_form()
|
self._stop_waiting_display()
|
||||||
|
logger.error(f"Error updating config: \n {''.join(traceback.format_exception(e))}")
|
||||||
|
finally:
|
||||||
|
self._fetch_config()
|
||||||
|
self._fill_form()
|
||||||
|
|
||||||
|
Thread(target=_communicate_update).start()
|
||||||
|
|
||||||
def _start_waiting_display(self):
|
def _start_waiting_display(self):
|
||||||
self._overlay_widget.setVisible(True)
|
self._overlay_widget.setVisible(True)
|
||||||
|
@ -30,8 +30,11 @@ class DeviceConfigForm(PydanticModelForm):
|
|||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
self._widget_types = DEFAULT_WIDGET_TYPES.copy()
|
self._widget_types = DEFAULT_WIDGET_TYPES.copy()
|
||||||
self._widget_types["bool"] = (lambda anno: anno is bool, BoolToggleFormItem)
|
self._widget_types["bool"] = (lambda spec: spec.item_type is bool, BoolToggleFormItem)
|
||||||
self._widget_types["optional_bool"] = (lambda anno: anno == bool | None, BoolFormItem)
|
self._widget_types["optional_bool"] = (
|
||||||
|
lambda spec: spec.item_type == bool | None,
|
||||||
|
BoolFormItem,
|
||||||
|
)
|
||||||
self._validity.setVisible(False)
|
self._validity.setVisible(False)
|
||||||
self._connect_to_theme_change()
|
self._connect_to_theme_change()
|
||||||
self.populate()
|
self.populate()
|
||||||
|
Reference in New Issue
Block a user