Variable scopes¶
Tip
Code for examples on this page can be found in examples/22_variable_scopes.
Local scope by default¶
In Tutorial #21 we wrote expressions that refer to other values by their path. In Tutorial #11 we saw that a configuration file can be loaded at any key, with the __include__ keyword in config files or --config.path.to.key command line parameters.
To make those two behaviors compatible, the variable paths used in expressions in configuration files are local paths.
To illustrate this, let's reuse our examples of Tutorial #21 and nest the two-float configuration within a deeper configuration.
Our derived_value2.yaml configuration used an expression to deduce value2 from value1 like so:
Say we have a root configuration like so:
However, this has a deep meaning: variable path in expressions are relative to their current file, not to the global configuration.
Bringing our old derived_value2.yaml under its new values key works without a second thought.
$ uv run nested_floats.py --config top_value.yaml --config.values derived_value2.yaml
Config(value=5.0, values=SubConfig(value1=3.0, value2=4.5))
Consider for example that to use the same value in an expression from the top config file, the full path to the value must be used:
$ uv run nested_floats.py --config top_value_expression.yaml --config.values derived_value2.yaml
Config(value=5.0, values=SubConfig(value1=3.0, value2=4.5))
Note
The exact same behavior would be obtained with __include__ in configuration files instead of --config.<key-path> command-line arguments.
Expressions provided by the environment or command line parameters sit at the top level and must use the full path of variables, even when they refer to nested keys.
$ # Error: value1 is not found from the CLI, even for expressions on value2
$ uv run nested_floats.py --config included_values.yaml --values.value2 '${value1 * 2.0}'
...
$ # OK: the full path of value1 is used.
$ uv run nested_floats.py --config included_values.yaml --values.value2 '${values.value1 * 2.0}'
Config(value=5.0, values=SubConfig(value1=3.0, value2=6.0))
Referring to the whole configuration¶
While preserving perfect encapsulation is a desirable goal, in practice a configuration file could need to refer to values defined outside of its scope. This allows big interdependent configurations to be split into manageable components.
To allow for this use case, confarg relies on root-paths — paths with a :: prefix — to denote global paths that are defined down from the top configuration.
In our example, the nested configuration can reach for the top value using a root-path. Note that root-paths and standard (local) paths can be mixed within the same expression.
$ uv run nested_floats.py --config top_value.yaml --config.values root_path.yaml
Config(value=5.0, values=SubConfig(value1=3.0, value2=9.5))
Note that this file is no longer standalone: it knows of context that lies outside of its scope. Reusability of such configuration files is affected.
Saving a configuration¶
# saved_config_uninterpolated.yaml
When saving the configuration, expressions are resolved against the assembled configuration, as local paths to that global configuration:
```console
$ uv run nested_floats.py --config top_value_expression.yaml --config.values root_path.yaml
Config(value=5.0, values=SubConfig(value1=3.0, value2=9.5))
value: ${values.value1 + 2} values: value1: 3.0 value2: ${value + values.value1 * 1.5}