diff --git a/tests/test_metaconfig.py b/tests/test_metaconfig.py index 4a3bbb6..18c7627 100644 --- a/tests/test_metaconfig.py +++ b/tests/test_metaconfig.py @@ -293,8 +293,8 @@ def test_meta_add_config_readd(): config = Config(od, session_id='new') # meta.config.add(config) - # - raises(ConflictError, "meta2.config.add(config)") + meta2.config.add(config) + assert len(list(config.config.parents())) == 2 def test_meta_new_config_wrong_name(): @@ -429,7 +429,11 @@ def test_meta_unconsistent(): meta.owner.set(owners.meta1) raises(TypeError, 'MetaConfig("string")') #same descr but conf1 already in meta - raises(ValueError, "MetaConfig([conf1, conf3])") + assert len(list(conf1.config.parents())) == 1 + assert len(list(conf3.config.parents())) == 0 + new_meta = MetaConfig([conf1, conf3]) + assert len(list(conf1.config.parents())) == 2 + assert len(list(conf3.config.parents())) == 1 #not same descr raises(ValueError, "MetaConfig([conf3, conf4])") @@ -786,39 +790,54 @@ def test_meta_callback_follower(): interface1 = Leadership('val1', '', [val1, val3, val4]) od = OptionDescription('root', '', [interface1]) maconfig = OptionDescription('rootconfig', '', [val, interface1]) - cfg = Config(maconfig, session_id='cfg1') - meta = MetaConfig([cfg]) + cfg1 = Config(maconfig, session_id='cfg1') + meta = MetaConfig([cfg1]) meta.property.read_write() - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} - meta.config('cfg1').option('val').value.set('val1') - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val1'], 'val1.val1': ['val1'], 'val1.val3': ['val1'], 'val': 'val1'} - meta.config('cfg1').option('val').value.reset() + assert cfg1.value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # + cfg1.option('val').value.set('val1') + assert cfg1.value.dict() == {'val1.val2': ['val1'], 'val1.val1': ['val1'], 'val1.val3': ['val1'], 'val': 'val1'} + # + cfg1.option('val').value.reset() meta.option('val').value.set('val1') - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val1'], 'val1.val1': ['val1'], 'val1.val3': ['val1'], 'val': 'val1'} + assert cfg1.value.dict() == {'val1.val2': ['val1'], 'val1.val1': ['val1'], 'val1.val3': ['val1'], 'val': 'val1'} + # meta.option('val').value.reset() - meta.config('cfg1').option('val1.val2', 0).value.set('val2') - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} - meta.config('cfg1').option('val1.val2', 0).value.reset() - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + cfg1.option('val1.val2', 0).value.set('val2') + assert cfg1.value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # + cfg1.option('val1.val2', 0).value.reset() + assert cfg1.value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # meta.option('val1.val2', 0).value.set('val2') - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} - meta.config('cfg1').option('val1.val3', 0).value.set('val6') - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val'], 'val1.val3': ['val6'], 'val': 'val'} + assert cfg1.value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # + meta.option('val1.val1').value.set(['val']) + assert cfg1.value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # + cfg1.option('val1.val3', 0).value.set('val6') + assert cfg1.value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val'], 'val1.val3': ['val6'], 'val': 'val'} + # meta.option('val1.val2', 0).value.reset() - meta.config('cfg1').option('val1.val3', 0).value.reset() - meta.config('cfg1').option('val1.val1').value.set(['val3']) - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val3'], 'val1.val1': ['val3'], 'val1.val3': ['val3'], 'val': 'val'} - meta.config('cfg1').option('val1.val1').value.reset() - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + cfg1.option('val1.val3', 0).value.reset() + cfg1.option('val1.val1').value.set(['val3']) + assert cfg1.value.dict() == {'val1.val2': ['val3'], 'val1.val1': ['val3'], 'val1.val3': ['val3'], 'val': 'val'} + # + cfg1.option('val1.val1').value.reset() + assert cfg1.value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # meta.option('val1.val1').value.set(['val3']) - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val3'], 'val1.val1': ['val3'], 'val1.val3': ['val3'], 'val': 'val'} - meta.config('cfg1').option('val1.val2', 0).value.set('val2') - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val3'], 'val1.val3': ['val3'], 'val': 'val'} + assert cfg1.value.dict() == {'val1.val2': ['val3'], 'val1.val1': ['val3'], 'val1.val3': ['val3'], 'val': 'val'} + # + cfg1.option('val1.val2', 0).value.set('val2') + assert cfg1.value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val3'], 'val1.val3': ['val3'], 'val': 'val'} + # meta.option('val1.val1').value.set(['val3', 'rah']) - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val2', 'rah'], 'val1.val1': ['val3', 'rah'], 'val1.val3': ['val3', 'rah'], 'val': 'val'} + assert cfg1.value.dict() == {'val1.val2': ['val2', 'rah'], 'val1.val1': ['val3', 'rah'], 'val1.val3': ['val3', 'rah'], 'val': 'val'} + # meta.option('val1.val1').value.pop(1) meta.option('val1.val1').value.set(['val4']) - assert meta.config('cfg1').value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val4'], 'val1.val3': ['val4'], 'val': 'val'} + assert cfg1.value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val4'], 'val1.val3': ['val4'], 'val': 'val'} def test_meta_reset(): @@ -861,8 +880,13 @@ def test_meta_properties_meta_copy(): meta.property.read_write() conf3 = meta.config('conf1').config.copy(session_id='conf3') + # old fashion meta2 = conf3.config.metaconfig() assert meta.config.name() == meta2.config.name() + # new method + meta2 = list(conf3.config.parents()) + assert len(meta2) == 1 + assert meta.config.name() == meta2[0].config.name() assert meta.config('conf1').value.dict() == {'ip_admin_eth0': ['192.168.1.1']} assert meta.config('conf2').value.dict() == {'ip_admin_eth0': ['192.168.1.1']} diff --git a/tests/test_mixconfig.py b/tests/test_mixconfig.py index aadbc51..0bc8597 100644 --- a/tests/test_mixconfig.py +++ b/tests/test_mixconfig.py @@ -306,7 +306,11 @@ def test_mix_unconsistent(): mix.owner.set(owners.mix1) raises(TypeError, 'MixConfig(od2, "string")') # same descr but conf1 already in mix - raises(ValueError, "MixConfig(od2, [conf1, conf3])") + assert len(list(conf1.config.parents())) == 1 + assert len(list(conf3.config.parents())) == 0 + new_mix = MixConfig(od2, [conf1, conf3]) + assert len(list(conf1.config.parents())) == 2 + assert len(list(conf3.config.parents())) == 1 # not same descr MixConfig(od2, [conf3, conf4]) @@ -647,32 +651,47 @@ def test_mix_callback_follower(): mix = MixConfig(maconfig, [cfg]) mix.property.read_write() assert mix.config('cfg1').value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # mix.config('cfg1').option('val').value.set('val1') assert mix.config('cfg1').value.dict() == {'val1.val2': ['val1'], 'val1.val1': ['val1'], 'val1.val3': ['val1'], 'val': 'val1'} + # mix.config('cfg1').option('val').value.reset() mix.option('val').value.set('val1') assert mix.config('cfg1').value.dict() == {'val1.val2': ['val1'], 'val1.val1': ['val1'], 'val1.val3': ['val1'], 'val': 'val1'} + # mix.option('val').value.reset() mix.config('cfg1').option('val1.val2', 0).value.set('val2') assert mix.config('cfg1').value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # mix.config('cfg1').option('val1.val2', 0).value.reset() assert mix.config('cfg1').value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # mix.option('val1.val2', 0).value.set('val2') + assert mix.config('cfg1').value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # + mix.option('val1.val1').value.set(['val']) assert mix.config('cfg1').value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # mix.config('cfg1').option('val1.val3', 0).value.set('val6') assert mix.config('cfg1').value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val'], 'val1.val3': ['val6'], 'val': 'val'} + # mix.option('val1.val2', 0).value.reset() mix.config('cfg1').option('val1.val3', 0).value.reset() mix.config('cfg1').option('val1.val1').value.set(['val3']) assert mix.config('cfg1').value.dict() == {'val1.val2': ['val3'], 'val1.val1': ['val3'], 'val1.val3': ['val3'], 'val': 'val'} + # mix.config('cfg1').option('val1.val1').value.reset() assert mix.config('cfg1').value.dict() == {'val1.val2': ['val'], 'val1.val1': ['val'], 'val1.val3': ['val'], 'val': 'val'} + # mix.option('val1.val1').value.set(['val3']) assert mix.config('cfg1').value.dict() == {'val1.val2': ['val3'], 'val1.val1': ['val3'], 'val1.val3': ['val3'], 'val': 'val'} + # mix.config('cfg1').option('val1.val2', 0).value.set('val2') assert mix.config('cfg1').value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val3'], 'val1.val3': ['val3'], 'val': 'val'} + # mix.option('val1.val1').value.set(['val3', 'rah']) assert mix.config('cfg1').value.dict() == {'val1.val2': ['val2', 'rah'], 'val1.val1': ['val3', 'rah'], 'val1.val3': ['val3', 'rah'], 'val': 'val'} + # mix.option('val1.val1').value.pop(1) mix.option('val1.val1').value.set(['val4']) assert mix.config('cfg1').value.dict() == {'val1.val2': ['val2'], 'val1.val1': ['val4'], 'val1.val3': ['val4'], 'val': 'val'} @@ -732,8 +751,13 @@ def test_mix_properties_mix_copy(): mix.property.read_write() conf3 = mix.config('conf1').config.copy(session_id='conf3') + # old fashion mix2 = conf3.config.metaconfig() assert mix.config.name() == mix2.config.name() + # new method + mix2 = list(conf3.config.parents()) + assert len(mix2) == 1 + assert mix.config.name() == mix2[0].config.name() assert mix.config('conf1').value.dict() == {'ip_admin_eth0': ['192.168.1.1']} assert mix.config('conf2').value.dict() == {'ip_admin_eth0': ['192.168.1.1']} @@ -1121,7 +1145,8 @@ def test_mix_add_config_readd(): # config = Config(od, session_id='new') mix.config.add(config) - raises(ConflictError, "mix2.config.add(config)") + mix2.config.add(config) + assert len(list(config.config.parents())) == 2 def test_meta_new_mixconfig(): diff --git a/tiramisu/api.py b/tiramisu/api.py index 6617fe6..e771c5e 100644 --- a/tiramisu/api.py +++ b/tiramisu/api.py @@ -780,7 +780,7 @@ class _TiramisuOptionDescription(_TiramisuOption, TiramisuConfig): option_bag = OptionBag() option_bag.set_option(opt, opt.impl_getpath(), - None, + None, config_bag) if opt.impl_is_optiondescription(): config_bag.context.cfgimpl_get_settings().validate_properties(option_bag) @@ -1339,6 +1339,7 @@ class _TiramisuContextConfig(TiramisuConfig, _TiramisuContextConfigReset): session_id=None, persistent=False, storage=None): + """Copy current config""" return self._return_config(self._config_bag.context.duplicate(session_id, persistent=persistent, storage=storage)) @@ -1348,6 +1349,7 @@ class _TiramisuContextConfig(TiramisuConfig, _TiramisuContextConfigReset): persistent=False, storage=None, metaconfig_prefix=None): + """Copy current config with all parents""" return self._return_config(self._config_bag.context.duplicate(session_id, persistent=persistent, storage=storage, @@ -1355,9 +1357,19 @@ class _TiramisuContextConfig(TiramisuConfig, _TiramisuContextConfigReset): deep=True)) def metaconfig(self): - return self._return_config(self._config_bag.context.cfgimpl_get_meta()) + """Get first meta configuration (obsolete please use parents)""" + try: + return next(self.parents()) + except StopIteration: + return None + + def parents(self): + """Get all parents of current config""" + for parent in self._config_bag.context.get_parents(): + yield self._return_config(parent) def path(self): + """Get path from config (all parents name)""" return self._config_bag.context.cfgimpl_get_config_path() diff --git a/tiramisu/config.py b/tiramisu/config.py index 58b264f..1fdb3c0 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -599,7 +599,7 @@ class _CommonConfig(SubConfig): '_impl_settings', '_impl_properties_cache', '_impl_permissives_cache', - '_impl_meta', + 'parents', 'impl_type') def _impl_build_all_caches(self): @@ -609,9 +609,9 @@ class _CommonConfig(SubConfig): config_bag = ConfigBag(context=self) descr.impl_build_force_store_values(config_bag) - def cfgimpl_get_meta(self): - if self._impl_meta is not None: - return self._impl_meta() + def get_parents(self): + for parent in self.parents: + yield parent() # information def impl_set_information(self, key, value): @@ -689,19 +689,27 @@ class _CommonConfig(SubConfig): duplicated_config.cfgimpl_reset_cache(None, None) if child is not None: duplicated_config._impl_children.append(child) - child._impl_meta = weakref.ref(duplicated_config) - if self._impl_meta: + child.parents.append(weakref.ref(duplicated_config)) + if self.parents: if deep: - duplicated_config = self._impl_meta().duplicate(deep=deep, - storage=storage, - metaconfig_prefix=metaconfig_prefix, - child=duplicated_config, - persistent=persistent) + for parent in self.parents: + duplicated_config = parent().duplicate(deep=deep, + storage=storage, + metaconfig_prefix=metaconfig_prefix, + child=duplicated_config, + persistent=persistent) else: - duplicated_config._impl_meta = self._impl_meta - self._impl_meta()._impl_children.append(duplicated_config) + duplicated_config.parents = self.parents + for parent in self.parents: + parent()._impl_children.append(duplicated_config) return duplicated_config + def cfgimpl_get_config_path(self): + path = self._impl_name + for parent in self.parents: + path = parent().cfgimpl_get_config_path() + '.' + path + return path + # ____________________________________________________________ class KernelConfig(_CommonConfig): @@ -733,7 +741,7 @@ class KernelConfig(_CommonConfig): :param persistent: if persistent, don't delete storage when leaving :type persistent: `boolean` """ - self._impl_meta = None + self.parents = [] self._impl_symlink = [] self._display_name = display_name if isinstance(descr, Leadership): @@ -774,11 +782,6 @@ class KernelConfig(_CommonConfig): def impl_getname(self): return self._impl_name - def cfgimpl_get_config_path(self): - if self._impl_meta is None or self._impl_meta() is None: - return self._impl_name - return self._impl_meta().cfgimpl_get_config_path() + '.' + self._impl_name - class KernelGroupConfig(_CommonConfig): __slots__ = ('__weakref__', @@ -804,7 +807,7 @@ class KernelGroupConfig(_CommonConfig): raise ConflictError(_('config name must be uniq in ' 'groupconfig for "{0}"').format(name)) self._impl_children = children - self._impl_meta = None + self.parents = [] session_id = gen_storage_id(session_id, self) assert valid_name(session_id), _("invalid session ID: {0} for config").format(session_id) super().__init__(_descr, @@ -973,11 +976,6 @@ class KernelGroupConfig(_CommonConfig): return child raise ConfigError(_('unknown config "{}"').format(name)) - def cfgimpl_get_config_path(self): - if self._impl_meta is None or self._impl_meta() is None: - return self._impl_name - return self._impl_meta().cfgimpl_get_config_path() + '.' + self._impl_name - class KernelMixConfig(KernelGroupConfig): __slots__ = ('_display_name', @@ -998,9 +996,7 @@ class KernelMixConfig(KernelGroupConfig): for child in children: if not isinstance(child, (KernelConfig, KernelMixConfig)): raise TypeError(_("child must be a Config, MixConfig or MetaConfig")) - if child.cfgimpl_get_meta() is not None: - raise ValueError(_("child has already a {}config's").format(self.impl_type)) - child._impl_meta = weakref.ref(self) + child.parents.append(weakref.ref(self)) properties, permissives, values, session_id = get_storages(self, session_id, persistent, @@ -1183,13 +1179,11 @@ class KernelMixConfig(KernelGroupConfig): def add_config(self, apiconfig): config = apiconfig._config_bag.context - if config._impl_meta is not None: - raise ConflictError(_('config is already in a metaconfig')) if config.impl_getname() in [child.impl_getname() for child in self._impl_children]: raise ConflictError(_('config name must be uniq in ' 'groupconfig for {0}').format(config.impl_getname())) - config._impl_meta = weakref.ref(self) + config.parents.append(weakref.ref(self)) self._impl_children.append(config) config.cfgimpl_reset_cache(None, None) @@ -1199,12 +1193,21 @@ class KernelMixConfig(KernelGroupConfig): if session_id is not None: for idx, child in enumerate(self._impl_children): if session_id == child.impl_getname(): - child._impl_meta = None child.cfgimpl_reset_cache(None, None) - return self._impl_children.pop(idx) - raise ConfigError(_('cannot find the config {}').format(session_id)) + self._impl_children.pop(idx) + break + else: + raise ConfigError(_('cannot find the config {}').format(session_id)) if config is not None: - self._impl_children.pop(config) + self._impl_children.remove(config) + child = config + for index, parent in enumerate(child.parents): + if parent() == self: + child.parents.pop(index) + break + else: + raise ConfigError(_('cannot find the config {}').format(self.session_id)) + return child class KernelMetaConfig(KernelMixConfig): @@ -1285,7 +1288,7 @@ class KernelMetaConfig(KernelMixConfig): config.cfgimpl_get_settings().rw_remove = self.cfgimpl_get_settings().rw_remove config.cfgimpl_get_settings().default_properties = self.cfgimpl_get_settings().default_properties - config._impl_meta = weakref.ref(self) + config.parents.append(weakref.ref(self)) self._impl_children.append(config) return config diff --git a/tiramisu/value.py b/tiramisu/value.py index 6867b9b..7199ca7 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -140,13 +140,13 @@ class Values(object): def getdefaultvalue(self, option_bag): """get default value: - - get meta config value or + - get parents config value or - get calculated value or - get default value """ - moption_bag = self._get_meta(option_bag) - if moption_bag: - # retrieved value from meta config + moption_bag = self._get_modified_parent(option_bag) + if moption_bag is not None: + # retrieved value from parent config return moption_bag.config_bag.context.cfgimpl_get_values().get_cached_value(moption_bag) if option_bag.option.impl_has_callback(): @@ -328,38 +328,56 @@ class Values(object): option_bag.index, commit) - def _get_meta(self, - option_bag): - context = option_bag.config_bag.context - meta = context.cfgimpl_get_meta() - if meta is None: - return None + def _get_modified_parent(self, + option_bag): + """ Search in differents parents a Config with a modified value and return it + If not found, return None + For follower option, return the Config where leader is modified + """ + def build_option_bag(option_bag, parent): + doption_bag = option_bag.copy() + config_bag = option_bag.config_bag.copy() + config_bag.context = parent + config_bag.unrestraint() + doption_bag.config_bag = config_bag + return doption_bag + if option_bag.option.impl_is_follower(): leader = option_bag.option.impl_get_leadership().get_leader() leaderpath = leader.impl_getpath() - # follower could be a "meta" only if leader hasn't value if self._p_.hasvalue(leaderpath, index=None): return None - doption_bag = option_bag.copy() - config_bag = option_bag.config_bag.copy() - config_bag.context = meta - doption_bag.config_bag = config_bag - if 'force_metaconfig_on_freeze' in option_bag.properties: - # remove force_metaconfig_on_freeze only if option in metaconfig - # hasn't force_metaconfig_on_freeze properties - ori_properties = doption_bag.properties - del doption_bag.properties - if not self.force_to_metaconfig(doption_bag): - doption_bag.properties = ori_properties - {'force_metaconfig_on_freeze'} - else: - doption_bag.properties = ori_properties - config_bag.unrestraint() - meta_option_bag = meta.cfgimpl_get_values().getowner(doption_bag, - only_default=True) - if meta_option_bag == owners.default: - return None - return meta_option_bag + config_bag = option_bag.config_bag + leader_option_bag = OptionBag() + leader_option_bag.set_option(leader, + leaderpath, + None, + config_bag) + leader_option_bag = self._get_modified_parent(leader_option_bag) + if leader_option_bag is None: + return None + new_config_bag = leader_option_bag.config_bag + if not new_config_bag.context.cfgimpl_get_values()._p_.hasvalue(option_bag.path, + index=option_bag.index): + return None + return build_option_bag(option_bag, new_config_bag.context) + for parent in option_bag.config_bag.context.get_parents(): + doption_bag = build_option_bag(option_bag, parent) + if 'force_metaconfig_on_freeze' in option_bag.properties: + # remove force_metaconfig_on_freeze only if option in metaconfig + # hasn't force_metaconfig_on_freeze properties + ori_properties = doption_bag.properties + del doption_bag.properties + if not self.force_to_metaconfig(doption_bag): + doption_bag.properties = ori_properties - {'force_metaconfig_on_freeze'} + else: + doption_bag.properties = ori_properties + parent_owner = parent.cfgimpl_get_values().getowner(doption_bag, + only_default=True) + if parent_owner != owners.default: + return doption_bag + return None #______________________________________________________________________ @@ -408,8 +426,8 @@ class Values(object): index=option_bag.index) if validate_meta is not False and (owner is owners.default or \ 'frozen' in option_bag.properties and 'force_metaconfig_on_freeze' in option_bag.properties): - moption_bag = self._get_meta(option_bag) - if moption_bag: + moption_bag = self._get_modified_parent(option_bag) + if moption_bag is not None: owner = moption_bag.config_bag.context.cfgimpl_get_values().getowner(moption_bag, only_default=only_default) elif 'force_metaconfig_on_freeze' in option_bag.properties: