Configuration model
Upref deliberately has a small data model and an explicit lifecycle. An application normally performs three independent steps:
create a
ConfigStoreandload()values;optionally use
collect()to ask for values;explicitly
save()the final mapping.
Loading never opens a terminal or window. Collecting never writes a file. This separation makes startup, cancellation, testing, and error recovery predictable.
Supported values
The document root is always a mapping with string keys. Each value can be one of the following:
Python value |
YAML representation |
|---|---|
|
|
|
|
|
A YAML number |
|
A Unicode YAML string |
|
A sequence whose items are supported values |
|
A nested mapping with string keys |
Empty strings, empty containers, False, and 0 are valid values. An
empty document or explicit YAML null at the root represents an empty
mapping. A list or non-null scalar at the document root is invalid. Mapping
keys at every level must be strings.
Objects such as pathlib.Path, datetime, tuples, sets, enum
members, and application classes are not converted implicitly. Convert them
to a supported representation before calling
save().
Cycles are rejected because YAML configuration must be a finite tree. Shared mutable objects are copied independently when accepted.
Detached values and no cache
Upref validates and copies the complete value tree at its public boundaries. Inputs are never mutated, and returned dictionaries do not share nested lists or dictionaries with caller-owned defaults or changes.
Every load() reads the file again; a store has no
implicit configuration cache. Mutating a loaded dictionary therefore changes
only that Python object until save() is called.
Recursive merge rules
Defaults and updates use the same merge rules:
nested mappings are merged recursively;
a value from the overriding mapping wins;
lists and scalars are replaced in full, not concatenated;
keys found only in the overriding mapping are retained;
both inputs and the result remain independent.
For example, given these defaults and saved values:
defaults = {
"network": {"host": "localhost", "ports": [8000, 8001]},
"enabled": True,
}
saved = {
"network": {"ports": [9000]},
"enabled": False,
}
the resolved configuration is:
{
"network": {"host": "localhost", "ports": [9000]},
"enabled": False,
}
See Using ConfigStore for the difference between in-memory defaults and persisted updates.