9. Optionals¶

A subconfig can be marked optional, so the consuming class does not need to branch on its presence.

9.1. Use OptionalConfig and NoneConfig.¶

Annotate the field with OptionalConfig[T], and call .construct() uniformly:

from pconfigs import OptionalConfig, NoneConfig, pconfig, pconfiged

@pconfiged
class System:
    config: SystemConfig

    def __init__(self):
        self.evaluator = self.config.evaluator_config.construct()


@pconfig(constructs=System)
class SystemConfig:
    evaluator_config: OptionalConfig[EvaluatorConfig]

NoneConfig.construct() returns None.

9.2. Turn the subconfig on and off.¶

Pass NoneConfig to disable, and an EvaluatorConfig(...) instance to enable:

system_without_eval = SystemConfig(
    evaluator_config=NoneConfig,
)
system_with_eval = SystemConfig(
    evaluator_config=EvaluatorConfig(...),
)

NoneConfig is a module-level singleton—like Python’s None, it is passed as a value, not constructed.

9.3. Bare union form.¶

OptionalConfig[EvaluatorConfig] evaluates to Union[EvaluatorConfig, OptionalConfig]. The bare union form is equivalent:

@pconfig(constructs=System)
class SystemConfig:
    evaluator_config: EvaluatorConfig | OptionalConfig

9.4. Why not Optional[T]?¶

Optional[T] forces every reader to branch on None:

self.evaluator = (
    self.config.evaluator_config.construct()
    if self.config.evaluator_config is not None
    else None
)

The branch is repeated wherever the field is read, and is easy to forget. OptionalConfig removes it.