| from __future__ import annotations |
| from pathlib import Path |
| from typing import Any, Dict, List |
| import copy |
| import yaml |
|
|
|
|
| def _deep_update(base: Dict[str, Any], update: Dict[str, Any]) -> Dict[str, Any]: |
| out = copy.deepcopy(base) |
| for k, v in update.items(): |
| if isinstance(v, dict) and isinstance(out.get(k), dict): |
| out[k] = _deep_update(out[k], v) |
| else: |
| out[k] = copy.deepcopy(v) |
| return out |
|
|
|
|
| def load_yaml(path: str | Path) -> Dict[str, Any]: |
| path = Path(path) |
| with open(path, "r") as f: |
| cfg = yaml.safe_load(f) or {} |
| base_files = cfg.pop("_base_", []) |
| if isinstance(base_files, str): |
| base_files = [base_files] |
| merged: Dict[str, Any] = {} |
| for base in base_files: |
| base_path = (path.parent / base).resolve() |
| merged = _deep_update(merged, load_yaml(base_path)) |
| merged = _deep_update(merged, cfg) |
| return merged |
|
|
|
|
| def save_yaml(cfg: Dict[str, Any], path: str | Path) -> None: |
| path = Path(path) |
| path.parent.mkdir(parents=True, exist_ok=True) |
| with open(path, "w") as f: |
| yaml.safe_dump(cfg, f, sort_keys=False) |
|
|
|
|
| def get(cfg: Dict[str, Any], key: str, default=None): |
| cur = cfg |
| for part in key.split("."): |
| if not isinstance(cur, dict) or part not in cur: |
| return default |
| cur = cur[part] |
| return cur |
|
|
|
|
| def set_by_path(cfg: Dict[str, Any], key: str, value: Any) -> None: |
| cur = cfg |
| parts = key.split(".") |
| for p in parts[:-1]: |
| cur = cur.setdefault(p, {}) |
| cur[parts[-1]] = value |
|
|