Concepts: Interpolation Engine¶
Dracon's interpolation feature (${...}
and $(...)
) allows embedding dynamic Python expressions within YAML strings. Understanding how this works, especially regarding lazy evaluation and security, is important.
Lazy (${...}
) vs. Immediate ($(...)
)¶
-
Lazy Interpolation (
${...}
): -
Evaluation: Deferred until the value is accessed in Python after the entire configuration is loaded and composed.
- Mechanism: Dracon creates internal
LazyInterpolable
placeholder objects during construction. When you accessconfig.my_key
, ifmy_key
holds aLazyInterpolable
, its expression is evaluated at that moment using the captured context and references. If using Dracon's default containers (Mapping
,Sequence
), this happens automatically. If using standarddict
/list
, resolution might require manual triggering (e.g.,resolve_all_lazy(config)
). - References: Can use
@
to reference final constructed values of other keys and&
to reference nodes during composition (primarily for templating). -
Use Case: Most common. Ideal for values depending on other config keys, runtime context, or environment variables needed after loading.
-
Immediate Interpolation (
$(...)
): - Evaluation: Executed during the initial YAML parsing and composition phase. The result replaces the
$(...)
token immediately. - Mechanism: The expression is evaluated by the configured engine as soon as the token is encountered by the parser/composer.
- References: Cannot use
@
or&
reliably, as the target values/nodes likely haven't been fully composed or constructed yet. - Use Case: Less common. Primarily for dynamically generating YAML tags (
!$(type_var)
) or simple scalar values needed during parsing itself.
The Evaluation Engine: asteval
vs. eval
¶
Dracon offers two engines for evaluating the Python expressions within ${...}
and $(...)
:
-
asteval
(Default & Recommended):- Mechanism: Uses the asteval library.
asteval
parses the expression into an Abstract Syntax Tree (AST) and evaluates it in a sandboxed environment. - Safety: Significantly safer than
eval()
. It prevents the execution of arbitrary code that could perform dangerous operations like filesystem access (import os; os.remove(...)
), network calls, or accessing sensitive system information. It provides a controlled environment with access only to specified symbols (context variables, safe built-ins). - Limitations: Might not support every single Python syntax feature or complex metaclasses, but covers the vast majority of use cases needed for configuration. Also I (Jean) couldn't get it to output a clean traceback that shows where in your code an error occured if an expression fails. (If you know how to do this, please let us know)
- Mechanism: Uses the asteval library.
-
eval
(Use with Extreme Caution):- Mechanism: Uses Python's built-in
eval()
function. - Safety: Well,
eval()
can execute any arbitrary Python code provided in the expression string. If your YAML files come from untrusted sources or could be manipulated, usingeval()
opens a significant security vulnerability. Malicious code could be injected and executed with the permissions of your application. - Use Case: Only suitable if you have absolute trust in the source and integrity of your configuration files and require features not supported by
asteval
. Can give nice tracebacks if an expression fails.
Warning
Security Risk: Choosing
eval
as the interpolation engine can lead to severe security vulnerabilities if the configuration files are not fully trusted. Avoid usingeval()
unless you are certain of the source and content of the configuration files. Useeval
at your own risk. - Mechanism: Uses Python's built-in
Choosing the Engine:
from dracon import DraconLoader
# Default, safe engine
loader_safe = DraconLoader() # interpolation_engine='asteval'
# Use Python's raw eval (DANGEROUS if config is untrusted, or if you're not careful)
loader_raw = DraconLoader(interpolation_engine='eval')
# Disable interpolation entirely
loader_no_interp = DraconLoader(enable_interpolation=False)
There's also a DRACON_EVAL_ENGINE
environment variable that can be set to asteval
or eval
to control the default engine.
Recommendation: Stick with the default asteval
engine unless you have a very specific, well-understood need for eval
and fully control the configuration sources.
Context and Symbol Availability¶
Expressions have access to:
- Variables provided via
DraconLoader(context=...)
. - Variables defined via
!define
/!set_default
. - Dracon's default context (
getenv
,getcwd
,construct
). - Include-specific context (
$DIR
,$FILE
, etc.). - Python built-ins allowed by the engine (
asteval
has a curated list;eval
has all). - Special symbols for references (
@
,&
handled internally before evaluation). - Helper functions or classes added to the context.
The exact context available depends on when the expression is evaluated (composition time for $(...)
, access time for ${...}
). Lazy evaluation (${...}
) generally has access to a more complete context reflecting the final configuration structure.