Skip to content

Commit 12eb7b5

Browse files
committed
feat(forms): unified pydantic and scan control adapter for pydantic models
1 parent 9d7810e commit 12eb7b5

15 files changed

Lines changed: 920 additions & 702 deletions

bec_widgets/cli/client.py

Lines changed: 4 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ class _WidgetsEnumType(str, enum.Enum):
3333
"BECShell": "BECShell",
3434
"BECStatusBox": "BECStatusBox",
3535
"BeamlineStateManager": "BeamlineStateManager",
36-
"BeamlineStatePill": "BeamlineStatePill",
3736
"BecConsole": "BecConsole",
3837
"DapComboBox": "DapComboBox",
3938
"DeviceBrowser": "DeviceBrowser",
@@ -737,64 +736,19 @@ def set_idle_card_background(self, enabled: "bool") -> "None":
737736
Set whether idle collapsed pills keep the status-tinted card background.
738737
"""
739738

740-
@rpc_call
741-
def refresh_states(self) -> "None":
742-
"""
743-
Fetch the latest cached available beamline states and update the list immediately.
744-
"""
745-
746739
@rpc_call
747740
def clear_filters(self) -> "None":
748741
"""
749742
None
750743
"""
751744

752745
@rpc_call
753-
def remove(self):
754-
"""
755-
Cleanup the BECConnector
756-
"""
757-
758-
@rpc_call
759-
def attach(self):
760-
"""
761-
None
762-
"""
763-
764-
@rpc_call
765-
def detach(self):
766-
"""
767-
Detach the widget from its parent dock widget (if widget is in the dock), making it a floating widget.
768-
"""
769-
770-
@rpc_timeout(None)
771-
@rpc_call
772-
def screenshot(self, file_name: "str | None" = None):
773-
"""
774-
Take a screenshot of the dock area and save it to a file.
775-
"""
776-
777-
778-
class BeamlineStatePill(RPCBase):
779-
"""Compact widget showing one BEC beamline state."""
780-
781-
_IMPORT_MODULE = "bec_widgets.widgets.services.beamline_states.beamline_state_pill"
782-
783-
@property
784-
@rpc_call
785-
def state_name(self) -> "str | None":
786-
"""
787-
Name of the BEC beamline state displayed by this pill.
788-
"""
789-
790-
@rpc_call
791-
def set_state_name(self, state_name: "str | None", title: "str | None" = None) -> "None":
746+
def state_summary(self) -> "dict[str, dict[str, str]]":
792747
"""
793-
Set the BEC beamline state this pill displays.
748+
Return the displayed beamline states with their current status and label.
794749
795-
Args:
796-
state_name: State name as published by ``AvailableBeamlineStatesMessage``.
797-
title: Optional human-readable title for the state.
750+
Returns:
751+
dict: Mapping of state name to a dictionary with ``status`` and ``label`` keys.
798752
"""
799753

800754
@rpc_call

bec_widgets/cli/designer_plugins.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@
2323
"bec_widgets.widgets.services.beamline_states.beamline_state_pill",
2424
"BeamlineStateManager",
2525
),
26-
"BeamlineStatePill": (
27-
"bec_widgets.widgets.services.beamline_states.beamline_state_pill",
28-
"BeamlineStatePill",
29-
),
3026
"BecConsole": ("bec_widgets.widgets.editors.bec_console.bec_console", "BecConsole"),
3127
"ColorButton": ("bec_widgets.widgets.utility.visual.color_button.color_button", "ColorButton"),
3228
"ColorButtonNative": (
@@ -127,7 +123,6 @@
127123
"BECSpinBox": "123",
128124
"BECStatusBox": "widgets",
129125
"BeamlineStateManager": "format_list_bulleted",
130-
"BeamlineStatePill": "info",
131126
"BecConsole": "terminal",
132127
"ColorButton": "colors",
133128
"ColorButtonNative": "colors",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Mapping
4+
from typing import Any
5+
6+
from pydantic import BaseModel
7+
from pydantic_core import PydanticUndefined
8+
9+
from bec_widgets.utils.scan_arg_metadata import ui_config_from_metadata
10+
11+
NUMERIC_BOUND_KEYS = {"gt", "ge", "lt", "le"}
12+
13+
14+
def pydantic_model_input_configs(model: type[BaseModel]) -> list[dict[str, Any]]:
15+
"""Return scan-control-style field items for a Pydantic model."""
16+
configs = []
17+
for name, info in model.model_fields.items():
18+
metadata: dict[str, Any] = {}
19+
for entry in info.metadata:
20+
for key in NUMERIC_BOUND_KEYS:
21+
value = getattr(entry, key, None)
22+
if value is not None:
23+
metadata.setdefault(key, value)
24+
25+
if isinstance(info.json_schema_extra, Mapping):
26+
metadata.update(dict(info.json_schema_extra))
27+
28+
if info.description and metadata.get("description") is None:
29+
metadata["description"] = info.description
30+
31+
default: Any
32+
if info.default is not PydanticUndefined:
33+
default = info.default
34+
elif info.default_factory is not None:
35+
default = info.get_default(call_default_factory=True)
36+
else:
37+
default = None
38+
39+
display_name = metadata.get("display_name") or info.title
40+
if display_name is None:
41+
display_name = name.replace("_", " ").capitalize()
42+
43+
item = ui_config_from_metadata(
44+
name=name, metadata=metadata, default=default, display_name=display_name
45+
)
46+
item.update({key: value for key, value in metadata.items() if key not in item})
47+
configs.append(item)
48+
49+
return configs

0 commit comments

Comments
 (0)