Skip to content

API Reference

Decorators

Decorator factory. Bind once, reuse as a decorator on every command.

Parameters:

Name Type Description Default
app Typer

The Typer application to register commands on.

required
base_settings Dynaconf

Base Dynaconf settings instance.

required
output_dir Path

Root directory for run artifacts.

Path('outputs')

Returns:

Type Description
Callable[[Callable[..., Any]], Callable[..., Any]]

A decorator that registers the function as a Typer command with

Callable[[Callable[..., Any]], Callable[..., Any]]

automatic config merging and run logging.

Example::

cmd = command(app, settings)

@cmd
def my_command(settings=None, run_dir=None):
    ...
Source code in src/iolaus/decorators.py
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def command(
    app: typer.Typer,
    base_settings: Dynaconf,
    output_dir: Path = Path("outputs"),
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
    """Decorator factory. Bind once, reuse as a decorator on every command.

    Args:
        app: The Typer application to register commands on.
        base_settings: Base Dynaconf settings instance.
        output_dir: Root directory for run artifacts.

    Returns:
        A decorator that registers the function as a Typer command with
        automatic config merging and run logging.

    Example::

        cmd = command(app, settings)

        @cmd
        def my_command(settings=None, run_dir=None):
            ...
    """

    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
        sig = inspect.signature(func)
        existing = list(sig.parameters.values())
        existing_names = {p.name for p in existing}
        extra = [p for p in _EXTRA_PARAMS if p.name not in existing_names]
        new_sig = sig.replace(parameters=existing + extra)

        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            extra_config: Path | None = kwargs.pop("extra_config", None)
            overrides: list[str] = kwargs.pop("override", [])

            merged_settings = build_settings(base_settings, extra_config, overrides)
            run_dir = setup_logging(func.__name__, output_dir)
            save_config_snapshot(merged_settings, run_dir)

            if "settings" in sig.parameters:
                kwargs["settings"] = merged_settings
            if "run_dir" in sig.parameters:
                kwargs["run_dir"] = run_dir

            return func(*args, **kwargs)

        wrapper.__signature__ = new_sig  # type: ignore[attr-defined]
        app.command()(wrapper)
        return func  # return unwrapped original so it is unit-testable without Typer

    return decorator

Settings

Merge base settings, an optional extra config file, and CLI overrides.

Parameters:

Name Type Description Default
base Dynaconf

The base Dynaconf settings instance.

required
extra_config Path | None

Optional path to an additional config file to layer on top.

required
overrides list[str]

List of key=value strings. Use __ for nesting (e.g. model__lr=0.01).

required

Returns:

Type Description
Dynaconf

A new Dynaconf instance with all sources merged.

Source code in src/iolaus/settings.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def build_settings(
    base: Dynaconf,
    extra_config: Path | None,
    overrides: list[str],
) -> Dynaconf:
    """Merge base settings, an optional extra config file, and CLI overrides.

    Args:
        base: The base Dynaconf settings instance.
        extra_config: Optional path to an additional config file to layer on top.
        overrides: List of ``key=value`` strings. Use ``__`` for nesting
            (e.g. ``model__lr=0.01``).

    Returns:
        A new Dynaconf instance with all sources merged.
    """
    files = list(base.settings_file or [])
    if extra_config:
        files.append(str(extra_config))

    merged = Dynaconf(
        settings_files=files,
        envvar_prefix=base.envvar_prefix_for_dynaconf or "APP",
    )

    for item in overrides:
        key, _, value = item.partition("=")
        # tomlfy parses the value as TOML so numbers, booleans, and lists keep
        # their types instead of arriving as bare strings. Values that are not
        # valid TOML (bare paths, unquoted text) fall back to str.
        merged.set(key.replace("__", "."), value, tomlfy=True)

    return merged

Logging

Create a timestamped run directory and configure logging.

Parameters:

Name Type Description Default
command_name str

Name of the command (used as a subdirectory).

required
base_dir Path

Root output directory.

required

Returns:

Type Description
Path

Path to the created run directory.

Source code in src/iolaus/logging.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def setup_logging(command_name: str, base_dir: Path) -> Path:
    """Create a timestamped run directory and configure logging.

    Args:
        command_name: Name of the command (used as a subdirectory).
        base_dir: Root output directory.

    Returns:
        Path to the created run directory.
    """
    run_dir = base_dir / command_name / datetime.now().strftime("%Y-%m-%d/%H-%M-%S")
    run_dir.mkdir(parents=True, exist_ok=True)

    logging.basicConfig(
        level=logging.INFO,
        format="[%(asctime)s][%(name)s][%(levelname)s] %(message)s",
        handlers=[
            logging.FileHandler(run_dir / "run.log"),
            logging.StreamHandler(),
        ],
        force=True,
    )
    return run_dir

Write the full merged config as a JSON snapshot.

Parameters:

Name Type Description Default
settings Dynaconf

The merged Dynaconf settings instance.

required
run_dir Path

Directory to write config.json into.

required
Source code in src/iolaus/logging.py
38
39
40
41
42
43
44
45
46
def save_config_snapshot(settings: Dynaconf, run_dir: Path) -> None:
    """Write the full merged config as a JSON snapshot.

    Args:
        settings: The merged Dynaconf settings instance.
        run_dir: Directory to write ``config.json`` into.
    """
    snapshot = settings.as_dict()
    (run_dir / "config.json").write_text(json.dumps(snapshot, indent=2, default=str))