Skip to content

Cross-Source References#

Sources can reference values from other sources using the ${@tag.key} syntax. This lets you pass a Vault token from an env variable, resolve a config file path from a CLI argument, or build connection strings that combine several sources — without imperative glue code.

Quick start#

from dataclasses import dataclass

import dature


@dataclass
class AppConfig:
    host: str = "localhost"
    port: int = 8080


cfg = dature.load(
    dature.JsonSource(file="${@env.config_path}"),
    dature.EnvSource(prefix="APP_"),
    schema=AppConfig,
)

assert cfg.host == "db.internal"
assert cfg.port == 5432
{
  "host": "db.internal",
  "port": 5432
}

The env in ${@env.config_path} is the source tag. By default the tag equals the source type's format_name"env" for EnvSource, "json" for JsonSource, and so on. Set tag= explicitly when you have two sources of the same type.

Sources can be passed in any order: dature builds a dependency graph from ${@tag.key} patterns and loads them in topological order automatically. In the example above JsonSource is listed first but loaded second because it depends on EnvSource.

Syntax#

Pattern Result
${@tag.key} Value of key in the source tagged tag
${@tag.section.key} Nested key path (dot-separated)
${@tag.key:-default} Use default if key is absent
$${@tag.key} Literal ${@tag.key} (escape with $$)

Escaping#

Prefix $$ to produce a literal $. This is useful when a config-file path should contain ${@...} literally rather than be treated as a cross-ref:

from dataclasses import dataclass

import dature


@dataclass
class Config:
    value: str = ""


cfg = dature.load(
    dature.JsonSource(file=str(SOURCES_DIR / "$${@env.something}")),
    schema=Config,
)

assert cfg.value == "hello"
{
  "value": "hello"
}

T-string syntax (Python 3.14+)#

Requires Python 3.14+

T-strings (PEP 750) are a Python 3.14 language feature. On earlier versions use the ${@tag.key} string syntax instead.

from dature import ref gives you a proxy object. ref.tag.key inside a t-string is exactly equivalent to "${@tag.key}" as a plain string:

from dataclasses import dataclass

import dature
from dature import ref


@dataclass
class AppConfig:
    host: str = "localhost"
    port: int = 8080


# t"{ref.env.config_path}"  ≡  "${@env.config_path}"
# t"{ref.env.log_level:INFO}"  ≡  "${@env.log_level:-INFO}"

cfg = dature.load(
    dature.JsonSource(file=t"{ref.env.config_path}"),
    dature.EnvSource(prefix="APP_"),
    schema=AppConfig,
)

assert cfg.host == "db.internal"
assert cfg.port == 5432
{
  "host": "db.internal",
  "port": 5432
}

The format spec becomes the default value — t"{ref.env.log_level:INFO}" is the same as "${@env.log_level:-INFO}".

Error messages#

Unknown tag#

from dataclasses import dataclass

import dature


@dataclass
class Config:
    host: str = ""
    port: int = 8080


dature.load(
    dature.EnvSource(),
    dature.JsonSource(file="${@vault.config_path}"),
    schema=Config,
)
dature.errors.exceptions.DatureError: JsonSource(tag='json') references unknown tag 'vault'. Known tags: 'env', 'json'. Ensure a source with that tag is listed in the same load() call.

Cycle#

from dataclasses import dataclass

import dature


@dataclass
class Config:
    host: str = ""


dature.load(
    dature.EnvSource(prefix="${@json.prefix_key}"),
    dature.JsonSource(file="${@env.config_path}"),
    schema=Config,
)
dature.errors.exceptions.DatureError: Cross-source reference cycle detected:
  EnvSource(tag='env')  →  references ${@json.prefix_key}
  JsonSource(tag='json')  →  references ${@env.config_path}
  closes back to EnvSource(tag='env')

Sources cannot reference each other's data in a cycle. Break the cycle by hardcoding one side or parsing one source imperatively.

Tag collision#

Each source resolves to a tag that uniquely identifies it within a dature.load() call. If two sources resolve to the same tag, dature raises an error. EnvSource defaults to tag='env'. Loading two EnvSource instances without explicitly setting a tag on at least one of them will cause a collision:

from dataclasses import dataclass

import dature


@dataclass
class Config:
    host: str = ""


dature.load(
    dature.EnvSource(prefix="APP_"),  # resolve to tag = 'env'
    dature.EnvSource(prefix="DB_"),  # resolve to tag = 'env' => collision
    dature.JsonSource(file="${@env.config_path}"),
    schema=Config,
)
dature.errors.exceptions.DatureError: Tag collision: multiple sources share resolved_tag='env':
  EnvSource(prefix='APP_', expand_env_vars='default', nested_resolve_strategy='flat')
  EnvSource(prefix='DB_', expand_env_vars='default', nested_resolve_strategy='flat')
Set an explicit tag= on at least one of them.