Files
strepsiades 4a9c444b10 first commit
2026-03-09 16:31:42 +08:00

119 lines
4.1 KiB
Python

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