Store
store
Keyring-backed credential store for AXM.
:class:KeyringStore is a thin, stateless wrapper over the OS keyring
(keyring <https://github.com/jaraco/keyring>_). Secrets are stored under a
single fixed service (:data:SERVICE) and a composed username of the
form {group}.{instance?}.{name} so a credential group can host several
named values, optionally namespaced by instance (e.g. several mail accounts).
This module deliberately knows nothing about files or ~/.axm: any
disk layout (config directories, token files) is owned by axm-config.
:func:atomic_write is the single disk primitive vault exposes, for the
OAuth refresh-token rotation case where a caller — given a path by
axm-config — needs a crash-safe overwrite. It does not resolve or create
that path.
SERVICE = 'axm-vault'
module-attribute
The fixed keyring service name under which every AXM secret is stored.
KeyringStore
Store and retrieve secrets in the OS keyring under :data:SERVICE.
The store is stateless: every call delegates to the process-wide
keyring backend, so tests can swap in an in-memory backend via
keyring.set_keyring(...) without touching the real Keychain.
Source code in packages/axm-vault/src/axm_vault/store.py
delete(group, name, instance=None)
Remove the credential from the keyring (no-op if already absent).
The real macOS Keychain backend raises
:class:keyring.errors.PasswordDeleteError (Item not found) when
the credential is absent; it is suppressed here so the call is a true
no-op, as the contract promises and as rotation/cleanup callers rely on.
Source code in packages/axm-vault/src/axm_vault/store.py
get(group, name, instance=None)
Return the stored secret, or None if no such credential exists.
Raises :class:KeyringUnavailableError when no usable backend exists
(headless host) rather than surfacing a raw backend traceback.
Source code in packages/axm-vault/src/axm_vault/store.py
set(group, name, value, instance=None)
Store value under (group, name[, instance]) in the keyring.
Raises :class:KeyringUnavailableError when no usable backend exists
(headless host) rather than surfacing a raw backend traceback.
Source code in packages/axm-vault/src/axm_vault/store.py
username(group, name, instance=None)
staticmethod
Compose the keyring username for a credential.
The instance segment is omitted cleanly when instance is None
({group}.{name}) and inserted between group and name otherwise
({group}.{instance}.{name}).
. is the structural separator, so each segment is percent-escaped
before joining: a literal . (or %) inside any segment is
encoded so that distinct (group, name, instance) tuples can never
collapse onto the same username (e.g. ("a.b", "c") vs
("a", "b.c")). The encoding is reversible and leaves dot-free
segments untouched, so existing usernames are unchanged and the
reserved .prev rotation slot stays distinct from its base name.
Source code in packages/axm-vault/src/axm_vault/store.py
KeyringUnavailableError
Bases: RuntimeError
The OS keyring backend is unavailable (headless host / no Keychain).
Raised in place of a raw backend traceback (e.g. keyring reporting
No recommended backend was available) so callers can degrade the
keyring layer gracefully. The message is fixed and actionable; it never
carries a credential value (never-leak invariant).
Source code in packages/axm-vault/src/axm_vault/store.py
atomic_write(path, data, *, encoding='utf-8')
Write data to path atomically (temp file + os.replace).
The write goes to a temporary file in the destination directory, is flushed
and fsync-ed, then atomically renamed over path so a concurrent
reader never observes a partially written file. After the rename the parent
directory is itself fsync-ed so the new directory entry is durable —
without that, a crash right after os.replace could lose the rename even
though the file content reached disk. The destination directory must
already exist — vault does not create it (that is axm-config's
responsibility). Intended for the OAuth refresh-token rotation case.
Source code in packages/axm-vault/src/axm_vault/store.py
rotate_secret(group, name, value, instance=None)
Rotate a keyring secret, retaining the previous value for one cycle.
The prior cycle's backup is purged first, the current value (if any) is
copied to the reserved {name}.prev slot, then value is written over
{name}. Retention is strictly one cycle: a stale .prev never
lingers across rotations. Keeping the previous secret one rotation lets a
caller fall back during an in-flight credential roll. No value is ever
returned or logged.
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
KeyringUnavailableError
|
when the OS keyring backend is unavailable. |