Config composition

Inheritance

Standard Python inheritance works naturally. Fields are collected from the full MRO automatically, so a child class has all parent fields plus any it adds or overrides:

from cfx import Config, Int, Float, Options

class ProcessingConfig(Config):
    confid = "processing"
    iterations = Int(100, "Number of iterations", ge=1)
    threshold = Float(0.5, "Acceptance threshold", ge=0.0, le=1.0)
    mode = Options(("fast", "balanced", "thorough"), "Processing mode")

class ChildConfig(ProcessingConfig):
    confid = "child"
    mode = Options(("fast", "balanced", "thorough"), "Processing mode",
                   default_value="balanced")  # changed default
    max_size = Int(1000, "Maximum item count", ge=1)
>>> cfg = ChildConfig()
>>> cfg.iterations
100
>>> cfg.mode
'balanced'
>>> cfg.max_size
1000

Composing with components=

Pass a list of config classes as components= to assemble them into a larger config:

from cfx import Config, Int, String

class FormatConfig(Config):
    confid = "format"
    precision = Int(6, "Decimal precision")
    encoding = String("utf-8", "Output encoding")

class PipelineConfig(Config, components=[ProcessingConfig, FormatConfig]):
    pass
>>> cfg = PipelineConfig()
>>> cfg.processing.iterations
100
>>> cfg.format.precision
6

Each component becomes a sub-object accessible by its confid.

The confid attribute

Every config class has a confid string identifier. If you do not set it explicitly, it defaults to the lowercase class name:

class QuickConfig(Config):
    field1 = Int(1, "A field")

QuickConfig.confid   # 'quickconfig'

Set confid explicitly whenever a class will be used as a component, so the resulting attribute name is predictable:

class FormatConfig(Config):
    confid = "format"  # accessible as parent_cfg.format
    precision = Int(6, "Decimal precision")

confid becomes the attribute name in nested composition and the key in nested serialization.

Nested sub-configs

Each component becomes a fresh sub-object on every parent instance, accessible as an attribute named after its confid:

cfg = PipelineConfig()
cfg.processing.iterations = 200
cfg.format.precision = 3

print(cfg.processing)   # full table for ProcessingConfig
print(cfg.format)       # full table for FormatConfig

Each parent instance gets its own independent sub-configs. Mutating cfg1.processing has no effect on cfg2.processing.

to_dict() produces a nested dict keyed by confid; from_dict() routes each sub-dict back to its component class:

.. doctest::
>>> cfg2 = PipelineConfig()
>>> cfg2.processing.iterations = 200
>>> cfg2.format.precision = 3
>>> cfg2.to_dict()
{'processing': {'iterations': 200, 'threshold': 0.5, 'mode': 'fast'}, 'format': {'precision': 3, 'encoding': 'utf-8'}}
>>> cfg3 = PipelineConfig.from_dict(cfg2.to_dict())
>>> cfg3.processing.iterations
200

Duplicate ``confid`` values across components are not allowed. If two classes in the components= list share a confid, a ValueError is raised at class-definition time.

Mixed own fields and components

A config that uses components= can also declare its own flat fields alongside the sub-configs:

from cfx import String, Bool

class PipelineConfig(Config, components=[ProcessingConfig, FormatConfig]):
    confid = "pipeline"
    run_id = String("run_01", "Run identifier")
    dry_run = Bool(False, "Validate only; skip writes")
>>> from cfx import String, Bool
>>> class PipelineConfig(Config, components=[ProcessingConfig, FormatConfig]):
...     confid = "pipeline"
...     run_id = String("run_01", "Run identifier")
...     dry_run = Bool(False, "Validate only; skip writes")
>>> cfg = PipelineConfig()
>>> cfg.run_id
'run_01'
>>> cfg.processing.iterations
100
>>> cfg.format.precision
6

Own fields appear before sub-config fields in the display table. to_dict produces a flat key for each own field and a nested sub-dict for each component:

.. doctest::
>>> sorted(cfg.to_dict().keys())
['dry_run', 'format', 'processing', 'run_id']

For projecting a config tree into a different namespace, or exposing a curated subset of fields under new names, see Views.