Interpolation¶
Expression evaluation inside YAML values.
Syntax¶
${...} and $(...)¶
Identical behavior. Both delimit a lazy expression that is evaluated during construction.
When the entire YAML value is a single interpolation, the result retains its native type (int, list, dict, etc.). When mixed with literal text, the result is stringified.
$VAR shorthand¶
When enable_shorthand_vars=True (the default), bare $VAR tokens are auto-converted to ${VAR} before evaluation.
Escaping¶
Two ways to prevent interpolation and produce a literal ${...} in the output:
# backslash escape
template: \${not_evaluated} # -> ${not_evaluated}
# double-dollar escape
template: $${not_evaluated} # -> ${not_evaluated}
Both work for all interpolation forms:
| You write | Output |
|---|---|
\${expr} |
${expr} |
$${expr} |
${expr} |
\$(expr) |
$(expr) |
$$(expr) |
$(expr) |
\$VAR |
$VAR |
$$ (anywhere) |
$ |
The $$ form is often easier since it avoids interactions with YAML's own backslash handling. Both work in strings that also contain real interpolations:
!define name: world
# mix resolved and passthrough expressions in the same value
msg: "hello ${name}, metric=$${value}" # -> hello world, metric=${value}
This is useful when a host application needs certain ${...} tokens to survive Dracon construction for later runtime resolution.
For values owned by a downstream runtime
If the entire value belongs to an external system (not just a literal $), use !raw instead of escaping. !raw survives any nesting depth without counting escape layers.
Expression Evaluation¶
Expressions support full Python syntax, evaluated via one of two engines:
| Engine | Safety | Notes |
|---|---|---|
asteval (default) |
Sandboxed, no imports, no file access | Sufficient for most configs |
eval |
Full Python eval() |
Use when you need builtins, imports, etc. |
Set via interpolation_engine in DraconLoader or @dracon_program.
Expressions can use any variable in the current context: built-in functions, !defined variables, file context vars, and CLI-injected values.
Examples¶
!define items: [1, 2, 3]
count: ${len(items)}
doubled: ${[x * 2 for x in items]}
env: ${getenv('HOME', '/root')}
stamp: ${now('%Y%m%d')}
conditional: ${'prod' if env == 'production' else 'dev'}
Value References¶
@path -- constructed value reference¶
Retrieves the final constructed value at a keypath, resolved relative to the current node's parent.
Here @port is a relative reference to the sibling key port within the same mapping. You could also write ${@/db.port} as an absolute path from the document root. Using @db.port from inside db would incorrectly resolve to /db/db.port.
Absolute paths (starting with /) start from the document root. Relative paths are resolved from the expression's location.
&path -- node copy reference¶
Deep-copies the raw YAML node at composition time (before construction). Must appear inside ${...}.
Also works with anchors: ${&anchor_name} or ${&anchor_name.sub.key}.
Context can be passed to the copied node: ${&path:var1=expr1,var2=expr2}.
!ref / !refs -- locator reference¶
@path is positional. When you need predicates, axes (nearest-enclosing, sibling), or a list of matches, use !ref / !refs: a Locator resolved through the same lazy, post-construction frame as @, so it reads constructed values and survives !include merges.
replica: { port: !ref ^.primary.port } # sibling, by identity (no predicate -> flow ok)
scrape: !refs /services.*[enabled].port # predicate fan-out -> list (truthy filter)
Built-in Functions¶
Available in all interpolation expressions. See Loader API - Built-in Context for the full table.
| Function | Description |
|---|---|
getenv(name, default) |
Environment variable |
getcwd() |
Current working directory |
listdir(path) |
Directory listing |
join(*parts) |
Path joining |
basename(path) |
Filename from path |
dirname(path) |
Directory from path |
expanduser(path) |
Expand ~ |
isfile(path) |
Check file exists |
isdir(path) |
Check directory exists |
Path |
pathlib.Path constructor |
now(fmt) |
Current timestamp |
construct(node) |
Construct a raw node |
When numpy is installed, np is also available.
Permissive Mode¶
When permissive=True, unresolvable expressions are left as strings instead of raising UndefinedNameError.
Partially resolvable expressions are simplified: if some variables are known, they are folded, and the remaining unknowns stay as ${...}.
Typed lazy fields¶
A typed Pydantic field can wrap a ${...} value in Lazy[T] to defer interpolation while preserving the static type. On a LazyDraconModel, attribute access triggers resolution and returns T:
from dracon import Lazy, LazyDraconModel, DraconLoader
class Cfg(LazyDraconModel):
port: Lazy[int]
host: Lazy[str] = "localhost"
cfg = DraconLoader().loads("port: ${env_port}\nhost: ${env_host}",
context={"env_port": 9000, "env_host": "api.local"})
cfg.port # -> 9000 (resolved on access, returns int)
Lazy[T] is the ${...}-side parallel to Resolvable[T]. See Deferred Execution for the full comparison.
Two-Phase Resolution¶
During construction, interpolations are evaluated twice with a growing context. The first pass resolves what it can; the second pass picks up values that depended on results from the first pass. This handles most forward-reference scenarios without explicit ordering.
Recursion depth is capped at 5 levels to catch circular references.
Error Types¶
UndefinedNameError¶
Raised when an expression references a name not in the current context. In permissive mode, caught and left as a string.
Includes: the undefined name, source context (file/line), the full expression, and available symbol names for "did you mean?" suggestions.
EvaluationError¶
Raised for any other evaluation failure (syntax errors, type errors, attribute errors, etc.).
Includes: the expression, source context, a visual pointer to the error location (when available), and hints for common mistakes.
evaluate_expression API¶
Low-level function for evaluating interpolation expressions programmatically.
from dracon import evaluate_expression
result = evaluate_expression(
expr="Hello, ${name}!",
current_path="/",
root_obj=None,
context={"name": "world"},
engine="asteval",
permissive=False,
)
| Parameter | Description |
|---|---|
expr |
The string to evaluate (may contain ${...} blocks). |
current_path |
KeyPath for @ references. Default "/". |
root_obj |
Root object for @ path resolution. |
context |
Variables available in expressions. |
engine |
'asteval' or 'eval'. |
permissive |
Leave unresolvable as strings. |
enable_shorthand_vars |
Convert $VAR to ${VAR}. Default True. |
source_context |
SourceContext for error messages. |
allow_recurse |
Max recursion depth. Default 5. |