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'}}