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.