Skip to content

Loading#

When a dature.load(...) call fails, the error message tells you which field broke, where in the source it came from, and why. This page walks through the failures you are most likely to hit while wiring up your first config — and one pattern for recovering from them.

All examples share the same schema

Source does not exist#

Wrong path or wrong working directory — the most common first error. dature raises a plain FileNotFoundError before any parsing happens.

"""Loading error — config file does not exist."""

from dataclasses import dataclass
from pathlib import Path

import dature

SOURCES_DIR = Path(__file__).parent / "sources"


@dataclass
class Config:
    host: str
    port: int
    debug: bool = False


dature.load(
    dature.Yaml12Source(file=SOURCES_DIR / "does_not_exist.yaml"),
    schema=Config,
)
  | dature.errors.exceptions.DatureConfigError: Config loading errors (1)
  +-+---------------- 1 ----------------
    | FileNotFoundError: Config file not found: {SOURCES_DIR}does_not_exist.yaml
    +------------------------------------

Source exists but is broken#

The file is present but the parser can't read it (here: invalid YAML indentation). dature does not swallow parser errors — the underlying exception propagates with the original file and line.

"""Loading error — config file exists but is malformed."""

from dataclasses import dataclass
from pathlib import Path

import dature

SOURCES_DIR = Path(__file__).parent / "sources"


@dataclass
class Config:
    host: str
    port: int
    debug: bool = False


dature.load(
    dature.Yaml12Source(file=SOURCES_DIR / "broken.yaml"),
    schema=Config,
)
host: localhost
port: 8080
  bad_indent: oops
debug: false
  | dature.errors.exceptions.DatureConfigError: Config loading errors (1)
  +-+---------------- 1 ----------------
    | ruamel.yaml.scanner.ScannerError: mapping values are not allowed here
    |   in "{SOURCES_DIR}broken.yaml", line 3, column 13
    +------------------------------------

Type mismatch#

The source parses, but a value can't be coerced to the field's annotated type. dature raises a FieldLoadError with the field path, the offending value, a caret pointing at it, and the source location.

"""Loading error — value cannot be coerced to the field type."""

from dataclasses import dataclass
from pathlib import Path

import dature

SOURCES_DIR = Path(__file__).parent / "sources"


@dataclass
class Config:
    host: str
    port: int
    debug: bool = False


dature.load(
    dature.Yaml12Source(file=SOURCES_DIR / "type_mismatch.yaml"),
    schema=Config,
)
host: localhost
port: not_a_number
debug: false
  | dature.errors.exceptions.DatureConfigError: Config loading errors (1)
  +-+---------------- 1 ----------------
    | dature.errors.exceptions.FieldLoadError:   [port]  invalid literal for int() with base 10: 'not_a_number'
    |    ├── port: not_a_number
    |    │         ^^^^^^^^^^^^
    |    └── FILE '{SOURCES_DIR}type_mismatch.yaml', line 2
    +------------------------------------

Required field missing#

A field with no default value is absent from the source. The error points at the file but has no line — there is nothing in the source to highlight.

"""Loading error — required field absent from the source."""

from dataclasses import dataclass
from pathlib import Path

import dature

SOURCES_DIR = Path(__file__).parent / "sources"


@dataclass
class Config:
    host: str
    port: int
    debug: bool = False


dature.load(
    dature.Yaml12Source(file=SOURCES_DIR / "missing_field.yaml"),
    schema=Config,
)
host: localhost
debug: false
  | dature.errors.exceptions.DatureConfigError: Config loading errors (1)
  +-+---------------- 1 ----------------
    | dature.errors.exceptions.FieldLoadError:   [port]  Missing required field
    |    └── FILE '{SOURCES_DIR}missing_field.yaml'
    +------------------------------------

Multiple errors at once#

dature does not stop at the first error — it keeps going and reports every failed field together as an ExceptionGroup. You fix the config in one pass instead of "fix, rerun, fix, rerun".

"""Loading error — every field is checked, all errors are reported together."""

from dataclasses import dataclass
from pathlib import Path

import dature

SOURCES_DIR = Path(__file__).parent / "sources"


@dataclass
class Config:
    host: str
    port: int
    debug: bool = False


dature.load(
    dature.Yaml12Source(file=SOURCES_DIR / "multiple_errors.yaml"),
    schema=Config,
)
host: localhost
port: not_a_number
debug: maybe
  | dature.errors.exceptions.DatureConfigError: Config loading errors (2)
  +-+---------------- 1 ----------------
    | dature.errors.exceptions.FieldLoadError:   [port]  invalid literal for int() with base 10: 'not_a_number'
    |    ├── port: not_a_number
    |    │         ^^^^^^^^^^^^
    |    └── FILE '{SOURCES_DIR}multiple_errors.yaml', line 2
    +---------------- 2 ----------------
    | dature.errors.exceptions.FieldLoadError:   [debug]  Expected bool, got str
    |    ├── debug: maybe
    |    │          ^^^^^
    |    └── FILE '{SOURCES_DIR}multiple_errors.yaml', line 3
    +------------------------------------

Recovering: skip a broken source#

When merging multiple sources, a missing or malformed one can be skipped with skip_if_broken=True so the next source supplies the values:

"""Loading recovery — skip a missing source and fall back to the next one."""

from dataclasses import dataclass
from pathlib import Path

import dature

SOURCES_DIR = Path(__file__).parent / "sources"


@dataclass
class Config:
    host: str
    port: int
    debug: bool = False


config = dature.load(
    dature.Yaml12Source(
        file=SOURCES_DIR / "does_not_exist.yaml",
        skip_if_broken=True,
    ),
    dature.Yaml12Source(file=SOURCES_DIR / "fallback.yaml"),
    schema=Config,
)

assert config.host == "localhost"
assert config.port == 8080
assert config.debug is False
host: localhost
port: 8080
debug: false

If every source fails, dature still raises — there is no value to load. The flag also has variants for skipping individual invalid fields rather than entire sources. See Merge Rules — Skipping Broken Sources for the full picture.

What's next#

  • Validation — add custom rules so loading also fails when values are the wrong shape (out of range, bad regex, …), not just the wrong type.
  • Merge Rules — control what happens when several sources disagree, and how to skip broken or invalid pieces.
  • Debug & Reports — inspect what each source contributed before the merge happened.