diff --git a/tests/test_dyn_optiondescription.py b/tests/test_dyn_optiondescription.py index d04d4f2..8d9b0de 100644 --- a/tests/test_dyn_optiondescription.py +++ b/tests/test_dyn_optiondescription.py @@ -14,6 +14,12 @@ from tiramisu.error import PropertiesOptionError, ConfigError, ConflictError from tiramisu.storage import list_sessions +class ConvertDynOptionDescription(DynOptionDescription): + def convert_suffix_to_path(self, suffix): + # remove dot with is illegal + return suffix.replace('.', '') + + def teardown_function(function): assert list_sessions() == [], 'session list is not empty when leaving "{}"'.format(function.__name__) @@ -39,6 +45,10 @@ def return_list(val=None, suffix=None): return ['val1', 'val2'] +def return_list_dot(val=None, suffix=None): + return ['val.1', 'val.2'] + + def return_same_list(*args, **kwargs): return ['val1', 'val1'] @@ -911,6 +921,7 @@ def test_leadership_default_multi_dyndescription(): assert api.option('od.stval1.st1val1.st2val1', 0).owner.isdefault() assert api.option('od.stval2.st1val2.st1val2').owner.isdefault() + def test_leadership_dyndescription_param(): val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) odval = OptionDescription('odval1', '', [val1]) @@ -1259,11 +1270,11 @@ def test_leadership_callback_samegroup_dyndescription(): api = Config(od2) owner = api.owner.get() assert api.value.dict() == {'od.stval1.st1val1.st1val1': [], - 'od.stval1.st1val1.st2val1': [], - 'od.stval1.st1val1.st3val1': [], - 'od.stval2.st1val2.st1val2': [], - 'od.stval2.st1val2.st2val2': [], - 'od.stval2.st1val2.st3val2': []} + 'od.stval1.st1val1.st2val1': [], + 'od.stval1.st1val1.st3val1': [], + 'od.stval2.st1val2.st1val2': [], + 'od.stval2.st1val2.st2val2': [], + 'od.stval2.st1val2.st3val2': []} assert api.option('od.stval1.st1val1.st1val1').value.get() == [] assert api.option('od.stval2.st1val2.st1val2').value.get() == [] assert api.option('od.stval1.st1val1.st1val1').owner.isdefault() @@ -1271,11 +1282,11 @@ def test_leadership_callback_samegroup_dyndescription(): # api.option('od.stval1.st1val1.st1val1').value.set(['yes']) assert api.value.dict() == {'od.stval1.st1val1.st1val1': ['yes'], - 'od.stval1.st1val1.st2val1': [None], - 'od.stval1.st1val1.st3val1': [None], - 'od.stval2.st1val2.st1val2': [], - 'od.stval2.st1val2.st2val2': [], - 'od.stval2.st1val2.st3val2': []} + 'od.stval1.st1val1.st2val1': [None], + 'od.stval1.st1val1.st3val1': [None], + 'od.stval2.st1val2.st1val2': [], + 'od.stval2.st1val2.st2val2': [], + 'od.stval2.st1val2.st3val2': []} assert api.option('od.stval1.st1val1.st1val1').owner.get() == owner assert api.option('od.stval1.st1val1.st2val1', 0).owner.isdefault() assert api.option('od.stval1.st1val1.st3val1', 0).owner.isdefault() @@ -1283,11 +1294,11 @@ def test_leadership_callback_samegroup_dyndescription(): # api.option('od.stval1.st1val1.st2val1', 0).value.set('yes') assert api.value.dict() == {'od.stval1.st1val1.st1val1': ['yes'], - 'od.stval1.st1val1.st2val1': ['yes'], - 'od.stval1.st1val1.st3val1': ['yes'], - 'od.stval2.st1val2.st1val2': [], - 'od.stval2.st1val2.st2val2': [], - 'od.stval2.st1val2.st3val2': []} + 'od.stval1.st1val1.st2val1': ['yes'], + 'od.stval1.st1val1.st3val1': ['yes'], + 'od.stval2.st1val2.st1val2': [], + 'od.stval2.st1val2.st2val2': [], + 'od.stval2.st1val2.st3val2': []} assert api.option('od.stval1.st1val1.st1val1').owner.get() == owner assert api.option('od.stval1.st1val1.st2val1', 0).owner.get() == owner assert api.option('od.stval1.st1val1.st3val1', 0).owner.isdefault() @@ -1344,3 +1355,107 @@ def test_invalid_name_dyndescription(): od1 = OptionDescription('od', '', [dod]) cfg = Config(od1) raises(ValueError, "cfg.value.dict()") + + +def test_leadership_dyndescription_convert(): + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True) + stm = Leadership('st1', '', [st1, st2]) + st = ConvertDynOptionDescription('st', '', [stm], suffixes=Calculation(return_list_dot)) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od]) + api = Config(od2) + owner = api.owner.get() + # + assert api.value.dict() == {'od.stval1.st1val1.st2val1': [], 'od.stval2.st1val2.st2val2': [], 'od.stval2.st1val2.st1val2': [], 'od.stval1.st1val1.st1val1': []} + assert api.option('od.stval1.st1val1.st1val1').value.get() == [] + assert api.option('od.stval2.st1val2.st1val2').value.get() == [] + assert api.option('od.stval1.st1val1.st1val1').owner.isdefault() + assert api.option('od.stval2.st1val2.st1val2').owner.isdefault() + # + api.option('od.stval1.st1val1.st1val1').value.set(['yes']) + assert api.value.dict() == {'od.stval1.st1val1.st2val1': [None], 'od.stval2.st1val2.st2val2': [], 'od.stval2.st1val2.st1val2': [], 'od.stval1.st1val1.st1val1': ['yes']} + assert api.option('od.stval1.st1val1.st1val1').value.get() == ['yes'] + assert api.option('od.stval1.st1val1.st2val1', 0).value.get() == None + assert api.option('od.stval2.st1val2.st1val2').value.get() == [] + assert api.option('od.stval1.st1val1.st1val1').owner.get() == owner + assert api.option('od.stval1.st1val1.st2val1', 0).owner.isdefault() + assert api.option('od.stval2.st1val2.st1val2').owner.isdefault() + # + api.option('od.stval1.st1val1.st2val1', 0).value.set('no') + assert api.option('od.stval1.st1val1.st1val1').value.get() == ['yes'] + assert api.option('od.stval1.st1val1.st2val1', 0).value.get() == 'no' + assert api.option('od.stval2.st1val2.st1val2').value.get() == [] + assert api.option('od.stval1.st1val1.st1val1').owner.get() == owner + assert api.option('od.stval1.st1val1.st2val1', 0).owner.get() == owner + assert api.option('od.stval2.st1val2.st1val2').owner.isdefault() + # + api.option('od.stval1.st1val1.st1val1').value.pop(0) + assert api.option('od.stval1.st1val1.st1val1').value.get() == [] + assert api.option('od.stval2.st1val2.st1val2').value.get() == [] + assert api.option('od.stval1.st1val1.st1val1').owner.get() == owner + assert api.option('od.stval2.st1val2.st1val2').owner.isdefault() + # + api.option('od.stval1.st1val1.st1val1').value.set(['yes']) + api.option('od.stval1.st1val1.st2val1', 0).value.set('yes') + assert api.option('od.stval1.st1val1.st1val1').owner.get() == owner + assert api.option('od.stval1.st1val1.st2val1', 0).owner.get() == owner + assert api.option('od.stval2.st1val2.st1val2').owner.isdefault() + assert api.option('od.stval1.st1val1.st1val1').owner.get() == owner + api.option('od.stval1.st1val1.st2val1', 0).value.reset() + assert api.option('od.stval1.st1val1.st1val1').owner.get() == owner + assert api.option('od.stval1.st1val1.st2val1', 0).owner.isdefault() + # + api.option('od.stval1.st1val1.st1val1').value.set(['yes']) + api.option('od.stval1.st1val1.st2val1', 0).value.set('yes') + api.option('od.stval1.st1val1.st1val1').value.reset() + assert api.option('od.stval1.st1val1.st1val1').value.get() == [] + assert api.option('od.stval2.st1val2.st1val2').value.get() == [] + assert api.option('od.stval1.st1val1.st1val1').owner.isdefault() + assert api.option('od.stval2.st1val2.st1val2').owner.isdefault() + + +def test_leadership_callback_samegroup_dyndescription_convert(): + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True) + st3 = StrOption('st3', "", Calculation(return_dynval, Params(ParamOption(st2))), multi=True) + stm = Leadership('st1', '', [st1, st2, st3]) + stt = ConvertDynOptionDescription('st', '', [stm], suffixes=Calculation(return_list_dot)) + od1 = OptionDescription('od', '', [stt]) + od2 = OptionDescription('od', '', [od1]) + api = Config(od2) + owner = api.owner.get() + assert api.value.dict() == {'od.stval1.st1val1.st1val1': [], + 'od.stval1.st1val1.st2val1': [], + 'od.stval1.st1val1.st3val1': [], + 'od.stval2.st1val2.st1val2': [], + 'od.stval2.st1val2.st2val2': [], + 'od.stval2.st1val2.st3val2': []} + assert api.option('od.stval1.st1val1.st1val1').value.get() == [] + assert api.option('od.stval2.st1val2.st1val2').value.get() == [] + assert api.option('od.stval1.st1val1.st1val1').owner.isdefault() + assert api.option('od.stval2.st1val2.st1val2').owner.isdefault() + # + api.option('od.stval1.st1val1.st1val1').value.set(['yes']) + assert api.value.dict() == {'od.stval1.st1val1.st1val1': ['yes'], + 'od.stval1.st1val1.st2val1': [None], + 'od.stval1.st1val1.st3val1': [None], + 'od.stval2.st1val2.st1val2': [], + 'od.stval2.st1val2.st2val2': [], + 'od.stval2.st1val2.st3val2': []} + assert api.option('od.stval1.st1val1.st1val1').owner.get() == owner + assert api.option('od.stval1.st1val1.st2val1', 0).owner.isdefault() + assert api.option('od.stval1.st1val1.st3val1', 0).owner.isdefault() + assert api.option('od.stval2.st1val2.st1val2').owner.isdefault() + # + api.option('od.stval1.st1val1.st2val1', 0).value.set('yes') + assert api.value.dict() == {'od.stval1.st1val1.st1val1': ['yes'], + 'od.stval1.st1val1.st2val1': ['yes'], + 'od.stval1.st1val1.st3val1': ['yes'], + 'od.stval2.st1val2.st1val2': [], + 'od.stval2.st1val2.st2val2': [], + 'od.stval2.st1val2.st3val2': []} + assert api.option('od.stval1.st1val1.st1val1').owner.get() == owner + assert api.option('od.stval1.st1val1.st2val1', 0).owner.get() == owner + assert api.option('od.stval1.st1val1.st3val1', 0).owner.isdefault() + assert api.option('od.stval2.st1val2.st1val2').owner.isdefault() diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index f22f208..001d242 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -279,7 +279,8 @@ def manager_callback(callbk: Union[ParamOption, ParamValue], callbk_option = callbk.option if callbk_option.issubdyn(): callbk_option = callbk_option.to_dynoption(option.rootpath, - option.impl_getsuffix()) + option.impl_getsuffix(), + callbk_option.getsubdyn()) if leadership_must_have_index and callbk_option.impl_get_leadership() and index is None: raise Break() if config_bag is undefined: diff --git a/tiramisu/config.py b/tiramisu/config.py index 13ac054..924a80d 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -110,10 +110,11 @@ class SubConfig(object): for woption in option_bag.option._get_dependencies(self.cfgimpl_get_description()): option = woption() if option.impl_is_dynoptiondescription(): - subpath = option.impl_getpath().rsplit('.', 1)[0] + # it's a dynoptiondescription remove cache for all generated optiondescription for suffix in option.get_suffixes(option_bag.config_bag): - doption = option.to_dynoption(subpath, - suffix) + doption = option.to_dynoption(desc.impl_getpath(), + suffix, + option) doption_path = doption.impl_getpath() doption_bag = OptionBag() doption_bag.set_option(doption, @@ -124,13 +125,16 @@ class SubConfig(object): resetted_opts, doption_bag) elif option.issubdyn(): + # it's an option in dynoptiondescription, remove cache for all generated option dynopt = option.getsubdyn() rootpath = dynopt.impl_getpath() subpaths = [rootpath] + option.impl_getpath()[len(rootpath) + 1:].split('.')[:-1] for suffix in dynopt.get_suffixes(option_bag.config_bag): - subpath = '.'.join([subp + suffix for subp in subpaths]) + path_suffix = dynopt.convert_suffix_to_path(suffix) + subpath = '.'.join([subp + path_suffix for subp in subpaths]) doption = option.to_dynoption(subpath, - suffix) + suffix, + dynopt) doption_path = doption.impl_getpath() doption_bag = OptionBag() doption_bag.set_option(doption, diff --git a/tiramisu/option/dynoptiondescription.py b/tiramisu/option/dynoptiondescription.py index 43e810e..46975e3 100644 --- a/tiramisu/option/dynoptiondescription.py +++ b/tiramisu/option/dynoptiondescription.py @@ -63,6 +63,10 @@ class DynOptionDescription(OptionDescription): if __debug__ and isinstance(suffixes, Calculation): self._suffixes = suffixes + def convert_suffix_to_path(self, + suffix): + return suffix + def get_suffixes(self, config_bag: ConfigBag) -> List[str]: @@ -78,6 +82,7 @@ class DynOptionDescription(OptionDescription): '').format(self.impl_get_display_name(), values)) values_ = [] for val in values: + val = self.convert_suffix_to_path(val) if not isinstance(val, str) or re.match(NAME_REGEXP, val) is None: if val is not None: raise ValueError(_('invalid suffix "{}" for option "{}"' diff --git a/tiramisu/option/leadership.py b/tiramisu/option/leadership.py index 19ec78a..f75588c 100644 --- a/tiramisu/option/leadership.py +++ b/tiramisu/option/leadership.py @@ -219,7 +219,9 @@ class Leadership(OptionDescription): def to_dynoption(self, rootpath: str, - suffix: str) -> SynDynLeadership: + suffix: str, + ori_dyn) -> SynDynLeadership: return SynDynLeadership(self, rootpath, - suffix) + suffix, + ori_dyn) diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py index 313d8f3..6cfee06 100644 --- a/tiramisu/option/option.py +++ b/tiramisu/option/option.py @@ -429,7 +429,9 @@ class Option(BaseOption): def to_dynoption(self, rootpath: str, - suffix: str) -> SynDynOption: + suffix: str, + ori_dyn) -> SynDynOption: return SynDynOption(self, rootpath, - suffix) + suffix, + ori_dyn) diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py index b754fdb..0e6f30a 100644 --- a/tiramisu/option/optiondescription.py +++ b/tiramisu/option/optiondescription.py @@ -175,9 +175,10 @@ class OptionDescriptionWalk(CacheOptionDescription): cname = child.impl_getname() if name.startswith(cname): for suffix in child.get_suffixes(config_bag): - if name == cname + suffix: + if name == cname + child.convert_suffix_to_path(suffix): return child.to_dynoption(subpath, - suffix) + suffix, + child) if self.impl_get_group_type() == groups.root: raise AttributeError(_('unknown option "{0}" ' 'in root optiondescription' @@ -199,7 +200,8 @@ class OptionDescriptionWalk(CacheOptionDescription): if dyn and child.impl_is_dynoptiondescription(): for suffix in child.get_suffixes(config_bag): yield child.to_dynoption(subpath, - suffix) + suffix, + child) else: yield child @@ -305,7 +307,9 @@ class OptionDescription(OptionDescriptionWalk): def to_dynoption(self, rootpath: str, - suffix: str) -> SynDynOptionDescription: + suffix: str, + ori_dyn) -> SynDynOptionDescription: return SynDynOptionDescription(self, rootpath, - suffix) + suffix, + ori_dyn) diff --git a/tiramisu/option/syndynoption.py b/tiramisu/option/syndynoption.py index 62451f6..7dcbca2 100644 --- a/tiramisu/option/syndynoption.py +++ b/tiramisu/option/syndynoption.py @@ -29,15 +29,18 @@ class SynDynOption: __slots__ = ('rootpath', 'opt', 'suffix', + 'ori_dyn', '__weakref__') def __init__(self, opt: BaseOption, rootpath: str, - suffix: str) -> None: + suffix: str, + ori_dyn) -> None: self.opt = opt self.rootpath = rootpath self.suffix = suffix + self.ori_dyn = ori_dyn def __getattr__(self, name: str) -> Any: @@ -71,4 +74,5 @@ class SynDynOption: leadership = self.opt.impl_get_leadership() if leadership: return leadership.to_dynoption(self.rootpath, - self.suffix) + self.suffix, + self.ori_dyn) diff --git a/tiramisu/option/syndynoptiondescription.py b/tiramisu/option/syndynoptiondescription.py index 2d4083f..8062fde 100644 --- a/tiramisu/option/syndynoptiondescription.py +++ b/tiramisu/option/syndynoptiondescription.py @@ -18,7 +18,7 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ -from typing import Optional, Iterator, Union, Any, List +from typing import Optional, Iterator, Any, List from ..i18n import _ @@ -31,18 +31,24 @@ from .syndynoption import SynDynOption class SynDynOptionDescription: __slots__ = ('_opt', '_subpath', - '_suffix') + '_suffix', + 'ori_dyn') def __init__(self, opt: BaseOption, subpath: str, - suffix: str) -> None: + suffix: str, + ori_dyn) -> None: + if opt.__class__.__name__.startswith('L') and ori_dyn is None: + raise Exception() self._opt = opt if subpath is None: subpath = '' assert isinstance(subpath, str), 'subpath must be a string, not {}'.format(type(subpath)) self._subpath = subpath self._suffix = suffix + # For a Leadership inside a DynOptionDescription + self.ori_dyn = ori_dyn def __getattr__(self, name: str) -> Any: @@ -57,7 +63,6 @@ class SynDynOptionDescription: name: str, config_bag: ConfigBag, subpath: str) -> BaseOption: - #FIXME -> Union[BaseOption, SynDynOptionDescription]: if name.endswith(self._suffix): oname = name[:-len(self._suffix)] try: @@ -67,12 +72,20 @@ class SynDynOptionDescription: pass else: return child.to_dynoption(subpath, - self._suffix) + self._suffix, + self._opt) raise AttributeError(_('unknown option "{0}" ' 'in dynamic optiondescription "{1}"' '').format(name, self.impl_get_display_name())) - def impl_getname(self) -> str: + def impl_getname(self, + for_path=False) -> str: + if for_path == 'toto': + if self.ori_dyn: + opt = self.ori_dyn + else: + opt = self._opt + return self._opt.impl_getname() + opt.convert_suffix_to_path(self._suffix) return self._opt.impl_getname() + self._suffix def impl_is_dynoptiondescription(self) -> bool: @@ -84,14 +97,14 @@ class SynDynOptionDescription: subpath = self.impl_getpath() for child in self._opt.get_children(config_bag): yield child.to_dynoption(subpath, - self._suffix) + self._suffix, + self._opt) def get_children_recursively(self, bytype: Optional[BaseOption], byname: Optional[str], config_bag: ConfigBag, self_opt: BaseOption=None) -> BaseOption: - # FIXME -> Iterator[Union[BaseOption, SynDynOptionDescription]]: return self._opt.get_children_recursively(bytype, byname, config_bag, @@ -101,7 +114,7 @@ class SynDynOptionDescription: subpath = self._subpath if subpath != '': subpath += '.' - return subpath + self.impl_getname() + return subpath + self.impl_getname(for_path=True) def impl_get_display_name(self) -> str: return self._opt.impl_get_display_name() + self._suffix @@ -110,13 +123,15 @@ class SynDynOptionDescription: class SynDynLeadership(SynDynOptionDescription): def get_leader(self) -> SynDynOption: return self._opt.get_leader().to_dynoption(self.impl_getpath(), - self._suffix) + self._suffix, + self.ori_dyn) def get_followers(self) -> Iterator[SynDynOption]: subpath = self.impl_getpath() for follower in self._opt.get_followers(): yield follower.to_dynoption(subpath, - self._suffix) + self._suffix, + self.ori_dyn) def reset_cache(self, path: str,