Config

The Config class is the core (and currently the only) feature of confwire. It lets you load configuration from Python, YAML, or JSON files into a single, dict-like object, and it supports composing configs out of multiple files.

Note

Config only loads and merges data. Turning that data into live Python objects is handled separately by build_from_config — see Building Objects.

Reserved keys

Warning

_base_, _delete_, _deprecation_, _args_, filename, text, and pretty_text are reserved and must not be used as your own config keys.

1. Loading a configuration file

Note

The examples on this page use plain, abstract key names (a, b, c, …) on purpose, rather than domain-specific ones like model or backboneconfwire isn’t tied to machine learning or any other specific use case, and generic names make the mechanics easier to follow.

Real configs are usually nested, several levels deep — think of it like a small matrix, where b has children b1 and b2, and b1 itself has children b11 and b12. Here is one such config, expressed in each supported format.

train.py / train.yaml / train.json:

a = 1
b = dict(
    b1=dict(b11=10, b12=20),
    b2=30,
)
c = "string"

Load it, then reach any nested value with the dot operator — no matter how deep it is:

>>> from confwire import Config
>>> cfg = Config.fromfile("train.py")
>>> print(cfg)
Config (path: train.py): {'a': 1, 'b': {'b1': {'b11': 10, 'b12': 20}, 'b2': 30}, 'c': 'string'}
>>> cfg.a
1
>>> cfg.b.b1.b11
10
>>> cfg.b.b1.b12
20
>>> cfg.b.b2
30

Item access works the same way, and both styles can be mixed:

>>> cfg["b"]["b1"]["b11"]
10
>>> cfg.b["b1"].b12
20

Values can be read using either attribute access (cfg.a) or item access (cfg["a"]), since Config wraps the underlying data in a dict-like ConfigDict.

2. Composing configs with _base_

As a project grows, a single config file gets unwieldy. confwire lets you split shared settings into a base file and reuse them from as many child configs as you like, via _base_ = './other_config.py' (or a list of files).

Move the b block from the previous example into its own file, base.py:

b = dict(
    b1=dict(b11=10, b12=20),
    b2=30,
)

Then full.py inherits it and only adds what’s new:

_base_ = "./base.py"
a = 1
c = "string"

Loading full.py alone gives you back the exact same merged config as before — confwire pulls in everything from base.py automatically:

>>> cfg = Config.fromfile("./full.py")
>>> print(cfg)
Config (path: ./full.py): {'b': {'b1': {'b11': 10, 'b12': 20}, 'b2': 30}, 'a': 1, 'c': 'string'}

If two base configs define the same top-level key, Config.fromfile raises a KeyError rather than silently picking one.

3. Overriding with _delete_

By default, a child config merges into the base recursively — a nested dict field is combined key by key, not replaced wholesale. Sometimes you want a full replacement instead, e.g. swapping b for a completely different shape. Set _delete_=True inside the dict you want to replace outright.

override.py:

_base_ = "./base.py"
b = dict(_delete_=True, b3=99, b4=100)
>>> cfg = Config.fromfile("./override.py")
>>> print(cfg)
Config (path: ./override.py): {'b': {'b3': 99, 'b4': 100}}

Because _delete_=True is set inside b, the entire b dict from base.pyb1, b2, everything — is dropped and replaced by the new b3, b4 fields. Without _delete_, b1 and b2 would have merged in alongside the new b3 key instead.

4. Referencing values

Referencing within the same file

Since a Python config file is executed as real Python, one variable can simply reference another defined earlier in the same file — no special syntax needed. This only works for the Python format; YAML and JSON have no notion of executing code, so this pattern is Python-only.

x = 1
y = dict(z=x)
>>> cfg = Config.fromfile("./sameref.py")
>>> print(cfg)
Config (path: ./sameref.py): {'x': 1, 'y': {'z': 1}}

Referencing values from a base file

To pull a value from a base config into a child config — across all three formats — use the {{ _base_.path.to.value }} grammar. Write the placeholder without surrounding quotes; confwire substitutes it with the real value (including its type) before the file is parsed.

value.py:

n = 5

ref.py:

_base_ = "./value.py"
m = dict(x={{ _base_.n }})
>>> cfg = Config.fromfile("./ref.py")
>>> print(cfg)
Config (path: ./ref.py): {'n': 5, 'm': {'x': 5}}

Warning

Do not wrap the placeholder in quotes ("{{ _base_.x }}"). The substitution already inserts the value with correct quoting for its type, so adding your own quotes produces invalid Python/YAML/JSON.

Next steps

  • See Advanced Configuration for common usage patterns, predefined path variables, deprecating a config file, and how config resolution works internally.

  • See API Reference for the full Config API reference.