"""YAML configs with `extends:` inheritance and dotted CLI overrides.""" from __future__ import annotations import copy from pathlib import Path from typing import Any import yaml def deep_merge(base: dict, override: dict) -> dict: out = copy.deepcopy(base) for k, v in override.items(): if isinstance(v, dict) and isinstance(out.get(k), dict): out[k] = deep_merge(out[k], v) else: out[k] = copy.deepcopy(v) return out def _load(path: Path) -> dict: cfg = yaml.safe_load(path.read_text()) or {} parent = cfg.pop("extends", None) return deep_merge(_load(path.parent / parent), cfg) if parent else cfg def load_config(path: str | Path, overrides: list[str] | None = None) -> dict: path = Path(path) raw = yaml.safe_load(path.read_text()) or {} cfg = _load(path) cfg["name"] = raw.get("name", path.stem) for ov in overrides or []: key, _, val = ov.partition("=") set_dotted(cfg, key.strip(), _parse_value(val)) return cfg def _parse_value(val: str) -> Any: v = yaml.safe_load(val) if isinstance(v, str): # YAML 1.1 reads "3e-4" as a string try: return float(v) except ValueError: pass return v def set_dotted(cfg: dict, key: str, value: Any) -> None: node = cfg parts = key.split(".") for p in parts[:-1]: node = node.setdefault(p, {}) node[parts[-1]] = value def get_dotted(cfg: dict, key: str, default: Any = None) -> Any: node = cfg for p in key.split("."): if not isinstance(node, dict) or p not in node: return default node = node[p] return node