Using configs

This page covers runtime operations on config instances: reading and modifying fields, comparing and copying instances, making them read-only, and saving or loading values.

Accessing and setting fields

cfg = ProcessingConfig()

cfg.iterations   # 100 - dot access
cfg["mode"]      # 'fast' - dict-style access

cfg.iterations = 200       # dot assignment
cfg["mode"] = "thorough"   # dict-style assignment

list(cfg.keys())   # ['iterations', 'threshold', 'label', 'mode', 'verbose']
cfg.values()       # [200, 0.5, 'run_01', 'thorough', False]
dict(cfg.items())  # {'iterations': 200, ..., 'mode': 'thorough', ...}

Updating

update() applies several field changes at once. Each value goes through the same descriptor validation as direct assignment, and validate() is called at the end to enforce cross-field invariants:

cfg.update({"iterations": 50, "threshold": 0.8})

Comparison

Two config instances compare equal when all their field values are equal:

cfg1 = ProcessingConfig()
cfg2 = ProcessingConfig()
cfg1 == cfg2   # True

cfg1.mode = "thorough"
cfg1 == cfg2   # False

Copying and diffing

copy() returns a new instance with the same field values. Pass keyword arguments to override specific fields in the copy:

base = ProcessingConfig()
modified = base.copy(iterations=500, mode="thorough")

diff() returns a dict of the fields that differ between two instances of the same type:

base.diff(modified)
# {'iterations': (100, 500), 'mode': ('fast', 'thorough')}

See Callable fields and copy() for important behaviour to know about when the config has callable (computed) defaults.

Freezing

freeze() makes an instance read-only. Any subsequent field assignment raises FrozenConfigError:

cfg = ProcessingConfig()
cfg.freeze()
cfg.iterations = 10    # FrozenConfigError: Cannot set field 'iterations' ...

Freezing is instance-level - other instances of the same class are unaffected. To freeze individual fields at the class level instead of entire instances, use static=True on the field declaration (see Fields).

Checking and resetting field state

is_set(), unset(), and reset() are methods on the field descriptor. They take a config instance as their first argument:

class Derived(Config):
    base: float = Field(1.0, "Base value")
    triple: float = Field(lambda self: self.base * 3, "3× base")

cfg = Derived()

f_base   = Derived._fields["base"]
f_triple = Derived._fields["triple"]

f_base.is_set(cfg)    # True  — seeded from default by __init__
f_triple.is_set(cfg)  # False — callable default, not pre-populated

cfg.base = 10.0
f_base.is_set(cfg)    # True
cfg.triple            # 30.0  — computed on access

cfg.triple = 99.0
f_triple.is_set(cfg)  # True  — now explicitly stored

f_triple.unset(cfg)
f_triple.is_set(cfg)  # False — reverts to callable default
cfg.triple            # 30.0  — computed again

f_triple.reset(cfg, 5.0)    # same as cfg.triple = 5.0
f_triple.reset(cfg)         # same as unset()

Saving and loading

d = cfg.to_dict()
cfg2 = ProcessingConfig.from_dict(d)

For YAML and round-trip details see Serialization.