from __future__ import annotations import copy import json from pathlib import Path from typing import Any, Dict import yaml def load_yaml(path: Path) -> Dict[str, Any]: with path.open("r", encoding="utf-8") as handle: payload = yaml.safe_load(handle) return payload or {} def deep_merge(base: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]: merged = copy.deepcopy(base) for key, value in update.items(): if isinstance(value, dict) and isinstance(merged.get(key), dict): merged[key] = deep_merge(merged[key], value) else: merged[key] = copy.deepcopy(value) return merged def set_dot_key(target: Dict[str, Any], dot_key: str, value: Any) -> None: parts = dot_key.split(".") current = target for part in parts[:-1]: if part not in current or not isinstance(current[part], dict): current[part] = {} current = current[part] current[parts[-1]] = value def apply_dot_overrides(target: Dict[str, Any], overrides: Dict[str, Any]) -> Dict[str, Any]: out = copy.deepcopy(target) for dot_key, value in overrides.items(): set_dot_key(out, dot_key, value) return out def parse_overrides_json(raw: str | None) -> Dict[str, Any]: if not raw: return {} payload = json.loads(raw) if not isinstance(payload, dict): raise ValueError("Overrides JSON must be an object.") return payload