Using ConfigStore
ConfigStore represents one YAML file. Constructing a store
validates and resolves its path, but does not create a directory or read the
file.
from upref import ConfigStore
store = ConfigStore(
"my-application",
filename="config.yaml",
app_author="Example Corp",
roaming=False,
)
The app_name,
filename, and path
properties expose the resolved identity. See Configuration paths for platform and
portable locations.
Loading values
Call load() whenever the application needs a fresh
snapshot:
config = store.load()
A missing, zero-byte, whitespace-only, comment-only, or explicit YAML null
document represents an empty mapping. Valid YAML is decoded as UTF-8 and
normalized to the supported value model. Invalid YAML,
invalid UTF-8, a non-mapping root, and non-string keys are reported rather than
silently replaced.
PyYAML accepts duplicate mapping keys and keeps the last occurrence. Upref does not currently add a duplicate-key check, so applications that permit manual editing should treat duplicate keys as ambiguous and may validate them with a stricter YAML loader before calling Upref.
Using defaults
Pass defaults when the application has values that need not be present in the file:
config = store.load(
defaults={
"theme": "dark",
"network": {"host": "localhost", "port": 8080},
}
)
Saved values override defaults according to the recursive merge rules. Defaults are copied and never modified. Supplying defaults
does not write them; call store.save(config) if the fully resolved
mapping should become persistent.
Saving the complete mapping
save() validates the complete mapping and replaces
the complete YAML document:
config["theme"] = "light"
store.save(config)
Validation and YAML serialization happen before the destination directory or temporary file is created. Unsupported values therefore cannot damage an existing configuration. A valid save attempt creates the parent directory as needed before opening its temporary file; the directory can therefore remain even if a later filesystem operation fails.
The document is written in UTF-8 with Unicode characters preserved and keys kept in insertion order. YAML comments, anchors, aliases, quoting choices, and custom formatting are not part of the configuration model and are not preserved on save.
Applying selected changes
update() is a convenience read, recursive merge, and
save operation:
updated = store.update(
{
"network": {"port": 9000},
"notifications": False,
}
)
The returned mapping is exactly the merged mapping that was saved. Existing nested keys are retained, while lists and scalar values are replaced. If the file does not exist, the normalized changes become its initial content.
update does not accept defaults. Load and save explicitly when defaults
must participate in the persisted result:
config = store.load(defaults=application_defaults)
config["theme"] = "light"
store.save(config)
Existence and deletion
exists() reports whether the resolved path currently
identifies a regular file. delete() deletes only that
file and returns True when a file was removed or False when it was
already absent:
if store.exists():
removed = store.delete()
These are separate filesystem operations. Another process may change the
file between an exists check and a later load, save, or delete, so callers
should still handle the operation’s documented exceptions.
Atomicity and concurrent access
Saving uses this sequence:
validate and serialize all data in memory;
create a uniquely named temporary file in the destination directory;
write UTF-8 YAML, flush Python and operating-system file buffers, and call
fsync;on POSIX, set the temporary file mode to
0600;atomically replace the destination with
os.replace;attempt to remove any remaining temporary file after failure.
Because the temporary file is on the same filesystem, readers see either the old complete document or the new complete document, not a partially written one. If replacement fails, the previous file remains available. A uniquely named temporary file can remain if both the primary operation and best-effort cleanup fail; Upref never treats that file as configuration.
Upref deliberately provides no process or thread lock. The last successful
os.replace wins. In particular, two simultaneous update calls can both
read the same old value and one can overwrite the other’s changes.
Applications that need coordinated read-modify-write transactions must place
an appropriate lock around the whole operation.
Atomic replacement also does not turn YAML into a database: it provides file integrity, not conflict detection, revision history, or multi-file transactions.