File size: 1,563 Bytes
d65ae7d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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
|