Config
- Start here: Reconfigure from a ConfigMap
- Common recipes: add
ExternalConfig("/etc/grelmicro")toGrelmicro(uses=[...])to keep live components in sync with a mounted ConfigMap or Secret. Callreload()in tests for a deterministic apply pass.
grelmicro.config
Externalized configuration: reconfigure live components from a source.
ExternalConfig
ExternalConfig(
config: ConfigBackend
| str
| PathLike[str]
| None = None,
*,
secrets: ConfigBackend
| str
| PathLike[str]
| None = None,
reload_interval: float = _DEFAULT_INTERVAL,
)
Reconfigure live components from an external source.
Implements the Externalized Configuration pattern: configuration lives
outside the image and is applied at runtime. Add it to a Grelmicro
app and every component that resolves from the environment is kept in
sync with a mounted ConfigMap, Secret, or any other
ConfigBackend, with no
per-component wiring.
from grelmicro import Grelmicro, ExternalConfig
from grelmicro.coordination import Coordination, Lock
from grelmicro.coordination.redis import RedisLockAdapter
ledger_lock = Lock("ledger")
micro = Grelmicro(uses=[
Coordination(RedisLockAdapter()),
ExternalConfig(
config="/etc/grelmicro/config",
secrets="/etc/grelmicro/secrets",
),
])
Config and secrets are separate sources so sensitive values live in a
Secret and the rest in a ConfigMap, the same split the platform makes.
Both carry the same GREL_... keys components read from the
environment. On a key collision the secret wins.
A str or path is routed by scheme: a local path uses
FileConfigAdapter, an
http(s):// or git URL uses the matching adapter.
The source is applied once when the app opens, then polled. Only keys
present in the source are applied, so a generated lock worker and any
field the source omits keep their value. An invalid value is logged and
skipped, leaving the running config in place. List the component last in
uses= so the components it reconfigures open first.
Every named component is addressable by its GREL_... prefix
(GREL_RATELIMITER_API_*, GREL_CIRCUITBREAKER_PAYMENTS_*, ...),
whether or not it loaded any value from the environment. A component
built through its from_config classmethod opts out of live reload and
stays on the config it was constructed with.
A bad poll never crashes the app or stops future polls: an adapter that
raises on an unreadable source is logged and the last good config is
kept. Call reload for a
deterministic load-and-apply pass instead of waiting for the next poll.
Initialize the external config reloader.
| PARAMETER | DESCRIPTION |
|---|---|
config
|
The configuration source: a mounted ConfigMap directory, a
TYPE:
|
secrets
|
The secrets source: a mounted Secret directory or any other
TYPE:
|
reload_interval
|
Seconds between reloads of the sources. Each reload re-applies only what changed.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If neither |
reload
async
reload() -> None
Load the sources once and reconfigure every live component.
Performs one immediate load-and-apply pass, the same pass the poll loop runs on each interval. Use it for a deterministic trigger in tests and ops runbooks instead of waiting for the next poll.
An adapter that raises on an unreadable source is logged and the
last good config is kept, so a single failed reload never raises.
Values rejected by a component are logged and skipped by
reconfigure_all, leaving the running config in place.
ConfigBackend
Bases: Protocol
Config Backend Protocol.
A config backend is the pluggable source ExternalConfig reads. It
returns a flat mapping of environment-style keys to string values,
the same GREL_... keys components resolve from the environment. A
mounted ConfigMap directory, a single .env file, an HTTP config
server, or a git repository each implement this one protocol, so any
of them can drive live reconfiguration.
The backend tracks what it last returned so it can report "nothing
changed" cheaply: load returns None when the source is unchanged
since the previous call.
load
async
load() -> Mapping[str, str] | None
Read the current configuration from the source.
Returns a flat mapping of environment-style keys to string values on
the first call and whenever the source changes. Returns None when
nothing changed since the last call, so the caller can skip
re-applying.
Error contract:
- Raise
OSError(or a subclass) when the source is unreadable: a missing mount, a permission error, or a network failure. - Raise
ValueErrorwhen the source is readable but its content is not a valid flat mapping (for example malformed JSON).
ExternalConfig catches both, logs a warning, and keeps the last
good config, so one bad read never crashes the app or stops future
polls.
FileConfigAdapter
FileConfigAdapter(path: str | PathLike[str])
Read configuration from the filesystem.
Built for mounted configuration: a Kubernetes ConfigMap or Secret, a Docker config or secret, or any directory a sidecar writes to. The shape is picked from what is on disk:
- A directory: every file is one key, the filename is the key and the
file content is the value. This is how Kubernetes mounts a ConfigMap
or Secret as a volume. Entries whose name starts with
..are skipped, so the..datasymlink Kubernetes maintains is ignored. - A
.json,.yaml,.yml, or.tomlfile: a mapping document. Either a flat mapping ofGREL_...keys to scalar values, or a nested mapping whose segments join with_and uppercase, sogrel: {lock: {cart: {lease_duration: 30}}}reads asGREL_LOCK_CART_LEASE_DURATION=30. - Any other file:
KEY=VALUElines, blank lines and#comments ignored, matching a.envfile.
Keys are the same GREL_... names components resolve from the
environment. The adapter remembers what it last read and returns
None from load when nothing changed, so an unchanged mount costs
one read and no reconfiguration.
An absent path reads as an empty mapping rather than an error, so a
mount that is not present yet is not a failure. A path that exists but
cannot be read raises OSError, and a mapping document whose content
is not a mapping raises ValueError. ExternalConfig catches both
and keeps the last good config.
Reading .yaml or .yml needs PyYAML, installed with the yaml
extra. The import is lazy, so a DependencyNotFoundError is raised
only when a YAML file is actually read.
Initialize the filesystem config backend.
| PARAMETER | DESCRIPTION |
|---|---|
path
|
The directory or file to read. A mounted ConfigMap or Secret
is a directory. A single
TYPE:
|
load
async
load() -> Mapping[str, str] | None
Read the current mapping, or None when unchanged.
| RAISES | DESCRIPTION |
|---|---|
OSError
|
The path exists but cannot be read. |
ValueError
|
A mapping document does not hold a mapping. |
DependencyNotFoundError
|
A |