This page showcases common ways to integrate Durango into real projects. Each example is intentionally short and highlights the surrounding context so you can adapt it quickly.

Basic Script

from durango import ConfigManager, DurangoSettings


class AppSettings(DurangoSettings):
    api_url: str
    timeout_seconds: int = 10


settings = ConfigManager(
    settings_type=AppSettings,
    identifier="APP",
    default_file="~/.config/app/settings.yaml",
).load()

print(settings.api_url)

Durango will create ~/.config/app/settings.yaml if it does not exist, seeding it with the model defaults before layering environment variables and overrides.

CLI with Click

import click
from durango import ConfigManager, DurangoSettings


class ServiceSettings(DurangoSettings):
    endpoint: str
    debug: bool = False


manager = ConfigManager(
    settings_type=ServiceSettings,
    identifier="SERVICE",
    default_file="~/.service/config.toml",
)


@click.group()
@click.option("--config", type=click.Path(), help="Override config file path.")
@click.pass_context
def cli(ctx: click.Context, config: str | None) -> None:
    settings = manager.load(config_path=config)
    ctx.obj = {"settings": settings}


@cli.command()
@click.pass_context
def ping(ctx: click.Context) -> None:
    settings: ServiceSettings = ctx.obj["settings"]
    click.echo(f"Pinging {settings.endpoint} (debug={settings.debug})")


if __name__ == "__main__":
    cli()

Key details:

  • Command-line options can override the path before the first load() call.
  • Extra CLI flags map cleanly to manager.override(...) if you want to promote runtime changes to highest precedence.

Environment Overrides

Environment variables use the <IDENTIFIER>__SECTION__KEY pattern. Nested sections become additional segments.

export SERVICE__ENDPOINT="https://api.example.com"
export SERVICE__FEATURES__CACHE_TTL=300

Pair this with settings that match the shape:

class FeatureSettings(DurangoSettings):
    cache_ttl: int = 120


class ServiceSettings(DurangoSettings):
    endpoint: str
    features: FeatureSettings = FeatureSettings()

Durango normalises dashed names to snake_case (SERVICE__FEATURES__CACHE-TTLfeatures.cache_ttl) and forbids unknown keys so typos surface immediately.

Hot Reload with Callbacks

Long-running services can observe configuration changes via callbacks.

from durango import ConfigManager, DurangoSettings


class SearchSettings(DurangoSettings):
    default_limit: int = 20


def on_post_load(settings: SearchSettings) -> None:
    print("Loaded search settings:", settings.model_dump())


manager = ConfigManager(
    settings_type=SearchSettings,
    identifier="SEARCH",
    default_file="~/.search/config.json",
    callbacks={"post_load": [on_post_load]},
)


settings = manager.load()
# Later, maybe on a signal:
manager.reload()

Callbacks fire on the main thread; keep them fast and delegate heavy work to background tasks if necessary.

Merging Runtime Overrides

Use manager.override() to capture programmatic configuration that sits above environment variables and files. This is ideal for CLI flags or UI-driven options.

manager.override({"features": {"enable_metrics": True}})
settings = manager.reload()

Durango performs deep merges, so you can override a single nested value without rewriting the entire section.