File size: 1,976 Bytes
a04730e | 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | from __future__ import annotations
from pathlib import Path
from typing import Any
import yaml
def load_yaml(path: str | Path) -> dict[str, Any]:
path = Path(path)
with open(path, "r", encoding="utf-8") as f:
return yaml.safe_load(f)
def deep_update(base: dict, update: dict) -> dict:
"""
Recursively merge update into base.
"""
for key, value in update.items():
if (
key in base
and isinstance(base[key], dict)
and isinstance(value, dict)
):
deep_update(base[key], value)
else:
base[key] = value
return base
def load_config(config_path: str | Path) -> dict[str, Any]:
"""
Load a config file and recursively merge files listed under `defaults`.
Example:
defaults:
- ../paths.yaml
- ../data/coco_256.yaml
"""
config_path = Path(config_path)
cfg = load_yaml(config_path)
merged: dict[str, Any] = {}
defaults = cfg.pop("defaults", [])
for default_path in defaults:
default_file = (config_path.parent / default_path).resolve()
default_cfg = load_config(default_file)
merged = deep_update(merged, default_cfg)
merged = deep_update(merged, cfg)
return merged
def get_by_key(cfg: dict[str, Any], key: str) -> Any:
"""
Get nested config value using dot notation.
Example:
get_by_key(cfg, "data.coco_root")
"""
value: Any = cfg
for part in key.split("."):
value = value[part]
return value
def resolve_path_key(cfg: dict[str, Any], key: str) -> Path:
"""
Resolve a path stored in config using dot notation.
"""
return Path(get_by_key(cfg, key)).expanduser().resolve()
def save_yaml(data: dict, path: str | Path) -> None:
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
yaml.safe_dump(data, f, sort_keys=False) |