Skip to content

Custom Types#

Use type_loaders to teach dature how to parse custom types from strings.

Pass type_loaders as a dict[type, Callable] mapping types to conversion functions:

from dataclasses import dataclass

import dature


@dataclass(frozen=True, slots=True)
class Rgb:
    r: int
    g: int
    b: int


def rgb_from_string(value: str) -> Rgb:
    parts = value.split(",")
    return Rgb(r=int(parts[0]), g=int(parts[1]), b=int(parts[2]))


@dataclass
class AppConfig:
    name: str
    color: Rgb


config = dature.load(
    dature.Yaml12Source(
        file=SOURCES_DIR / "custom_type_common.yaml",
        type_loaders={Rgb: rgb_from_string},  # applies only to this file source
    ),
    schema=AppConfig,
)

assert config == AppConfig(name="my-app", color=Rgb(r=255, g=128, b=0))
name: my-app
color: "255,128,0"

Per-source vs Global#

type_loaders can be set per-source in Source, in dature.load() for merge mode, or globally via configure():

from dataclasses import dataclass

import dature


@dataclass(frozen=True, slots=True)
class Rgb:
    r: int
    g: int
    b: int


def rgb_from_string(value: str) -> Rgb:
    parts = value.split(",")
    return Rgb(r=int(parts[0]), g=int(parts[1]), b=int(parts[2]))


@dataclass
class AppConfig:
    name: str
    color: Rgb


config = dature.load(
    dature.Yaml12Source(
        file=SOURCES_DIR / "custom_type_common.yaml",
        type_loaders={Rgb: rgb_from_string},  # applies only to this file source
    ),
    schema=AppConfig,
)

assert config == AppConfig(name="my-app", color=Rgb(r=255, g=128, b=0))
from dataclasses import dataclass

import dature


@dataclass(frozen=True, slots=True)
class Rgb:
    r: int
    g: int
    b: int


def rgb_from_string(value: str) -> Rgb:
    parts = value.split(",")
    return Rgb(r=int(parts[0]), g=int(parts[1]), b=int(parts[2]))


@dataclass
class AppConfig:
    name: str
    color: Rgb


config = dature.load(
    dature.Yaml12Source(file=SOURCES_DIR / "custom_type_common.yaml"),
    dature.Yaml12Source(file=SOURCES_DIR / "custom_type_merge_override.yaml"),
    schema=AppConfig,
    # applies to all sources within a single load() call
    type_loaders={Rgb: rgb_from_string},
)

assert config == AppConfig(name="my-app", color=Rgb(r=100, g=200, b=50))
from dataclasses import dataclass

import dature


@dataclass(frozen=True, slots=True)
class Rgb:
    r: int
    g: int
    b: int


def rgb_from_string(value: str) -> Rgb:
    parts = value.split(",")
    return Rgb(r=int(parts[0]), g=int(parts[1]), b=int(parts[2]))


@dataclass
class AppConfig:
    name: str
    color: Rgb


# Register Rgb parser globally — no need to pass type_loaders
# to every load() call
dature.configure(type_loaders={Rgb: rgb_from_string})

config = dature.load(
    dature.Yaml12Source(file=SOURCES_DIR / "custom_type_common.yaml"),
    schema=AppConfig,
)
assert config == AppConfig(name="my-app", color=Rgb(r=255, g=128, b=0))

When both per-source and global type_loaders are set, they merge — per-source loaders take priority.