Skip to content

Backends

A backend turns one source into a Python object. yaconfiglib picks a backend automatically from the source's file extension (or URI scheme), or you can request one explicitly with loader="name". Every backend implements the same ConfigBackend contract, so custom backends work identically to the built-in ones.

YAML

import yaconfiglib

config = yaconfiglib.load("config.yaml")

Registers !include and !load tag constructors automatically — see Includes for details. Requires yaconfiglib[yaml].

TOML

config = yaconfiglib.load("config.toml")

Uses the standard library tomllib on Python 3.11+, falling back to the third-party toml package (yaconfiglib[toml]) on 3.9/3.10.

JSON

config = yaconfiglib.load("config.json")

No extra dependency — uses the standard library json module.

INI

config = yaconfiglib.load("config.ini")
# {"section": {"key": "value", ...}, ...}

Parsed with the standard library configparser.ConfigParser. All values come back as strings, matching configparser semantics.

.env files

config = yaconfiglib.load(".env")
# {"database_url": "postgres://...", ...}

Supports KEY=value and export KEY=value syntax, single/double-quoted values, and # comments (both full-line and inline, outside quotes).

Environment variables

Unlike file-based backends, EnvVarBackend reads os.environ directly and is typically used explicitly rather than auto-detected:

from yaconfiglib import ConfigLoader
from yaconfiglib.backends.env import EnvVarBackend

# APP_DB__PORT=5432 -> {"db": {"port": 5432}}
config = ConfigLoader().load(
    loader=EnvVarBackend(prefix="APP_", nested_delimiter="__", coerce=True)
)
  • prefix — only variables starting with this are included (and it's stripped from the resulting keys).
  • nested_delimiter — split keys on this delimiter into nested dicts.
  • coerce — convert string values to None/bool/int/float/parsed JSON where they look like one, instead of leaving everything as str.

Commands and scripts

cmd://, exec://, and sh:// URIs (plus .sh/.bat/.ps1/.cmd files) run a shell command and parse its stdout:

# Recursively execute a command to retrieve secrets, parsed as JSON.
secrets: !include 'cmd+json://python -c "import json; print(json.dumps({\"token\": \"super-secret\"}))"'

Format resolution order: an explicit format= argument, the +fmt suffix on the scheme (cmd+yaml://...), a #!fmt shebang line in the command's own output, then sniffing (json, yaml, toml, dotenv, ini in turn). If nothing matches and no format was requested, the raw stdout string is returned instead of raising.

In-memory Python objects

PythonBackend wraps an already-parsed object so it can be spliced into a loader chain without writing a file:

from yaconfiglib.backends.python_backend import PythonBackend

loader.load("base.yaml", loader=PythonBackend({"override_key": "override_value"}))

Jinja2-templated sources

Append .j2/.jinja2 to any filename to render it as a Jinja2 template first, then parse the rendered output with the backend matching the underlying extension — see Templating.

Writing a custom backend

Subclass ConfigBackend and override load() (and optionally dumps()):

from yaconfiglib.backends.base import ConfigBackend
import re

class MyBackend(ConfigBackend):
    PATHNAME_REGEX = re.compile(r".*\.myfmt$", re.IGNORECASE)
    NAME = "myfmt"

    def load(self, path, encoding=None, **options):
        return parse_my_format(path.read_text(encoding=encoding or self.DEFAULT_ENCODING))

Subclasses are auto-discovered on import — no registry call needed. Once imported, yaconfiglib.load("config.myfmt") and loader="myfmt" both resolve to it.