diff --git a/tests/test_cache.py b/tests/test_cache.py index c6b426b..2911e43 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -268,7 +268,7 @@ def test_cache_leadership(): assert set(cache.keys()) == set([None, 'ip_admin_eth0', 'ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0']) assert set(cache['ip_admin_eth0'].keys()) == set([None]) assert set(cache['ip_admin_eth0.ip_admin_eth0'].keys()) == set([None]) - assert set(cache['ip_admin_eth0.netmask_admin_eth0'].keys()) == set([0, None]) + assert set(cache['ip_admin_eth0.netmask_admin_eth0'].keys()) == {0} # cfg.option('ip_admin_eth0.ip_admin_eth0').value.set(['192.168.1.2', '192.168.1.1']) cfg.option('ip_admin_eth0.ip_admin_eth0').value.get() @@ -286,7 +286,7 @@ def test_cache_leadership(): assert set(cache.keys()) == set([None, 'ip_admin_eth0', 'ip_admin_eth0.ip_admin_eth0', 'ip_admin_eth0.netmask_admin_eth0']) assert set(cache['ip_admin_eth0'].keys()) == set([None]) assert set(cache['ip_admin_eth0.ip_admin_eth0'].keys()) == set([None]) - assert set(cache['ip_admin_eth0.netmask_admin_eth0'].keys()) == set([None, 0, 1]) + assert set(cache['ip_admin_eth0.netmask_admin_eth0'].keys()) == set([0, 1]) #DEL, insert, ... # assert not list_sessions() @@ -398,19 +398,20 @@ def test_cache_leader_and_followers(): val_val2_props = {idx_val2: (val1_val2_props, None), None: (set(), None)} compare(settings.get_cached(), {None: {None: (set(global_props), None)}, 'val1.val1': {None: (val1_val1_props, None)}, - 'val1.val2': val_val2_props}) + }) compare(values.get_cached(), {'val1.val1': {None: ([None], None, True)}}) cfg.value.dict() #has value idx_val2 = 0 val_val2 = None - val_val2_props = {idx_val2: (val1_val2_props, None), None: (set(), None)} + val_val2_props = {idx_val2: (val1_val2_props, None)} compare(settings.get_cached(), {None: {None: (global_props, None)}, 'val1': {None: (val1_props, None)}, 'val1.val1': {None: (val1_val1_props, None)}, 'val1.val2': val_val2_props}) compare(values.get_cached(), {'val1.val1': {None: ([None], None)}, - 'val1.val2': {idx_val2: (val_val2, None)}}) + 'val1.val2': {idx_val2: (val_val2, None)}, + }) cfg.option('val1.val1').value.set([undefined, undefined]) cfg.value.dict() cfg.option('val1.val2', 1).value.set('oui') @@ -446,7 +447,7 @@ def test_cache_leader_callback(): cfg.option('val1.val1').value.set([undefined]) compare(settings.get_cached(), {None: {None: (set(global_props), None)}, 'val1.val1': {None: (val1_val1_props, None)}, - 'val1.val2': {None: (val1_val2_props, None)}}) + }) compare(values.get_cached(), {'val1.val1': {None: ([None], None, True)}}) cfg.value.dict() diff --git a/tests/test_option_setting.py b/tests/test_option_setting.py index dc84a38..12037db 100644 --- a/tests/test_option_setting.py +++ b/tests/test_option_setting.py @@ -880,3 +880,17 @@ def test_pprint_not_todict(): def test_property_invalid_type(): with pytest.raises(ValueError): s3 = StrOption("string3", "", default=["string"], default_multi="string", multi=True, properties=(1,)) + + +def test_settings_list_with_follower(): + leader = StrOption("leader", "leader", default=['leader'], multi=True) + option = StrOption("str", "str", default_multi="dhcp", multi=True, properties=frozenset({'disabled'})) + ip = StrOption(name="ip", + doc="ip", + multi=True, + properties=frozenset({"basic", "mandatory", Calculation(calc_value, Params(ParamValue('disabled'), kwargs={'condition': ParamOption(option, notraisepropertyerror=True), 'expected': ParamValue("ipv4"), 'reverse_condition': ParamValue(True)}), calc_value_property_help)}), + ) + descr = Leadership("root", "", [leader, option, ip]) + cfg = Config(OptionDescription('root', 'root', [descr])) + cfg.property.read_write() + assert len(cfg.option('root').list('all')) == 2 diff --git a/tiramisu/api.py b/tiramisu/api.py index 3410938..92e6a4a 100644 --- a/tiramisu/api.py +++ b/tiramisu/api.py @@ -89,12 +89,15 @@ class TiramisuHelp: class CommonTiramisu(TiramisuHelp): _validate_properties = True - def _get_options_bag(self) -> OptionBag: + def _get_options_bag(self, + follower_not_apply_requires: bool, + ) -> OptionBag: try: options_bag = self._config_bag.context.get_sub_option_bag(self._config_bag, self._path, self._index, self._validate_properties, + follower_not_apply_requires=follower_not_apply_requires, ) except AssertionError as err: raise ConfigError(str(err)) @@ -122,7 +125,7 @@ def option_type(typ): )] kwargs['is_group'] = True return func(self, options_bag, *args[1:], **kwargs) - options_bag = self._get_options_bag() + options_bag = self._get_options_bag('with_index' not in types) option = options_bag[-1].option if option.impl_is_optiondescription() and 'optiondescription' in types or \ not option.impl_is_optiondescription() and ( @@ -135,12 +138,13 @@ def option_type(typ): if not option.impl_is_optiondescription() and \ not option.impl_is_symlinkoption() and \ option.impl_is_follower(): + # default is "without_index" if 'with_index' not in types and 'with_or_without_index' not in types and \ self._index is not None: msg = _('please do not specify index ' f'({self.__class__.__name__}.{func.__name__})') raise ConfigError(_(msg)) - if 'with_index' in types and self._index is None: + if self._index is None and 'with_index' in types: msg = _('please specify index with a follower option ' f'({self.__class__.__name__}.{func.__name__})') raise ConfigError(msg) @@ -251,7 +255,7 @@ class _TiramisuOptionOptionDescription: option_bag = options_bag[-1] return option_bag.path - @option_type(['optiondescription', 'option', 'symlink']) + @option_type(['optiondescription', 'option', 'symlink', 'with_or_without_index']) def has_dependency(self, options_bag: List[OptionBag], self_is_dep=True, @@ -355,7 +359,7 @@ class _TiramisuOptionOption(_TiramisuOptionOptionDescription): return 'optiondescription' return option_bag.option.get_type() - @option_type('option') + @option_type(['option', 'with_or_without_index']) def pattern(self, options_bag: List[OptionBag]) -> str: """Get the option pattern""" option_bag = options_bag[-1] diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index b9a05cd..f2093fa 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -539,6 +539,8 @@ def carry_out_calculation(option, - tuple with option and boolean's force_permissive (True when don't raise if PropertiesOptionError) Values could have multiple values only when key is ''.""" + if not option.impl_is_optiondescription() and option.impl_is_follower() and index is None: + raise Exception('follower must have index in carry_out_calculation!') def fake_items(iterator): return ((None, i) for i in iterator) args = [] diff --git a/tiramisu/config.py b/tiramisu/config.py index 21ffaf6..16bc7fa 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -342,6 +342,7 @@ class _SubConfig: opt.impl_getpath(), None, True, + follower_not_apply_requires=flatten_leadership, )[-1], types=types, recursive=recursive, @@ -721,6 +722,7 @@ class _CommonConfig(_SubConfig): validate_properties: bool, leadership_length: int=None, properties=undefined, + follower_not_apply_requires: bool=False, ) -> List[OptionBag]: """Get the suboption for path and the name of the option :returns: tuple (config, name)""" @@ -734,12 +736,12 @@ class _CommonConfig(_SubConfig): option_bag = bag if option_bag.option != option_bag.config_bag.context.get_description(): path = path[len(option_bag.path) + 1:] - path = path.split('.') - last_idx = len(path) - 1 + split_path = path.split('.') + last_idx = len(split_path) - 1 suboption = option_bag.option options_bag = [] sub_option_bag = option_bag - for idx, step in enumerate(path): + for idx, step in enumerate(split_path): if not suboption.impl_is_optiondescription(): raise TypeError(f'{suboption.impl_getpath()} is not an optiondescription') @@ -753,9 +755,9 @@ class _CommonConfig(_SubConfig): ) if idx == last_idx: option_index = index - else: - option_index = None - if idx == last_idx: + apply_requires = not follower_not_apply_requires or \ + option.impl_is_optiondescription() or \ + not option.impl_is_follower() if option_index is not None: if option.impl_is_optiondescription() or \ option.impl_is_symlinkoption() or \ @@ -771,11 +773,14 @@ class _CommonConfig(_SubConfig): f'"{option.impl_get_display_name()}"')) option_properties = properties else: + option_index = None + apply_requires = True option_properties = undefined sub_option_bag = OptionBag(option, option_index, option_bag.config_bag, properties=option_properties, + apply_requires=apply_requires, ) if validate_properties: self.get_settings().validate_properties(sub_option_bag) diff --git a/tiramisu/option/leadership.py b/tiramisu/option/leadership.py index 5a852e5..9737e52 100644 --- a/tiramisu/option/leadership.py +++ b/tiramisu/option/leadership.py @@ -165,16 +165,20 @@ class Leadership(OptionDescription): dyn = self values = config_bag.context.get_values() for idx, follower in enumerate(dyn.get_children(config_bag)): + if not idx: + # it's a master + apply_requires = True + indexes = [None] + else: + apply_requires = False + indexes = range(len(value)) foption_bag = OptionBag(follower, None, config_bag, + apply_requires=apply_requires, ) if 'force_store_value' not in foption_bag.properties: continue - if idx == 0: - indexes = [None] - else: - indexes = range(len(value)) for index in indexes: foption_bag_index = OptionBag(follower, index,