Skip to content

Config

  • Start here: Reconfigure from a ConfigMap
  • Common recipes: add ExternalConfig("/etc/grelmicro") to Grelmicro(uses=[...]) to keep live components in sync with a mounted ConfigMap or Secret. Call reload() 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 .env or .json file, a URL, or a ConfigBackend. Holds the non-sensitive GREL_... keys.

TYPE: ConfigBackend | str | PathLike[str] | None DEFAULT: None

secrets

The secrets source: a mounted Secret directory or any other ConfigBackend. Its keys override config on collision.

TYPE: ConfigBackend | str | PathLike[str] | None DEFAULT: None

reload_interval

Seconds between reloads of the sources. Each reload re-applies only what changed.

TYPE: float DEFAULT: _DEFAULT_INTERVAL

RAISES DESCRIPTION
ValueError

If neither config nor secrets is given.

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 ValueError when 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 ..data symlink Kubernetes maintains is ignored.
  • A .json, .yaml, .yml, or .toml file: a mapping document. Either a flat mapping of GREL_... keys to scalar values, or a nested mapping whose segments join with _ and uppercase, so grel: {lock: {cart: {lease_duration: 30}}} reads as GREL_LOCK_CART_LEASE_DURATION=30.
  • Any other file: KEY=VALUE lines, blank lines and # comments ignored, matching a .env file.

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 .env, .json, .yaml, .yml, or .toml file works too.

TYPE: str | PathLike[str]

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 .yaml or .yml file is read without PyYAML installed.