"""Resolved profile bindings, derived from the GUI dataclass field declarations.""" from __future__ import annotations from dataclasses import dataclass, fields, is_dataclass from typing import Iterator from python_app.models.gui_profile_schema import GuiStateModel, Scalar, Setting @dataclass(frozen=True, slots=True) class BoundSetting: """A declaration resolved to its persistent field and processing page.""" path: tuple[str, ...] spec: Setting mode: str | None def read(self, gui: GuiStateModel) -> Scalar: value: object = gui for name in self.path: value = getattr(value, name) if not isinstance(value, (str, int, float)): raise TypeError(f"Setting {'.'.join(self.path)} must be a scalar") return value def write(self, gui: GuiStateModel, value: Scalar) -> None: owner = gui for name in self.path[:-1]: owner = getattr(owner, name) setattr(owner, self.path[-1], value) def _settings(model: object, path: tuple[str, ...] = ()) -> Iterator[BoundSetting]: if not is_dataclass(model) or isinstance(model, type): return for member in fields(model): member_path = (*path, member.name) spec = member.metadata.get("setting") if isinstance(spec, Setting): mode = member_path[1] if member_path[0] == "processing" and len(member_path) == 3 else None yield BoundSetting(member_path, spec, mode) else: yield from _settings(getattr(model, member.name), member_path) GUI_FIELDS = tuple(_settings(GuiStateModel())) def active_live_fields(mode: str) -> tuple[BoundSetting, ...]: """Use the selected page for shared fields; retain other fields' own values.""" selected: dict[str, BoundSetting] = {} for binding in GUI_FIELDS: name = binding.spec.live if name and (name not in selected or binding.mode == mode): selected[name] = binding return tuple(selected.values()) def active_web_fields(mode: str) -> tuple[BoundSetting, ...]: """Expose only settings belonging to the current processing page.""" return tuple(binding for binding in GUI_FIELDS if binding.spec.web and binding.mode in (None, mode))