mini.config¶
Keep configuration logic in regular Python modules. Start with a single dictionary, then scale into parameterized templates, inheritance chains, and CLI-friendly overrides without learning a new DSL.
Everything you write stays Python: functions, conditionals, list comprehensions, imports.
mini.configfocuses on loading, layering, and mutating dictionaries so you can drop the result into any workflow.
Highlights¶
- Load any Python config module or callable with
load. - Compose configs via
parents = [...]chains or by supplying multiple paths at once. - Parameterize configs with function arguments; inject runtime values with
params. - Adjust values on the fly using
apply_overridesand a compact CLI syntax. - Control merge behavior with
Delete()andReplace(value). - Snapshot final dictionaries back to Python with
dump, or pretty-print them withformat.
Quick tour¶
-
Define a base config as a plain dictionary:
-
Extend it with parents and parameters when the project grows:
# configs/finetune.py parents = ["base.py"] def config(num_classes=10, max_steps=10_000, warmup_steps=1_000): return { "model": { "head": {"out_channels": num_classes}, }, "scheduler": { "type": "linear_warmup_cosine_decay", "warmup_steps": warmup_steps, "decay_steps": max_steps - warmup_steps, }, "trainer": {"max_steps": max_steps}, } -
Load everything and apply runtime tweaks:
-
Feed the dictionary wherever you need it—serialize to JSON, log to disk, or hand it to any factory you already use:
Anatomy of a config module¶
Each config file is just Python. The loader only pays attention to two attributes:
config: dictionary or callable returning a dictionary.parents: string or list of strings pointing to other config files (paths resolved relative to the current file).
Use a dictionary (config = {...}) when the configuration is static. Use a callable when you want parameters; default argument values become part of the config defaults automatically.
def config(batch_size: int = 64, *, device: str = "cuda"):
return {
"data": {"batch_size": batch_size},
"trainer": {"device": device},
}
Multiple parents or paths¶
Parents are loaded depth-first (left to right), so later parents override earlier ones before the child applies its updates.
# configs/experiment.py
parents = ["base.py", "schedules/cosine.py"]
config = {"trainer": {"max_epochs": 40}}
load also accepts a sequence of paths when you want to compose parts dynamically:
Runtime overrides¶
apply_overrides(config_dict, sequence_of_strings) mutates the dictionary in place. Each string uses a compact syntax designed for CLI usage.
path=value→ assign (dict keys or list indices)path+=value→ append to a listpath-=value→ remove a matching element from a listpath!=→ delete a key or remove a list index
Values are parsed with ast.literal_eval, so strings, numbers, booleans, lists, dictionaries, and None all work (as long as they do not contain whitespace). If parsing fails, the raw string is used, so quoting strings is usually not necessary.
apply_overrides(
cfg,
[
"optimizer.lr=5e-4",
"trainer.max_steps=10_000",
"trainer.hooks+='wandb'",
"trainer.hooks-='checkpoint'",
"data.pipeline[0]!=",
],
)
Merge semantics¶
When configs are layered, mini.config walks the override dictionary and combines it with the base using:
- Dicts merge recursively.
Delete()removes the key entirely.Replace(value)usesvalueas-is without deeper merging.- Otherwise the override value replaces the base.
from mini.config import Delete, Replace, merge
base = {
"optimizer": {
"lr": 3e-4,
"weight_decay": 0.01,
"schedule": {"type": "linear", "warmup": 1_000},
},
"trainer": {"hooks": ["progress", "checkpoint"]},
}
override = {
"optimizer": {
"weight_decay": Delete(),
"schedule": Replace({"type": "cosine", "t_max": 20_000}),
},
"trainer": {"steps": 10_000, "hooks": ["progress"]},
}
merged = merge(base, override)
merge is exported in case you want to reuse the algorithm, but load and apply_overrides already rely on it internally.
Formatting and snapshots¶
Freeze the exact configuration you ran:
from pathlib import Path
from mini.config import dump, format
print(format(cfg)) # Black-formatted string
dump(cfg, Path("runs/2024-01-10/config_snapshot.py"))
formatreturns a nicely formatted string—useful for logging.dumpwrites the same representation to disk with a short header and# fmt: off. Because the file is valid Python, you can load it again withload.
Tips for structuring configs¶
- Organize by concern:
configs/data/imagenet.py,configs/optim/adamw.py,configs/modes/eval.py. - Expose helper functions alongside
configfor reusable snippets. - Pair with object builders only if you want to: the output is a plain dict, so you can plug it into your own factories or formats.
- Prefer parameters over repeated overrides when you always tweak the same value.
API reference ¶
| CLASS | DESCRIPTION |
|---|---|
Delete |
Sentinel that removes a key from a merged config. |
Replace |
Sentinel that forces a value to replace a mapping during merge. |
| FUNCTION | DESCRIPTION |
|---|---|
load |
Load config modules from one or more paths, apply params, and merge the results. |
apply_overrides |
Apply CLI-style override strings to a config dictionary. |
merge |
Recursively merge two dictionaries, honoring Delete/Replace sentinels. |
dump |
Persist a config dictionary to a ruff-formatted Python file. |
format |
Return a ruff-formatted string representation of the config dictionary. |
Delete ¶
Sentinel that removes a key from a merged config.
Replace ¶
Sentinel that forces a value to replace a mapping during merge.
load ¶
Load config modules from one or more paths, apply params, and merge the results.
Parent configs (via parents) are resolved first, then later paths override
earlier ones. Callable configs receive params (defaults come from signatures);
plain dict configs are merged directly.
apply_overrides ¶
Apply CLI-style override strings to a config dictionary.
Supports assignment (=), append (+=), delete (!=), and removal from list
(-=) using dotted/indexed key paths like model.layers[0].units. Returns a
shallow copy with overrides applied.
merge ¶
Recursively merge two dictionaries, honoring Delete/Replace sentinels.
If both sides contain dicts, merge continues down the tree. Delete removes a key from the base config, Replace overwrites without further deep merging, and other values simply override. Returns a new dictionary without mutating the inputs.
dump ¶
Persist a config dictionary to a ruff-formatted Python file.
format ¶
Return a ruff-formatted string representation of the config dictionary.