Source code for fable_pyculator.controls

"""Jupyter widget controls for FABLE scenario inputs.

The widgets in this module are intentionally thin. They collect values for curated scalar parameters
and FABLE-C selection controls, then hand those values back to
:class:`~fable_pyculator.spec.FableCalculatorSpec` for conversion into generated-model cell inputs.
"""

from __future__ import annotations

from typing import Any

from fable_pyculator.spec import FableCalculatorSpec, ScenarioParameter


[docs] class ScenarioControlSurface: """Small ipywidgets-backed control surface for a FABLE scenario spec. The surface displays scalar parameters and mutually exclusive selection controls. It does not yet render editable widgets for the detailed ``SCENARIOS definition`` tables. """ def __init__(self, spec: FableCalculatorSpec) -> None: self.spec = spec widgets = _load_widgets() self._controls = { **{parameter.name: _parameter_widget(widgets, parameter) for parameter in spec.parameters}, **{control.name: _selection_widget(widgets, control) for control in spec.selection_controls}, } self.widget = widgets.VBox(list(self._controls.values()))
[docs] def values(self) -> dict[str, object]: """Return current widget values keyed by spec input name.""" return {name: control.value for name, control in self._controls.items()}
[docs] def set_values(self, values: dict[str, object]) -> None: """Set current widget values by spec input name.""" unknown = sorted(set(values) - set(self._controls)) if unknown: raise KeyError(f"unknown scenario parameter(s): {', '.join(unknown)}") for name, value in values.items(): self._controls[name].value = value
def _ipython_display_(self) -> None: display = _load_display() display(self.widget)
def _parameter_widget(widgets: Any, parameter: ScenarioParameter) -> Any: description = parameter.label if parameter.choices: return widgets.Dropdown( options=list(parameter.choices), value=parameter.default if parameter.default is not None else parameter.choices[0], description=description, ) if parameter.kind == "boolean": return widgets.Checkbox(value=bool(parameter.default), description=description) if parameter.kind == "number": value = 0 if parameter.default is None else parameter.default return widgets.FloatText(value=value, description=description) return widgets.Text(value="" if parameter.default is None else str(parameter.default), description=description) def _selection_widget(widgets: Any, control: Any) -> Any: return widgets.Dropdown( options=[(option.label or option.value, option.value) for option in control.options], value=control.default, description=control.label, ) def _load_widgets() -> Any: try: import ipywidgets as widgets except ImportError as error: raise RuntimeError("Install fable-pyculator[notebook] to use ScenarioControlSurface.") from error return widgets def _load_display() -> Any: try: from IPython.display import display except ImportError as error: raise RuntimeError("Install fable-pyculator[notebook] to display ScenarioControlSurface.") from error return display