from __future__ import annotations from enum import Enum from typing import Any, Optional, Set, Type, TypeVar, cast from ..common.sync_node import SyncNode from ..common.types import BindingStatus, SyncAction, SyncStatus from .dispatcher import StateMachineRuntime E = TypeVar("E", bound=Enum) def _state_def(runtime: StateMachineRuntime, state_id: str) -> dict[str, Any]: states = runtime._config.raw.get("states") if not isinstance(states, dict): raise RuntimeError("invalid state-machine config: missing states") state_def = states.get(state_id) if not isinstance(state_def, dict): raise RuntimeError(f"unknown state in config: {state_id}") return state_def def _to_enum(enum_cls: Type[E], raw_value: Any, field_name: str, state_id: str) -> E: if not isinstance(raw_value, str): raise RuntimeError( f"invalid {field_name} for {state_id}: expected str/list[str], got {type(raw_value).__name__}" ) try: return cast(E, enum_cls(raw_value)) except Exception: raw_lower = str(raw_value).lower() for member in enum_cls: if str(member.value).lower() == raw_lower or str(member.name).lower() == raw_lower: return cast(E, member) raise RuntimeError(f"invalid {field_name} value for {state_id}: {raw_value}") def _resolve_enum_value( *, state_id: str, field_name: str, raw_value: Any, enum_cls: Type[E], current_value: E, ) -> E: if isinstance(raw_value, str): return _to_enum(enum_cls, raw_value, field_name, state_id) if isinstance(raw_value, list): candidates = [_to_enum(enum_cls, v, field_name, state_id) for v in raw_value] if not candidates: raise RuntimeError(f"empty {field_name} candidates for {state_id}") if current_value in candidates: return current_value if len(candidates) == 1: return candidates[0] raise RuntimeError( f"ambiguous {field_name} for {state_id}: candidates={[c.value for c in candidates]}, " f"current={current_value.value}" ) raise RuntimeError( f"invalid {field_name} for {state_id}: expected str/list[str], got {type(raw_value).__name__}" ) def apply_state_to_node( runtime: StateMachineRuntime, node: SyncNode, *, state_id: Optional[str], reason: str = "", fields: Optional[Set[str]] = None, data_id: Optional[str] = None, ) -> bool: if not state_id: return False state_def = _state_def(runtime, state_id) selected_fields = fields or {"binding_status", "action", "status"} with node.allow_core_state_write(source="state_machine"): if "binding_status" in selected_fields and "binding_status" in state_def: target_binding = _resolve_enum_value( state_id=state_id, field_name="binding_status", raw_value=state_def["binding_status"], enum_cls=BindingStatus, current_value=node.binding_status, ) node.set_binding_status(target_binding, reason) if "action" in selected_fields and "action" in state_def: target_action = _resolve_enum_value( state_id=state_id, field_name="action", raw_value=state_def["action"], enum_cls=SyncAction, current_value=node.action, ) node.set_action(target_action, reason) if "status" in selected_fields and "status" in state_def: target_status = _resolve_enum_value( state_id=state_id, field_name="status", raw_value=state_def["status"], enum_cls=SyncStatus, current_value=node.status, ) node.set_status(target_status, reason) if "data_id" in selected_fields: if data_id is None: raise RuntimeError(f"state {state_id} requested data_id update but no data_id provided") node.set_data_id(data_id, reason) return True