======================== 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 <../configblock.configblock.html#configblock.configblock.ConfigBlock.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. .. code-block:: python 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) .. code-block:: python >>> 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: .. code-block:: python >>> 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 <../configblock.configblock.html#configblock.configblock.ConfigBlock.add_subconfig_type>`_ or the `set_default_subconfig_type <../configblock.configblock.html#configblock.configblock.ConfigBlock.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 <../configblock.configblock.html#configblock.configblock.ConfigBlock.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. .. code-block:: python 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) .. code-block:: python >>> 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 ======================================= :python:`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 :python:`isinstance(value, Mapping) == False`) the ``set_from_single_value`` method is called. By default this will set any options that were declared with the :python:`set_from_single_value` parameter set to :python:`True`. If no such options have been declared a :python:`ValueError` will be raised. .. code-block:: python >>> 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'}} :python:`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 :python:`set_from_key` method is called. By default this will set any options that were declared with the :python:`set_from_key` parameter set to :python:`True`. .. code-block:: python >>> 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'}}