Level 1 Composing configs by inheritance
You're writing the loader for a training-run config system (think a tiny Hydra/OmegaConf). A config is a nested dict of JSON-like values. Configs are kept in a registry configs: dict[str, dict] and may inherit from others.
Implement resolve(configs, name) -> dict and an exception class ConfigError(ValueError):
- The optional key
"_base_"in a config is a parent name or a list of parent names. The result starts empty; each parent's fully resolved config is merged on in list order; finally the config's own keys are merged on top."_base_"never appears in the output. - Deep merge: when both sides have a
dictunder the same key, merge them recursively. In every other case (scalars, lists, a dict meeting a non-dict) the later value simply replaces the earlier one. Lists are not concatenated. - Raise
ConfigErrorif a name isn't in the registry or if inheritance loops back on itself. - Never mutate the inputs, and don't let the result share mutable objects with them.
configs = {
"base": {"optim": {"name": "adam", "lr": 0.001}, "layers": [64, 64]},
"big": {"_base_": "base", "layers": [256, 256, 256]},
"sweep": {"_base_": ["big"], "optim": {"lr": 0.01}},
}
resolve(configs, "sweep")
# {"optim": {"name": "adam", "lr": 0.01}, "layers": [256, 256, 256]}
Parents can be shared (diamonds), and chains can be long. Resolve each named config once per call, not once per path that reaches it.