-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathconfig.py
More file actions
89 lines (72 loc) · 2.39 KB
/
Copy pathconfig.py
File metadata and controls
89 lines (72 loc) · 2.39 KB
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
80
81
82
83
84
85
86
87
88
89
from pathlib import Path
import os
import yaml
def load_global_config(cfg_path: str | None = None) -> dict:
"""
Load global_config.yml.
Priority:
1) explicit cfg_path
2) env GLOBAL_CONFIG_YML
3) ./global_config.yml (project root = current working dir)
"""
if cfg_path is None:
cfg_path = os.environ.get("GLOBAL_CONFIG_YML", "global_config.yml")
cfg_file = Path(cfg_path)
if not cfg_file.exists():
raise FileNotFoundError(f"global config not found: {cfg_file.resolve()}")
with cfg_file.open("r", encoding="utf-8") as f:
return yaml.safe_load(f) or {}
# from __future__ import annotations
from functools import reduce
from operator import getitem
from pathlib import Path
import os
from typing import Any, Callable, Iterable, Sequence
def deep_get(d: Any, keys: Sequence[Any], default: Any = None) -> Any:
"""Safely get nested value: deep_get(cfg, ['a','b','c'])"""
try:
return reduce(getitem, keys, d)
except (KeyError, TypeError):
return default
def coalesce(*vals: Any) -> Any:
"""Return the first non-empty (not None and not '') value."""
for v in vals:
if v is not None and v != "":
return v
return None
def cfg_value(
cfg: dict,
*keypaths: Sequence[Any],
default: Any = None,
required: bool = False,
cast: Callable[[Any], Any] | None = None,
post: Callable[[Any], Any] | None = None,
) -> Any:
"""
Generic config getter with priority:
cfg_value(cfg, ['a','b'], ['x','y'], default=..., cast=int, ...)
- tries keypaths in order
- falls back to default
- optional required, cast, post
"""
val = coalesce(*(deep_get(cfg, kp, None) for kp in keypaths), default)
if required and (val is None or val == ""):
raise KeyError(f"Missing required config value for keypaths={keypaths}")
if cast is not None and val is not None:
val = cast(val)
if post is not None and val is not None:
val = post(val)
return val
from pathlib import Path
import os
def normalize_path(p):
return str(Path(os.path.expandvars(os.path.expanduser(p))))
def cfg_get(cfg, *keypaths, default=None, required=False, cast=None):
return cfg_value(
cfg,
*keypaths,
default=default,
required=required,
cast=cast,
post=normalize_path if cast is str or default is not None else None,
)