Resolver
resolver
Config resolution with env > file > default precedence.
The resolver layers three sources for a (namespace, key) pair:
- the process environment, under a deterministic name
AXM_<NS upper, dots->underscores>_<KEY upper>; - the
[ns]section of the single~/.axm/config.toml(via :class:axm_config.store.NamespaceStore); - an explicit
default.
:func:get / :func:set_ are the bare key-value surface; :func:load
populates a consumer's pydantic model, resolving each field by name and
raising :class:ConfigError when a required field stays unresolved.
Note: an environment value is returned as the raw str from
os.environ. Type coercion happens only in :func:load, where pydantic
validates the assembled mapping.
ConfigError
UnsafeHomeError
Bases: ConfigError
Raised when ~/.axm cannot be used safely (e.g. a HOME in a git repo).
A :class:ConfigError subclass so every consumer surface that already
catches :class:ConfigError (the CLI, :func:load) degrades cleanly
instead of leaking the raw ValueError from
:func:axm_config.home.resolve_safe. The security refusal itself is
intentional; only its type is narrowed here so callers can handle it.
Source code in packages/axm-config/src/axm_config/resolver.py
delete(namespace, key)
Remove key from the [namespace] section of config.toml (no-op if absent).
namespace and key are validated first. Deleting an absent key (or a
namespace with no file) is a silent no-op — it never raises. After removal
the key resolves through the lower layers again (env, then default).
Source code in packages/axm-config/src/axm_config/resolver.py
get(namespace, key, *, default=None)
Return the resolved value for key in namespace.
Precedence is env > file > default. An env value is returned as the
raw str from the environment; file values keep their TOML-parsed type.
Source code in packages/axm-config/src/axm_config/resolver.py
load(namespace, model)
Build model from namespace, resolving each field by name.
Every field of model is resolved via :func:get (the field name is
the config key). Unresolved fields are omitted so pydantic applies the
field default; a required field that stays unresolved raises
:class:ConfigError instead of a raw ValidationError.
Source code in packages/axm-config/src/axm_config/resolver.py
resolve(ns, key, default=None)
Resolve key in ns with env > file > default precedence.
Validates ns and key at this boundary (covers :func:get and
:func:load), then returns the env value (raw str) if set, else the
file value from the namespace store, else default.
Source code in packages/axm-config/src/axm_config/resolver.py
set_(namespace, key, value)
Persist key = value in the [namespace] section of config.toml.
namespace and key are validated against the safe-segment pattern
first (path-traversal guard). A value of None is routed to
:func:delete — TOML cannot encode None, so deleting the key is the
well-defined contract rather than a raw TypeError. Otherwise delegates
to :meth:NamespaceStore.write (atomic, 0600, other keys preserved).
Source code in packages/axm-config/src/axm_config/resolver.py
validate_segment(value, *, kind='segment')
Return value if it is a safe config segment, else raise ConfigError.
A segment is a namespace or a key: the single entry-point guard
against path traversal and env-name ambiguity. It must be a non-empty
str matching its kind's pattern — no path separators, no ..
traversal, no NUL byte — so it can never widen the on-disk
~/.axm/<ns>.toml path. Both patterns are lowercase-only (no
upper-case): the env-name surface upper-cases the segments, so accepting
both "Demo" and "demo" would let two distinct namespaces fold to
the same AXM_DEMO_* prefix — forbidding upper-case makes that
collision unrepresentable. The patterns differ by kind: a
"namespace" (:data:_NAMESPACE_RE) is lowercase-alphanumeric segments
joined by dots — no _ and no - — whereas a "key"
(:data:_KEY_RE) is lowercase-alphanumeric segments joined by single
_ (no ./-, no leading/trailing _, no doubled __) so the
derived env name stays POSIX-valid and the ns/key boundary is
unambiguous: only the namespace's dot-fold yields __, the key can
never forge one, and the lone single _ separates the folded namespace
from the key. Any other kind falls back to the namespace pattern.
Shared with every public boundary (and reused by the env-name surface) so
validation is declared exactly once.