Adding Subconfigurations¶
Blocks can be nested inside each other forming a more complicated hierarchy of subconfigurations. Subconfigurations can be added as either predefined subconfigurations or extra subconfigurations. Predefined subconfigurations during declaration. Extra subconfigurations are determined during configuration from the user-supplied data.
Predefined Configurations¶
Predefined subconfigurations are added using the add_subconfig method.
By default new subconfigurations are created ‘inactive’; by default most iteration methods will not pass through inactive subconfigurations, this includes all of the validation methods.
This means that what they represent is optional by default.
This can be changed by setting the always
parameter to True
.
Once any option on the subconfiguration is set it will be made active.
class ExampleSubconfigBlock(ConfigBlock):
"""Example configuration block"""
def __init__(self):
super().__init__()
self.add_option("Message", default="Hello World!")
self.add_option("Level", default=0, type=int)
self.add_option("Required")
cfg = ConfigBlock()
cfg.add_subconfig("OptionalSubConfig", ExampleSubconfigBlock())
cfg.add_subconfig("RequiredSubConfig2", ExampleSubconfigBlock(), always=True)
>>> cfg.to_dict()
Required option '/RequiredSubConfig2/Required' missing!
ValueError: Invalid configuration!
>>> cfg["RequiredSubConfig2"]["Required"] = "Value"
>>> cfg.to_dict()
{'RequiredSubConfig2': {'Message': 'Hello World!', 'Level': 0, 'Required': 'Value'}}
To activate the subconfiguration it’s enough to just set an empty dictionary:
>>> cfg["OptionalSubConfig"] = {}
>>> cfg.to_dict()
Required option '/OptionalSubConfig/Required' missing!
ValueError: Invalid configuration!
>>> cfg["OptionalSubConfig"]["Required"] = "OtherValue"
>>> cfg.to_dict()
{'OptionalSubConfig': {'Message': 'Hello World!', 'Level': 0, 'Required': 'OtherValue'}, 'RequiredSubConfig2': {'Message': 'Hello World!', 'Level': 0, 'Required': 'Value'}}
Extra Configurations¶
Extra configurations are not created during declaration but during configuration.
During declaration factory functions can be registered on the block that will be called when the configuration demands.
This can be done through either the add_subconfig_type or the set_default_subconfig_type methods.
During configuration, if a user supplies a key which does not match an existing option or subconfiguration the available factories will be checked.
If the value provided by the user contains a Type
key then the corresponding named factory (added through the add_subconfig_type method) will be called.
Otherwise if there is a default factory declared that will be called.
If there is no default factory then a KeyError
will be raised.
class SubconfigA(ConfigBlock):
def __init__(self):
super().__init__()
self.add_option("Option1", type=int)
self.add_option("Option2", default="Value")
class DefaultSubconfig(ConfigBlock):
def __init__(self):
super.__init__()
self.add_option("Option3", type=float)
cfg = ConfigBlock()
cfg.add_subconfig_type("A", SubconfigA)
cfg.set_default_subconfig_type(DefaultSubconfig)
>>> cfg["MyA"] = {
... "Type": "A",
... "Option1": 20,
... "Option2": "MyValue",
... }
>>> cfg["B"] = {"Option3": 4.5}
>>> cfg.to_dict()
{'MyA': {'Option1': 20, 'Option2': 'MyValue'}, 'B': {'Option3': 4.5}}
As extra subconfigurations are only set from the user’s configuration they are always considered active.
Shortcuts for setting subconfigurations¶
set_from_single_value
¶
A block can be set up so that it can be set from a single value rather than from a dictionary.
When a block value is set from a non-dictionary value (something for which isinstance(value, Mapping) == False
) the set_from_single_value
method is called.
By default this will set any options that were declared with the set_from_single_value
parameter set to True
.
If no such options have been declared a ValueError
will be raised.
>>> cfg = ConfigBlock()
>>> subcfg = ConfigBlock()
>>> subcfg.add_option("Option", set_from_single_value=True)
>>> subcfg.add_option("Default", default="NotSet")
>>> cfg.add_subconfig("SubCfg", subcfg)
>>> cfg["SubCfg"] = "Value"
>>> cfg.to_dict()
{'SubCfg': {'Option': 'Value', 'Default': 'NotSet'}}
set_from_key
¶
A block can be set up so that its values can be configured from its key in its parent block.
When a block is added to its parent the set_from_key
method is called.
By default this will set any options that were declared with the set_from_key
parameter set to True
.
>>> def mk_subconfig():
>>> subcfg = ConfigBlock()
>>> subcfg.add_option("Name", set_from_key=True)
>>> subcfg.add_option("Other")
>>> return subcfg
>>> cfg = ConfigBlock()
>>> cfg.set_default_subconfig_type(mk_subconfig)
>>> cfg["SubCfg"] = {"Other": "Value"}
>>> cfg.to_dict()
{'SubCfg': {'Name': 'SubCfg', 'Other': 'Value'}}