diff --git a/ChangeLog b/ChangeLog index 04b5161..481e8ba 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Thu Jun 19 23:20:29 2014 +0200 Emmanuel Garette + + * add DynOptionDescription: + DynOptionDescription are OptionDescription that generate auto + OptionDescription with a callback function + Sun Apr 27 10:32:40 2014 +0200 Emmanuel Garette * behavior change in ChoiceOption: diff --git a/test/test_choice_option.py b/test/test_choice_option.py new file mode 100644 index 0000000..470f247 --- /dev/null +++ b/test/test_choice_option.py @@ -0,0 +1,87 @@ +# coding: utf-8 +import autopath + +from tiramisu.setting import owners +from tiramisu.option import ChoiceOption, StrOption, OptionDescription +from tiramisu.config import Config + +from py.test import raises + + +def return_val(val): + return val + + +def return_list(): + return ['val1', 'val2'] + + +def return_calc_list(val): + return [val] + + +def test_choiceoption_function(): + ch = ChoiceOption('ch', '', values=return_list) + od = OptionDescription('od', '', [ch]) + cfg = Config(od) + cfg.read_write() + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.getowner(ch) == owners.default + cfg.ch = 'val1' + assert cfg.getowner(ch) == owner + del(cfg.ch) + assert cfg.getowner(ch) == owners.default + raises(ValueError, "cfg.ch='no'") + assert cfg.getowner(ch) == owners.default + + +def test_choiceoption_calc_function(): + ch = ChoiceOption('ch', "", values=return_calc_list, values_params={'': ('val1',)}) + od = OptionDescription('od', '', [ch]) + cfg = Config(od) + cfg.read_write() + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.getowner(ch) == owners.default + cfg.ch = 'val1' + assert cfg.getowner(ch) == owner + del(cfg.ch) + assert cfg.getowner(ch) == owners.default + raises(ValueError, "cfg.ch='no'") + assert cfg.getowner(ch) == owners.default + + +def test_choiceoption_calc_opt_function(): + st = StrOption('st', '', 'val1') + ch = ChoiceOption('ch', "", values=return_calc_list, values_params={'': ((st, False),)}) + od = OptionDescription('od', '', [st, ch]) + cfg = Config(od) + cfg.read_write() + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.getowner(ch) == owners.default + cfg.ch = 'val1' + assert cfg.getowner(ch) == owner + del(cfg.ch) + assert cfg.getowner(ch) == owners.default + raises(ValueError, "cfg.ch='no'") + assert cfg.getowner(ch) == owners.default + + +def test_choiceoption_calc_opt_multi_function(): + st = StrOption('st', '', ['val1'], multi=True) + ch = ChoiceOption('ch', "", default_multi='val2', values=return_val, values_params={'': ((st, False),)}, multi=True) + ch2 = ChoiceOption('ch2', "", default=['val2'], values=return_val, values_params={'': ((st, False),)}, multi=True) + od = OptionDescription('od', '', [st, ch, ch2]) + cfg = Config(od) + cfg.read_write() + assert cfg.ch == [] + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.getowner(ch) == owners.default + raises(ValueError, "cfg.ch.append()") + cfg.ch = ['val1'] + assert cfg.getowner(ch) == owner + del(cfg.ch) + assert cfg.getowner(ch) == owners.default + raises(ValueError, "cfg.ch='no'") + assert cfg.getowner(ch) == owners.default + # + raises(ValueError, "cfg.ch2") diff --git a/test/test_dyn_optiondescription.py b/test/test_dyn_optiondescription.py new file mode 100644 index 0000000..446d160 --- /dev/null +++ b/test/test_dyn_optiondescription.py @@ -0,0 +1,1317 @@ +# coding: utf-8 +import autopath + +from tiramisu.setting import groups, owners +from tiramisu.option import BoolOption, StrOption, ChoiceOption, IPOption, \ + NetworkOption, NetmaskOption, IntOption, FloatOption, \ + UnicodeOption, PortOption, BroadcastOption, DomainnameOption, \ + EmailOption, URLOption, UsernameOption, FilenameOption, SymLinkOption, \ + OptionDescription, DynOptionDescription, DynSymLinkOption, submulti +from tiramisu.config import Config +from tiramisu.error import PropertiesOptionError, ConfigError, ConflictError +from tiramisu.storage import delete_session +from test.test_state import _diff_opts, _diff_conf + +from py.test import raises +from pickle import dumps, loads + + +def return_true(value, param=None): + if value == 'val' and param in [None, 'yes']: + return + raise ValueError('no value') + + +def return_dynval(suffix, value='val'): + return value + + +def return_list2(suffix): + return [suffix, 'val2'] + + +def return_list(val=None): + if val: + return val + else: + return ['val1', 'val2'] + + +def test_build_dyndescription(): + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + cfg = Config(od) + assert str(cfg) == """[dodval1] +[dodval2]""" + assert str(cfg.dodval1) == "stval1 = None" + assert str(cfg.dodval2) == "stval2 = None" + + +def test_subpath_dyndescription(): + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + assert str(cfg) == "[od]" + assert str(cfg.od) == """[dodval1] +[dodval2]""" + assert str(cfg.od.dodval1) == "stval1 = None" + assert str(cfg.od.dodval2) == "stval2 = None" + + +def test_list_dyndescription(): + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + + +def test_unknown_dyndescription(): + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + raises(AttributeError, "cfg.od.dodval3") + raises(AttributeError, "cfg.od.dodval1.novalue") + + +def test_getdoc_dyndescription(): + st = StrOption('st', 'doc1') + dod = DynOptionDescription('dod', 'doc2', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + dodval1 = cfg.unwrap_from_path('od.dodval1') + dodval2 = cfg.unwrap_from_path('od.dodval2') + assert stval1.impl_getname() == 'stval1' + assert stval2.impl_getname() == 'stval2' + assert dodval1.impl_getname() == 'dodval1' + assert dodval2.impl_getname() == 'dodval2' + assert stval1.impl_getdoc() == 'doc1' + assert stval2.impl_getdoc() == 'doc1' + assert dodval1.impl_getdoc() == 'doc2' + assert dodval2.impl_getdoc() == 'doc2' + + +def test_getpaths_dyndescription(): + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + assert cfg.cfgimpl_get_description().impl_getpaths() == ['od.dodval1.stval1', 'od.dodval2.stval2'] + assert cfg.cfgimpl_get_description().impl_getpaths(include_groups=True) == ['od', 'od.dodval1', 'od.dodval1.stval1', 'od.dodval2', 'od.dodval2.stval2'] + + +def test_mod_dyndescription(): + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + assert cfg.getowner(stval1) == owners.default + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval1.stval1 = 'yes' + assert cfg.od.dodval1.stval1 == 'yes' + assert cfg.od.dodval2.stval2 is None + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval2.stval2 = 'no' + assert cfg.od.dodval1.stval1 == 'yes' + assert cfg.od.dodval2.stval2 == 'no' + assert cfg.getowner(st) == owners.default + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owner + + +def test_del_dyndescription(): + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + cfg.od.dodval1.stval1 = 'yes' + assert cfg.getowner(stval1) == owner + del(cfg.od.dodval1.stval1) + assert cfg.getowner(stval1) == owners.default + + +def test_multi_dyndescription(): + st = StrOption('st', '', multi=True) + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + assert cfg.od.dodval1.stval1 == [] + assert cfg.od.dodval2.stval2 == [] + assert cfg.getowner(stval1) == owners.default + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval1.stval1.append('yes') + assert cfg.od.dodval1.stval1 == ['yes'] + assert cfg.od.dodval2.stval2 == [] + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval2.stval2 = ['no'] + assert cfg.od.dodval1.stval1 == ['yes'] + assert cfg.od.dodval2.stval2 == ['no'] + assert cfg.getowner(st) == owners.default + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owner + cfg.od.dodval1.stval1.append('yes') + assert cfg.od.dodval1.stval1 == ['yes', 'yes'] + cfg.od.dodval1.stval1.pop(0) + assert cfg.od.dodval1.stval1 == ['yes'] + + +def test_prop_dyndescription(): + st = StrOption('st', '', properties=('test',)) + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + dodval1 = cfg.unwrap_from_path('od.dodval1') + dodval2 = cfg.unwrap_from_path('od.dodval2') + assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test']) + assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test']) + cfg.cfgimpl_get_settings()[stval2].append('test2') + assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test']) + assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test', 'test2']) + cfg.cfgimpl_get_settings()[stval1].remove('test') + assert str(cfg.cfgimpl_get_settings()[stval1]) == str([]) + # + assert str(cfg.cfgimpl_get_settings()[dodval1]) == str([]) + assert str(cfg.cfgimpl_get_settings()[dodval2]) == str([]) + cfg.cfgimpl_get_settings()[dodval1].append('test1') + assert str(cfg.cfgimpl_get_settings()[dodval1]) == str(['test1']) + assert str(cfg.cfgimpl_get_settings()[dodval2]) == str([]) + cfg.cfgimpl_get_settings()[dodval1].remove('test1') + assert str(cfg.cfgimpl_get_settings()[dodval1]) == str([]) + assert str(cfg.cfgimpl_get_settings()[dodval2]) == str([]) + + +def test_callback_dyndescription(): + st = StrOption('st', '', callback=return_dynval) + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + assert cfg.od.dodval1.stval1 == 'val' + assert cfg.od.dodval2.stval2 == 'val' + assert cfg.getowner(stval1) == owners.default + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval1.stval1 = 'val2' + assert cfg.od.dodval1.stval1 == 'val2' + assert cfg.od.dodval2.stval2 == 'val' + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owners.default + del(cfg.od.dodval1.stval1) + assert cfg.od.dodval1.stval1 == 'val' + assert cfg.od.dodval2.stval2 == 'val' + assert cfg.getowner(stval1) == owners.default + assert cfg.getowner(stval2) == owners.default + + +def test_callback_list_dyndescription(): + st = StrOption('st', '', callback=return_list2, multi=True) + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + assert cfg.od.dodval1.stval1 == ['val1', 'val2'] + assert cfg.od.dodval2.stval2 == ['val2', 'val2'] + assert cfg.getowner(stval1) == owners.default + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval1.stval1 = ['val3', 'val2'] + assert cfg.od.dodval1.stval1 == ['val3', 'val2'] + assert cfg.od.dodval2.stval2 == ['val2', 'val2'] + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owners.default + + +def test_mandatory_dyndescription(): + st = StrOption('st', '', properties=('mandatory',)) + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + cfg.read_only() + raises(PropertiesOptionError, "cfg.od.dodval1.stval1") + raises(PropertiesOptionError, "cfg.od.dodval2.stval2") + cfg.read_write() + cfg.od.dodval1.stval1 = 'val' + cfg.read_only() + assert cfg.od.dodval1.stval1 == 'val' + raises(PropertiesOptionError, "cfg.od.dodval2.stval2") + cfg.read_write() + del(cfg.od.dodval1.stval1) + cfg.read_only() + raises(PropertiesOptionError, "cfg.od.dodval1.stval1") + assert cfg.cfgimpl_get_values().mandatory_warnings() == ['od.dodval1.stval1', 'od.dodval2.stval2'] + + +def test_build_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list, callback_params={'': ((val1, False),)}) + od = OptionDescription('od', '', [dod, val1]) + cfg = Config(od) + assert str(cfg) == """[dodval1] +[dodval2] +val1 = ['val1', 'val2']""" + assert str(cfg.dodval1) == "stval1 = None" + assert str(cfg.dodval2) == "stval2 = None" + cfg.unwrap_from_path('dodval2') + + +def test_subpath_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list, callback_params={'': ((val1, False),)}) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + assert str(cfg) == "[od]" + assert str(cfg.od) == """[dodval1] +[dodval2] +val1 = ['val1', 'val2']""" + assert str(cfg.od.dodval1) == "stval1 = None" + assert str(cfg.od.dodval2) == "stval2 = None" + + +def test_list_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list, callback_params={'': ((val1, False),)}) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + raises(AttributeError, "cfg.od.dodval3") + + +def test_mod_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list, callback_params={'': ((val1, False),)}) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + assert cfg.getowner(stval1) == owners.default + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval1.stval1 = 'yes' + assert cfg.od.dodval1.stval1 == 'yes' + assert cfg.od.dodval2.stval2 is None + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval2.stval2 = 'no' + assert cfg.od.dodval1.stval1 == 'yes' + assert cfg.od.dodval2.stval2 == 'no' + assert cfg.getowner(st) == owners.default + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owner + + +def test_del_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list, callback_params={'': ((val1, False),)}) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + cfg.od.dodval1.stval1 = 'yes' + assert cfg.getowner(stval1) == owner + del(cfg.od.dodval1.stval1) + assert cfg.getowner(stval1) == owners.default + + +def test_multi_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '', multi=True) + dod = DynOptionDescription('dod', '', [st], callback=return_list, callback_params={'': ((val1, False),)}) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + assert cfg.od.dodval1.stval1 == [] + assert cfg.od.dodval2.stval2 == [] + assert cfg.getowner(stval1) == owners.default + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval1.stval1.append('yes') + assert cfg.od.dodval1.stval1 == ['yes'] + assert cfg.od.dodval2.stval2 == [] + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval2.stval2 = ['no'] + assert cfg.od.dodval1.stval1 == ['yes'] + assert cfg.od.dodval2.stval2 == ['no'] + assert cfg.getowner(st) == owners.default + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owner + cfg.od.dodval1.stval1.append('yes') + assert cfg.od.dodval1.stval1 == ['yes', 'yes'] + cfg.od.dodval1.stval1.pop(0) + assert cfg.od.dodval1.stval1 == ['yes'] + + +def test_prop_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '', properties=('test',)) + dod = DynOptionDescription('dod', '', [st], callback=return_list, callback_params={'': ((val1, False),)}) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test']) + assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test']) + cfg.cfgimpl_get_settings()[stval2].append('test2') + assert str(cfg.cfgimpl_get_settings()[stval1]) == str(['test']) + assert str(cfg.cfgimpl_get_settings()[stval2]) == str(['test', 'test2']) + cfg.cfgimpl_get_settings()[stval1].remove('test') + assert str(cfg.cfgimpl_get_settings()[stval1]) == str([]) + + +def test_callback_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '', callback=return_dynval) + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + assert cfg.od.dodval1.stval1 == 'val' + assert cfg.od.dodval2.stval2 == 'val' + assert cfg.getowner(stval1) == owners.default + assert cfg.getowner(stval2) == owners.default + cfg.od.dodval1.stval1 = 'val2' + assert cfg.od.dodval1.stval1 == 'val2' + assert cfg.od.dodval2.stval2 == 'val' + assert cfg.getowner(stval1) == owner + assert cfg.getowner(stval2) == owners.default + del(cfg.od.dodval1.stval1) + assert cfg.od.dodval1.stval1 == 'val' + assert cfg.od.dodval2.stval2 == 'val' + assert cfg.getowner(stval1) == owners.default + assert cfg.getowner(stval2) == owners.default + + +def test_mandatory_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '', properties=('mandatory',)) + dod = DynOptionDescription('dod', '', [st], callback=return_list, callback_params={'': ((val1, False),)}) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + cfg.read_only() + raises(PropertiesOptionError, "cfg.od.dodval1.stval1") + raises(PropertiesOptionError, "cfg.od.dodval2.stval2") + cfg.read_write() + cfg.od.dodval1.stval1 = 'val' + cfg.read_only() + assert cfg.od.dodval1.stval1 == 'val' + raises(PropertiesOptionError, "cfg.od.dodval2.stval2") + cfg.read_write() + del(cfg.od.dodval1.stval1) + cfg.read_only() + raises(PropertiesOptionError, "cfg.od.dodval1.stval1") + assert cfg.cfgimpl_get_values().mandatory_warnings() == ['od.dodval1.stval1', 'od.dodval2.stval2'] + + +def test_increase_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '', properties=('mandatory',)) + dod = DynOptionDescription('dod', '', [st], callback=return_list, callback_params={'': ((val1, False),)}) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + cfg.read_write() + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + raises(AttributeError, "cfg.od.dodval3") + cfg.od.val1 = ['val1', 'val2', 'val3'] + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + assert cfg.od.dodval3.stval3 is None + + +def test_decrease_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '', properties=('mandatory',)) + dod = DynOptionDescription('dod', '', [st], callback=return_list, callback_params={'': ((val1, False),)}) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + stval1 = cfg.unwrap_from_path('od.dodval1.stval1') + stval2 = cfg.unwrap_from_path('od.dodval2.stval2') + cfg.read_write() + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + cfg.od.dodval2.stval2 = 'yes' + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 == 'yes' + assert cfg.getowner(stval1) == owners.default + assert cfg.getowner(stval2) == owner + raises(AttributeError, "cfg.od.dodval3") + cfg.od.val1 = ['val1'] + assert cfg.od.dodval1.stval1 is None + raises(AttributeError, "cfg.od.dodval2") + raises(AttributeError, "cfg.od.dodval3") + assert cfg.getowner(stval1) == owners.default + #FIXME +# raises(AttributeError, "cfg.getowner(stval2)") + raises(AttributeError, "cfg.unwrap_from_path('od.dodval2.stval2')") + + +def test_requires_dyndescription(): + boolean = BoolOption('boolean', '', True) + st = StrOption('st', '', requires=[{'option': boolean, 'expected': False, + 'action': 'disabled'}]) + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od, boolean]) + cfg = Config(od2) + cfg.read_write() + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + # + cfg.boolean = False + props = [] + try: + cfg.od.dodval1.stval1 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + props = [] + try: + cfg.od.dodval2.stval2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + # + cfg.boolean = True + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + #transitive + cfg.cfgimpl_get_settings()[boolean].append('disabled') + props = [] + try: + cfg.od.dodval1.stval1 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + props = [] + try: + cfg.od.dodval2.stval2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + +def test_requires_dyndescription2(): + boolean = BoolOption('boolean', '', True) + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list, + requires=[{'option': boolean, 'expected': False, + 'action': 'disabled'}]) + od = OptionDescription('od', '', [dod]) + od2 = OptionDescription('od', '', [od, boolean]) + cfg = Config(od2) + cfg.read_write() + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + # + cfg.boolean = False + props = [] + try: + cfg.od.dodval1.stval1 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + props = [] + try: + cfg.od.dodval2.stval2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + # + cfg.boolean = True + assert cfg.od.dodval1.stval1 is None + assert cfg.od.dodval2.stval2 is None + #transitive + cfg.cfgimpl_get_settings()[boolean].append('disabled') + props = [] + try: + cfg.od.dodval1.stval1 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + props = [] + try: + cfg.od.dodval2.stval2 + except PropertiesOptionError as err: + props = err.proptype + assert props == ['disabled'] + + +def test_validator_dyndescription(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '', validator=return_true, validator_params={'': ('yes',)}, default='val') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + assert cfg.od.dodval1.stval1 == 'val' + raises(ValueError, "cfg.od.dodval1.stval1 = 'no'") + cfg.od.dodval1.stval1 = 'val' + + +def test_makedict_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + cfg.od.dodval1.stval1 = 'yes' + assert cfg.make_dict() == {'od.val1': ['val1', 'val2'], 'od.dodval1.stval1': 'yes', 'od.dodval2.stval2': None} + assert cfg.make_dict(flatten=True) == {'val1': ['val1', 'val2'], 'stval1': 'yes', 'stval2': None} + assert cfg.make_dict(withoption='stval1') == {'od.dodval1.stval1': 'yes'} + assert cfg.od.make_dict(withoption='stval1') == {'dodval1.stval1': 'yes'} + assert cfg.od.dodval1.make_dict(withoption='stval1') == {'stval1': 'yes'} + + +def test_find_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + cfg.od.dodval1.stval1 = 'yes' + assert cfg.find_first(byname='stval1', type_='value') == "yes" + assert isinstance(cfg.find_first(byname='stval1', type_='option'), DynSymLinkOption) + assert cfg.find(bytype=StrOption, type_='path') == ['od.dodval1.stval1', 'od.dodval2.stval2', 'od.val1'] + opts = cfg.find(byvalue='yes') + assert len(opts) == 1 + assert isinstance(opts[0], DynSymLinkOption) + assert opts[0].impl_getname() == 'stval1' + + +def test_iter_all_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + for it in cfg.iter_all(): + assert it[0] == 'od' + assert str(it[1]) == str(cfg.od) + # + list_od = [] + for it in cfg.od.iter_all(): + list_od.append(it[0]) + assert list_od == ['dodval1', 'dodval2', 'val1'] + # + list_od = [] + for it in cfg.od.dodval1: + list_od.append(it[0]) + assert list_od == ['stval1'] + + +def test_information_dyndescription_context(): + val1 = StrOption('val1', '', ['val1', 'val2'], multi=True) + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + od = OptionDescription('od', '', [dod, val1]) + od2 = OptionDescription('od', '', [od]) + dod.impl_set_information('testod', 'val1') + st.impl_set_information('testst', 'val2') + cfg = Config(od2) + cfg.impl_set_information('testcfgod', 'val3') + assert dod.impl_get_information('testod') == 'val1' + assert st.impl_get_information('testst') == 'val2' + assert cfg.impl_get_information('testcfgod') == 'val3' + + +def test_consistency_dyndescription(): + st = StrOption('st', '') + st2 = StrOption('st2', '') + dod = DynOptionDescription('dod', '', [st, st2], callback=return_list) + od = OptionDescription('od', '', [dod]) + st.impl_add_consistency('not_equal', st2) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + cfg.od.dodval1.stval1 = 'yes' + raises(ValueError, "cfg.od.dodval1.st2val1 = 'yes'") + cfg.od.dodval2.stval2 = 'yes' + raises(ValueError, "cfg.od.dodval2.st2val2 = 'yes'") + raises(ValueError, "cfg.od.dodval1.st2val1 = 'yes'") + del(cfg.od.dodval2.stval2) + raises(ValueError, "cfg.od.dodval1.st2val1 = 'yes'") + cfg.od.dodval2.st2val2 = 'yes' + raises(ValueError, "cfg.od.dodval2.stval2 = 'yes'") + + +def test_consistency_external_dyndescription(): + st = StrOption('st', '') + st1 = StrOption('st1', '') + st2 = StrOption('st2', '') + dod = DynOptionDescription('dod', '', [st1, st2], callback=return_list) + od = OptionDescription('od', '', [dod, st]) + raises(ConfigError, "st.impl_add_consistency('not_equal', st2)") + + +def test_consistency_notsame_dyndescription(): + st1 = StrOption('st1', '') + st2 = StrOption('st2', '') + dod = DynOptionDescription('dod', '', [st1, st2], callback=return_list) + tst1 = StrOption('tst1', '') + tst2 = StrOption('tst2', '') + tdod = DynOptionDescription('tdod', '', [tst1, tst2], callback=return_list) + od = OptionDescription('od', '', [dod, tdod]) + raises(ConfigError, "st1.impl_add_consistency('not_equal', tst1)") + + +def test_all_dyndescription(): + st = StrOption('st', '') + ip = IPOption('ip', '') + network = NetworkOption('network', '') + netmask = NetmaskOption('netmask', '') + ch = ChoiceOption('ch', '', ('val1', 'val2', 'val3')) + ch1 = ChoiceOption('ch1', '', return_list) + boo = BoolOption('boo', '') + intr = IntOption('intr', '') + floa = FloatOption('floa', '') + uni = UnicodeOption('uni', '') + port = PortOption('port', '') + broad = BroadcastOption('broad', '') + domain = DomainnameOption('domain', '') + email = EmailOption('email', '') + url = URLOption('url', '') + username = UsernameOption('username', '') + filename = FilenameOption('filename', '') + dod = DynOptionDescription('dod', '', [st, ip, network, netmask, ch, ch1, + boo, intr, floa, uni, port, broad, + domain, email, url, username, + filename], callback=return_list) + od = OptionDescription('od', '', [dod]) + cfg = Config(od) + assert cfg.dodval1.stval1 is None + assert cfg.dodval1.ipval1 is None + assert cfg.dodval1.networkval1 is None + assert cfg.dodval1.netmaskval1 is None + assert cfg.dodval1.chval1 is None + assert cfg.dodval1.ch1val1 is None + assert cfg.dodval1.booval1 is None + assert cfg.dodval1.intrval1 is None + assert cfg.dodval1.floaval1 is None + assert cfg.dodval1.unival1 is None + assert cfg.dodval1.portval1 is None + assert cfg.dodval1.broadval1 is None + assert cfg.dodval1.domainval1 is None + assert cfg.dodval1.emailval1 is None + assert cfg.dodval1.urlval1 is None + assert cfg.dodval1.usernameval1 is None + assert cfg.dodval1.filenameval1 is None + # + cfg.dodval1.stval1 = "no" + cfg.dodval1.ipval1 = "1.1.1.1" + cfg.dodval1.networkval1 = "1.1.1.0" + cfg.dodval1.netmaskval1 = "255.255.255.0" + cfg.dodval1.chval1 = "val1" + cfg.dodval1.ch1val1 = "val2" + cfg.dodval1.booval1 = True + cfg.dodval1.intrval1 = 1 + cfg.dodval1.floaval1 = 0.1 + cfg.dodval1.unival1 = u"no" + cfg.dodval1.portval1 = 80 + cfg.dodval1.broadval1 = "1.1.1.255" + cfg.dodval1.domainval1 = "test.com" + cfg.dodval1.emailval1 = "test@test.com" + cfg.dodval1.urlval1 = "http://test.com" + cfg.dodval1.usernameval1 = "user1" + cfg.dodval1.filenameval1 = "/tmp" + assert cfg.dodval1.stval1 == "no" + assert cfg.dodval1.ipval1 == "1.1.1.1" + assert cfg.dodval1.networkval1 == "1.1.1.0" + assert cfg.dodval1.netmaskval1 == "255.255.255.0" + assert cfg.dodval1.chval1 == "val1" + assert cfg.dodval1.ch1val1 == "val2" + assert cfg.dodval1.booval1 is True + assert cfg.dodval1.intrval1 == 1 + assert cfg.dodval1.floaval1 == 0.1 + assert cfg.dodval1.unival1 == u"no" + assert cfg.dodval1.portval1 == 80 + assert cfg.dodval1.broadval1 == "1.1.1.255" + assert cfg.dodval1.domainval1 == "test.com" + assert cfg.dodval1.emailval1 == "test@test.com" + assert cfg.dodval1.urlval1 == "http://test.com" + assert cfg.dodval1.usernameval1 == "user1" + assert cfg.dodval1.filenameval1 == "/tmp" + # + assert cfg.dodval2.stval2 is None + assert cfg.dodval2.ipval2 is None + assert cfg.dodval2.networkval2 is None + assert cfg.dodval2.netmaskval2 is None + assert cfg.dodval2.chval2 is None + assert cfg.dodval2.ch1val2 is None + assert cfg.dodval2.booval2 is None + assert cfg.dodval2.intrval2 is None + assert cfg.dodval2.floaval2 is None + assert cfg.dodval2.unival2 is None + assert cfg.dodval2.portval2 is None + assert cfg.dodval2.broadval2 is None + assert cfg.dodval2.domainval2 is None + assert cfg.dodval2.emailval2 is None + assert cfg.dodval2.urlval2 is None + assert cfg.dodval2.usernameval2 is None + assert cfg.dodval2.filenameval2 is None + + +def test_consistency_ip_netmask_dyndescription(): + a = IPOption('a', '') + b = NetmaskOption('b', '') + dod = DynOptionDescription('dod', '', [a, b], callback=return_list) + b.impl_add_consistency('ip_netmask', a) + od = OptionDescription('od', '', [dod]) + c = Config(od) + c.dodval1.aval1 = '192.168.1.1' + c.dodval1.bval1 = '255.255.255.0' + c.dodval2.aval2 = '192.168.1.2' + c.dodval2.bval2 = '255.255.255.255' + c.dodval2.bval2 = '255.255.255.0' + + +def test_consistency_ip_in_network_dyndescription(): + a = NetworkOption('a', '') + b = NetmaskOption('b', '') + c = IPOption('c', '') + dod = DynOptionDescription('dod', '', [a, b, c], callback=return_list) + c.impl_add_consistency('in_network', a, b) + od = OptionDescription('od', '', [dod]) + cfg = Config(od) + cfg.dodval1.aval1 = '192.168.1.0' + cfg.dodval1.bval1 = '255.255.255.0' + cfg.dodval1.cval1 = '192.168.1.1' + + +def test_masterslaves_dyndescription(): + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True) + stm = OptionDescription('st1', '', [st1, st2]) + stm.impl_set_group_type(groups.master) + st = DynOptionDescription('st', '', [stm], callback=return_list) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + st1val1 = cfg.unwrap_from_path('od.stval1.st1val1.st1val1') + st2val1 = cfg.unwrap_from_path('od.stval1.st1val1.st2val1') + st1val2 = cfg.unwrap_from_path('od.stval2.st1val2.st1val2') + st2val2 = cfg.unwrap_from_path('od.stval2.st1val2.st2val2') + assert cfg.make_dict() == {'od.stval1.st1val1.st2val1': [], 'od.stval2.st1val2.st2val2': [], 'od.stval2.st1val2.st1val2': [], 'od.stval1.st1val1.st1val1': []} + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owners.default + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st1val1.append('yes') + assert cfg.make_dict() == {'od.stval1.st1val1.st2val1': [None], 'od.stval2.st1val2.st2val2': [], 'od.stval2.st1val2.st1val2': [], 'od.stval1.st1val1.st1val1': ['yes']} + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == [None] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st1val1 = ['yes'] + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == [None] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st2val1 = ['no'] + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == ['no'] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owner + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st1val1.pop(0) + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owner + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st1val1 = ['yes'] + cfg.od.stval1.st1val1.st2val1 = ['yes'] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st2val1) == owner + del(cfg.od.stval1.st1val1.st2val1) + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st1val1 = ['yes'] + cfg.od.stval1.st1val1.st2val1 = ['yes'] + del(cfg.od.stval1.st1val1.st1val1) + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owners.default + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + + +def test_masterslaves_default_multi_dyndescription(): + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True, default_multi='no') + stm = OptionDescription('st1', '', [st1, st2]) + stm.impl_set_group_type(groups.master) + st = DynOptionDescription('st', '', [stm], callback=return_list) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + st1val1 = cfg.unwrap_from_path('od.stval1.st1val1.st1val1') + st2val1 = cfg.unwrap_from_path('od.stval1.st1val1.st2val1') + st1val2 = cfg.unwrap_from_path('od.stval2.st1val2.st1val2') + st2val2 = cfg.unwrap_from_path('od.stval2.st1val2.st2val2') + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owners.default + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + cfg.od.stval1.st1val1.st1val1.append('yes') + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == ['no'] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + + +def test_masterslaves_submulti_dyndescription(): + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=submulti) + stm = OptionDescription('st1', '', [st1, st2]) + stm.impl_set_group_type(groups.master) + st = DynOptionDescription('st', '', [stm], callback=return_list) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + st1val1 = cfg.unwrap_from_path('od.stval1.st1val1.st1val1') + st2val1 = cfg.unwrap_from_path('od.stval1.st1val1.st2val1') + st1val2 = cfg.unwrap_from_path('od.stval2.st1val2.st1val2') + st2val2 = cfg.unwrap_from_path('od.stval2.st1val2.st2val2') + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owners.default + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + cfg.od.stval1.st1val1.st1val1.append('yes') + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == [[]] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st2val1[0].append('no') + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == [['no']] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owner + assert cfg.getowner(st2val2) == owners.default + + +def test_masterslaves_consistency_ip_dyndescription(): + a = NetworkOption('net', '', multi=True) + b = NetmaskOption('mask', '', multi=True) + c = BroadcastOption('broad', '', multi=True) + b.impl_add_consistency('network_netmask', a) + c.impl_add_consistency('broadcast', a, b) + dod = DynOptionDescription('net', '', [a, b, c], callback=return_list) + dod.impl_set_group_type(groups.master) + od = OptionDescription('od', '', [dod]) + cfg = Config(od) + cfg.netval1.netval1 = ['192.168.1.0'] + cfg.netval1.maskval1 = ['255.255.255.0'] + cfg.netval1.broadval1 = ['192.168.1.255'] + + cfg.netval1.netval1 = ['192.168.1.0', '192.168.2.128'] + cfg.netval1.maskval1 = ['255.255.255.0', '255.255.255.128'] + cfg.netval1.broadval1 = ['192.168.1.255', '192.168.2.255'] + cfg.netval1.broadval1[1] = '192.168.2.255' + # + assert cfg.netval1.netval1 == ['192.168.1.0', '192.168.2.128'] + assert cfg.netval1.maskval1 == ['255.255.255.0', '255.255.255.128'] + assert cfg.netval1.broadval1 == ['192.168.1.255', '192.168.2.255'] + assert cfg.netval2.netval2 == [] + assert cfg.netval2.maskval2 == [] + assert cfg.netval2.broadval2 == [] + + +def test_masterslaves_callback_dyndescription(): + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True, callback=return_dynval, callback_params={'value': ((st1, False),)}) + stm = OptionDescription('st1', '', [st1, st2]) + stm.impl_set_group_type(groups.master) + st = DynOptionDescription('st', '', [stm], callback=return_list) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + st1val1 = cfg.unwrap_from_path('od.stval1.st1val1.st1val1') + st2val1 = cfg.unwrap_from_path('od.stval1.st1val1.st2val1') + st1val2 = cfg.unwrap_from_path('od.stval2.st1val2.st1val2') + st2val2 = cfg.unwrap_from_path('od.stval2.st1val2.st2val2') + assert cfg.make_dict() == {'od.stval1.st1val1.st2val1': [], 'od.stval2.st1val2.st2val2': [], 'od.stval2.st1val2.st1val2': [], 'od.stval1.st1val1.st1val1': []} + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owners.default + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st1val1.append('yes') + assert cfg.make_dict() == {'od.stval1.st1val1.st2val1': ['yes'], 'od.stval2.st1val2.st2val2': [], 'od.stval2.st1val2.st1val2': [], 'od.stval1.st1val1.st1val1': ['yes']} + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == ['yes'] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st2val1 = ['no'] + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == ['no'] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owner + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st1val1.pop(0) + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owner + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st1val1 = ['yes'] + cfg.od.stval1.st1val1.st2val1 = ['yes'] + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st2val1) == owner + del(cfg.od.stval1.st1val1.st2val1) + assert cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st1val1 = ['yes'] + cfg.od.stval1.st1val1.st2val1 = ['yes'] + del(cfg.od.stval1.st1val1.st1val1) + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.getowner(st1val1) == owners.default + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + # + cfg.od.stval1.st1val1.st2val1 = [] + cfg.od.stval1.st1val1.st1val1 = ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == ['yes'] + + +def test_masterslaves_callback_value_dyndescription(): + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True, callback=return_dynval, callback_params={'value': ('val',)}) + stm = OptionDescription('st1', '', [st1, st2]) + stm.impl_set_group_type(groups.master) + st = DynOptionDescription('st', '', [stm], callback=return_list) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + cfg.od.stval1.st1val1.st1val1.append('yes') + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1[0] == 'val' + assert cfg.od.stval1.st1val1.st2val1 == ['val'] + + +def test_masterslaves_callback_nomulti_dyndescription(): + v1 = StrOption('v1', '', "val") + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True, callback=return_dynval, callback_params={'': ((v1, False),)}) + stm = OptionDescription('st1', '', [st1, st2]) + stm.impl_set_group_type(groups.master) + st = DynOptionDescription('st', '', [stm], callback=return_list) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od, v1]) + cfg = Config(od2) + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + cfg.od.stval1.st1val1.st1val1.append('yes') + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == ['val'] + + +def test_masterslaves_callback_multi_dyndescription(): + v1 = StrOption('v1', '', multi=True) + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True, callback=return_dynval, callback_params={'': ((v1, False),)}) + stm = OptionDescription('st1', '', [st1, st2]) + stm.impl_set_group_type(groups.master) + st = DynOptionDescription('st', '', [stm], callback=return_list) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od, v1]) + cfg = Config(od2) + assert cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + cfg.od.stval1.st1val1.st1val1.append('yes') + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == [None] + cfg.od.stval1.st1val1.st2val1 = ['no'] + cfg.v1 = ['no', 'no', 'no'] + cfg.od.stval1.st1val1.st1val1.append('yes') + assert cfg.od.stval1.st1val1.st1val1 == ['yes', 'yes'] + assert cfg.od.stval1.st1val1.st2val1 == ['no', 'no'] + cfg.od.stval1.st1val1.st1val1.pop(1) + assert cfg.od.stval1.st1val1.st1val1 == ['yes'] + assert cfg.od.stval1.st1val1.st2val1 == ['no'] + + +def test_masterslaves_callback_samegroup_dyndescription(): + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True) + st3 = StrOption('st3', "", multi=True, callback=return_dynval, callback_params={'': ((st2, False),)}) + stm = OptionDescription('st1', '', [st1, st2, st3]) + stm.impl_set_group_type(groups.master) + st = DynOptionDescription('st', '', [stm], callback=return_list) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + st1val1 = cfg.unwrap_from_path('od.stval1.st1val1.st1val1') + st2val1 = cfg.unwrap_from_path('od.stval1.st1val1.st2val1') + st3val1 = cfg.unwrap_from_path('od.stval1.st1val1.st3val1') + st1val2 = cfg.unwrap_from_path('od.stval2.st1val2.st1val2') + st2val2 = cfg.unwrap_from_path('od.stval2.st1val2.st2val2') + st3val2 = cfg.unwrap_from_path('od.stval2.st1val2.st3val2') + assert cfg.make_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 cfg.od.stval1.st1val1.st1val1 == [] + assert cfg.od.stval1.st1val1.st2val1 == [] + assert cfg.od.stval1.st1val1.st3val1 == [] + assert cfg.od.stval2.st1val2.st1val2 == [] + assert cfg.od.stval2.st1val2.st2val2 == [] + assert cfg.od.stval2.st1val2.st3val2 == [] + assert cfg.getowner(st1val1) == owners.default + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + assert cfg.getowner(st3val1) == owners.default + assert cfg.getowner(st3val2) == owners.default + ## + cfg.od.stval1.st1val1.st1val1.append('yes') + assert cfg.make_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 cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owners.default + assert cfg.getowner(st2val2) == owners.default + assert cfg.getowner(st3val1) == owners.default + assert cfg.getowner(st3val2) == owners.default + # + cfg.od.stval1.st1val1.st2val1[0] = 'yes' + assert cfg.make_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 cfg.getowner(st1val1) == owner + assert cfg.getowner(st1val2) == owners.default + assert cfg.getowner(st2val1) == owner + assert cfg.getowner(st2val2) == owners.default + assert cfg.getowner(st3val1) == owners.default + assert cfg.getowner(st3val2) == owners.default + + +def test_state_config(): + a = IPOption('a', '') + b = NetmaskOption('b', '') + dod1 = DynOptionDescription('dod1', '', [a, b], callback=return_list) + b.impl_add_consistency('ip_netmask', a) + od1 = OptionDescription('od1', '', [dod1]) + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True) + st3 = StrOption('st3', "", multi=True, callback=return_dynval, callback_params={'': ((st2, False),)}) + stm = OptionDescription('st1', '', [st1, st2, st3]) + stm.impl_set_group_type(groups.master) + st = DynOptionDescription('st', '', [stm], callback=return_list) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od, od1]) + try: + cfg = Config(od2, persistent=True, session_id='29090938') + except ValueError: + cfg = Config(od2, session_id='29090938') + cfg._impl_test = True + a = dumps(cfg) + q = loads(a) + _diff_opts(cfg.cfgimpl_get_description(), q.cfgimpl_get_description()) + _diff_conf(cfg, q) + + try: + delete_session('config', '29090938') + except ConfigError: + pass + + +def test_invalid_conflict_dyndescription(): + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_list) + dodinvalid = StrOption('dodinvalid', '') + raises(ConflictError, "OptionDescription('od', '', [dod, dodinvalid])") + + +def test_invalid_subod_dyndescription(): + st2 = StrOption('st2', '') + od1 = OptionDescription('od1', '', [st2]) + raises(ConfigError, "DynOptionDescription('dod', '', [od1], callback=return_list)") + + +def test_invalid_subdynod_dyndescription(): + st2 = StrOption('st2', '') + od1 = DynOptionDescription('od1', '', [st2], callback=return_list) + raises(ConfigError, "DynOptionDescription('dod', '', [od1], callback=return_list)") + + +def test_invalid_symlink_dyndescription(): + st = StrOption('st', '') + st2 = SymLinkOption('st2', st) + raises(ConfigError, "DynOptionDescription('dod', '', [st, st2], callback=return_list)") + + +def test_nocallback_dyndescription(): + st = StrOption('st', '') + st2 = StrOption('st2', st) + raises(ConfigError, "DynOptionDescription('dod', '', [st, st2])") + + +def test_invalid_samevalue_dyndescription(): + def return_same_list(): + return ['val1', 'val1'] + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_same_list) + od = OptionDescription('od', '', [dod]) + cfg = Config(od) + raises(ConfigError, "print cfg") + + +def test_invalid_name_dyndescription(): + def return_same_list(): + return ['---', ' '] + st = StrOption('st', '') + dod = DynOptionDescription('dod', '', [st], callback=return_same_list) + od = OptionDescription('od', '', [dod]) + cfg = Config(od) + raises(ValueError, "print cfg") diff --git a/test/test_option.py b/test/test_option.py index ec769c5..64985ea 100644 --- a/test/test_option.py +++ b/test/test_option.py @@ -46,9 +46,12 @@ def a_func(): def test_option_valid_name(): IntOption('test', '') raises(ValueError, 'IntOption(1, "")') + raises(ValueError, 'IntOption("1test", "")') + IntOption("test1", "") raises(ValueError, 'IntOption("impl_test", "")') raises(ValueError, 'IntOption("_test", "")') raises(ValueError, 'IntOption("unwrap_from_path", "")') + raises(ValueError, 'IntOption(" ", "")') def test_option_with_callback(): diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index 881d661..979c759 100644 --- a/test/test_option_calculation.py +++ b/test/test_option_calculation.py @@ -51,30 +51,6 @@ def is_config(config, **kwargs): return 'no' -def make_description(): - gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') - gcdummy = BoolOption('dummy', 'dummy', default=False) - objspaceoption = ChoiceOption('objspace', 'Object space', - ('std', 'thunk'), 'std') - booloption = BoolOption('bool', 'Test boolean option', default=True) - intoption = IntOption('int', 'Test int option', default=0) - intoption2 = IntOption('int', 'Test int option', default=0) - floatoption = FloatOption('float', 'Test float option', default=2.3) - stroption = StrOption('str', 'Test string option', default="abc") - boolop = BoolOption('boolop', 'Test boolean option op', default=True) - wantref_option = BoolOption('wantref', 'Test requires', default=False, - requires=({'option': boolop, 'expected': True, 'action': 'hidden'},)) - wantframework_option = BoolOption('wantframework', 'Test requires', - default=False, - requires=({'option': boolop, 'expected': True, 'action': 'hidden'},)) - gcgroup = OptionDescription('gc', '', [gcoption, gcdummy, floatoption, intoption2]) - descr = OptionDescription('constraints', '', [gcgroup, booloption, objspaceoption, - wantref_option, stroption, - wantframework_option, - intoption, boolop]) - return descr - - def make_description_duplicates(): gcoption = ChoiceOption('name', 'GC name', ('ref', 'framework'), 'ref') ## dummy 1 @@ -929,3 +905,26 @@ def test_callback_multi_list_params_key(): cfg = Config(maconfig) cfg.read_write() assert cfg.val2.val2 == ['val', 'val'] + + +def test_masterslaves_callback_description(): + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True, callback=return_value, callback_params={'': ((st1, False),)}) + stm = OptionDescription('st1', '', [st1, st2]) + stm.impl_set_group_type(groups.master) + st = OptionDescription('st', '', [stm]) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od]) + cfg = Config(od2) + owner = cfg.cfgimpl_get_settings().getowner() + st1 = cfg.unwrap_from_path('od.st.st1.st1') + st2 = cfg.unwrap_from_path('od.st.st1.st2') + assert cfg.od.st.st1.st1 == [] + assert cfg.od.st.st1.st2 == [] + assert cfg.getowner(st1) == owners.default + assert cfg.getowner(st2) == owners.default + ## + cfg.od.st.st1.st1.append('yes') + assert cfg.od.st.st1.st1 == ['yes'] + assert cfg.od.st.st1.st2 == ['yes'] + assert cfg.getowner(st1) == owner diff --git a/test/test_state.py b/test/test_state.py index 9b40219..e66eb34 100644 --- a/test/test_state.py +++ b/test/test_state.py @@ -1,9 +1,10 @@ import autopath from tiramisu.option import BoolOption, UnicodeOption, SymLinkOption, \ - IntOption, OptionDescription + IntOption, IPOption, NetmaskOption, StrOption, OptionDescription, \ + DynOptionDescription from tiramisu.config import Config, GroupConfig, MetaConfig -from tiramisu.setting import owners +from tiramisu.setting import groups, owners from tiramisu.storage import delete_session from tiramisu.error import ConfigError from pickle import dumps, loads @@ -93,10 +94,35 @@ def _diff_opt(opt1, opt2): assert val1[key][idx] == val2[key][idx] else: assert val1 == val2 + elif attr == '_master_slaves': + assert val1.master.impl_getname() == val2.master.impl_getname() + sval1 = [opt.impl_getname() for opt in val1.slaves] + sval2 = [opt.impl_getname() for opt in val2.slaves] + assert sval1 == sval2 + elif attr == '_subdyn': + try: + assert val1.impl_getname() == val2.impl_getname() + except AttributeError: + assert val1 == val2 else: assert val1 == val2 +def _diff_opts(opt1, opt2): + _diff_opt(opt1, opt2) + if isinstance(opt1, OptionDescription) or isinstance(opt1, DynOptionDescription): + children1 = set([opt.impl_getname() for opt in opt1._impl_getchildren(dyn=False)]) + children2 = set([opt.impl_getname() for opt in opt2._impl_getchildren(dyn=False)]) + diff1 = children1 - children2 + diff2 = children2 - children1 + if diff1 != set(): + raise Exception('more attribute in opt1 {0}'.format(list(diff1))) + if diff2 != set(): + raise Exception('more attribute in opt2 {0}'.format(list(diff2))) + for child in children1: + _diff_opts(opt1._getattr(child, dyn=False), opt2._getattr(child, dyn=False)) + + def _diff_conf(cfg1, cfg2): attr1 = set(_get_slots(cfg1)) attr2 = set(_get_slots(cfg2)) @@ -148,11 +174,7 @@ def test_diff_opt(): a = dumps(o1) q = loads(a) - _diff_opt(o1, q) - _diff_opt(o1.o, q.o) - _diff_opt(o1.o.b, q.o.b) - _diff_opt(o1.o.u, q.o.u) - _diff_opt(o1.o.s, q.o.s) + _diff_opts(o1, q) def test_only_optiondescription(): @@ -172,11 +194,7 @@ def test_diff_opt_cache(): a = dumps(o1) q = loads(a) - _diff_opt(o1, q) - _diff_opt(o1.o, q.o) - _diff_opt(o1.o.b, q.o.b) - _diff_opt(o1.o.u, q.o.u) - _diff_opt(o1.o.s, q.o.s) + _diff_opts(o1, q) def test_diff_opt_callback(): @@ -190,11 +208,7 @@ def test_diff_opt_callback(): a = dumps(o1) q = loads(a) - _diff_opt(o1, q) - _diff_opt(o1.o, q.o) - _diff_opt(o1.o.b, q.o.b) - _diff_opt(o1.o.b2, q.o.b2) - _diff_opt(o1.o.b3, q.o.b3) + _diff_opts(o1, q) def test_no_state_attr(): @@ -224,6 +238,7 @@ def test_state_config(): cfg._impl_test = True a = dumps(cfg) q = loads(a) + _diff_opts(cfg.cfgimpl_get_description(), q.cfgimpl_get_description()) _diff_conf(cfg, q) try: delete_session('config', '29090931') @@ -231,6 +246,35 @@ def test_state_config(): pass +def test_state_config2(): + a = IPOption('a', '') + b = NetmaskOption('b', '') + dod1 = OptionDescription('dod1', '', [a, b]) + b.impl_add_consistency('ip_netmask', a) + od1 = OptionDescription('od1', '', [dod1]) + st1 = StrOption('st1', "", multi=True) + st2 = StrOption('st2', "", multi=True) + st3 = StrOption('st3', "", multi=True, callback=return_value, callback_params={'': ((st2, False),)}) + stm = OptionDescription('st1', '', [st1, st2, st3]) + stm.impl_set_group_type(groups.master) + st = OptionDescription('st', '', [stm]) + od = OptionDescription('od', '', [st]) + od2 = OptionDescription('od', '', [od, od1]) + try: + cfg = Config(od2, persistent=True, session_id='29090939') + except ValueError: + cfg = Config(od2, session_id='29090939') + cfg._impl_test = True + a = dumps(cfg) + q = loads(a) + _diff_opts(cfg.cfgimpl_get_description(), q.cfgimpl_get_description()) + _diff_conf(cfg, q) + try: + delete_session('config', '29090939') + except ConfigError: + pass + + def test_state_properties(): val1 = BoolOption('val1', "") maconfig = OptionDescription('rootconfig', '', [val1]) diff --git a/tiramisu/autolib.py b/tiramisu/autolib.py index 48f61e0..41ad226 100644 --- a/tiramisu/autolib.py +++ b/tiramisu/autolib.py @@ -136,22 +136,32 @@ def carry_out_calculation(option, config, callback, callback_params, """ tcparams = {} # if callback_params has a callback, launch several time calculate() - one_is_multi = False + master_slave = False # multi's option should have same value for all option len_multi = None - + try: + if option._is_subdyn(): + tcparams[''] = [(option.impl_getsuffix(), False)] + except AttributeError: + pass for key, callbacks in callback_params.items(): for callbk in callbacks: if isinstance(callbk, tuple): - if config is None: - raise ContextError() - if len(callbk) == 1: + if config is undefined: + raise ContextError() # pragma: optional cover + if len(callbk) == 1: # pragma: optional cover tcparams.setdefault(key, []).append((config, False)) else: # callbk is something link (opt, True|False) opt, force_permissive = callbk - path = config.cfgimpl_get_description( - ).impl_get_path_by_opt(opt) + if opt._is_subdyn(): + root = '.'.join(option.impl_getpath(config).split('.')[:-1]) + name = opt.impl_getname() + option.impl_getsuffix() + path = root + '.' + name + opt = opt._impl_to_dyn(name, path) + else: + path = config.cfgimpl_get_description( + ).impl_get_path_by_opt(opt) # get value try: value = config.getattr(path, force_permissive=True, @@ -159,7 +169,7 @@ def carry_out_calculation(option, config, callback, callback_params, # convert to list, not modifie this multi if value.__class__.__name__ == 'Multi': value = list(value) - except PropertiesOptionError as err: + except PropertiesOptionError as err: # pragma: optional cover if force_permissive: continue raise ConfigError(_('unable to carry out a calculation' @@ -171,7 +181,7 @@ def carry_out_calculation(option, config, callback, callback_params, if opt.impl_is_master_slaves() and \ opt.impl_get_master_slaves().in_same_group(option): len_multi = len(value) - one_is_multi = True + master_slave = True is_multi = True else: is_multi = False @@ -183,9 +193,11 @@ def carry_out_calculation(option, config, callback, callback_params, # if one value is a multi, launch several time calculate # if index is set, return a value # if no index, return a list - if one_is_multi: + if master_slave: ret = [] if index is not undefined: + # for example if append master and get a no default slave without + # getting master range_ = [index] else: range_ = range(len_multi) diff --git a/tiramisu/config.py b/tiramisu/config.py index fd7e86f..d6347c8 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -21,7 +21,8 @@ "options handler global entry point" import weakref from tiramisu.error import PropertiesOptionError, ConfigError -from tiramisu.option import OptionDescription, Option, SymLinkOption +from tiramisu.option import OptionDescription, Option, SymLinkOption, \ + DynSymLinkOption from tiramisu.setting import groups, Settings, default_encoding, undefined from tiramisu.storage import get_storages, get_storage, set_storage, \ _impl_getstate_setting @@ -47,12 +48,18 @@ class SubConfig(object): :type subpath: `str` with the path name """ # main option description - if descr is not None and not isinstance(descr, OptionDescription): + error = False + try: + if descr is not None and not descr.impl_is_optiondescription(): # pragma: optional cover + error = True + except AttributeError: + error = True + if error: raise TypeError(_('descr must be an optiondescription, not {0}' ).format(type(descr))) self._impl_descr = descr # sub option descriptions - if not isinstance(context, weakref.ReferenceType): + if not isinstance(context, weakref.ReferenceType): # pragma: optional cover raise ValueError('context must be a Weakref') self._impl_context = context self._impl_path = subpath @@ -60,7 +67,7 @@ class SubConfig(object): def cfgimpl_reset_cache(self, only_expired=False, only=('values', 'settings')): "remove cache (in context)" - self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only) + self._cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only) # pragma: optional cover def cfgimpl_get_home_by_path(self, path, force_permissive=False): """:returns: tuple (config, name)""" @@ -93,13 +100,15 @@ class SubConfig(object): def __iter__(self): """Pythonesque way of parsing group's ordered options. iteration only on Options (not OptionDescriptions)""" - for child in self.cfgimpl_get_description().impl_getchildren(): - if not isinstance(child, OptionDescription): + for child in self.cfgimpl_get_description()._impl_getchildren( + context=self._cfgimpl_get_context()): + if not child.impl_is_optiondescription(): try: - yield child.impl_getname(), getattr(self, child.impl_getname()) - except GeneratorExit: + name = child.impl_getname() + yield name, getattr(self, name) + except GeneratorExit: # pragma: optional cover raise StopIteration - except PropertiesOptionError: + except PropertiesOptionError: # pragma: optional cover pass # option with properties def iter_all(self, force_permissive=False): @@ -108,10 +117,10 @@ class SubConfig(object): for child in self.cfgimpl_get_description().impl_getchildren(): try: yield child.impl_getname(), self.getattr(child.impl_getname(), - force_permissive=force_permissive) - except GeneratorExit: + force_permissive=force_permissive) + except GeneratorExit: # pragma: optional cover raise StopIteration - except PropertiesOptionError: + except PropertiesOptionError: # pragma: optional cover pass # option with properties def iter_groups(self, group_type=None, force_permissive=False): @@ -124,19 +133,20 @@ class SubConfig(object): `setting.groups` """ if group_type is not None and not isinstance(group_type, - groups.GroupType): + groups.GroupType): # pragma: optional cover raise TypeError(_("unknown group_type: {0}").format(group_type)) - for child in self.cfgimpl_get_description().impl_getchildren(): - if isinstance(child, OptionDescription): + for child in self.cfgimpl_get_description()._impl_getchildren( + context=self._cfgimpl_get_context()): + if child.impl_is_optiondescription(): try: if group_type is None or (group_type is not None and child.impl_get_group_type() == group_type): - yield child.impl_getname(), self.getattr(child.impl_getname(), - force_permissive=force_permissive) - except GeneratorExit: + name = child.impl_getname() + yield name, self.getattr(name, force_permissive=force_permissive) + except GeneratorExit: # pragma: optional cover raise StopIteration - except PropertiesOptionError: + except PropertiesOptionError: # pragma: optional cover pass # ______________________________________________________________________ @@ -148,7 +158,7 @@ class SubConfig(object): for name, value in self: try: lines.append("{0} = {1}".format(name, value)) - except UnicodeEncodeError: + except UnicodeEncodeError: # pragma: optional cover lines.append("{0} = {1}".format(name, value.encode(default_encoding))) return '\n'.join(lines) @@ -162,12 +172,12 @@ class SubConfig(object): old `SubConfig`, `Values`, `Multi` or `Settings`) """ context = self._impl_context() - if context is None: + if context is None: # pragma: optional cover raise ConfigError(_('the context does not exist anymore')) return context def cfgimpl_get_description(self): - if self._impl_descr is None: + if self._impl_descr is None: # pragma: optional cover raise ConfigError(_('no option description found for this config' ' (may be GroupConfig)')) else: @@ -183,43 +193,50 @@ class SubConfig(object): # attribute methods def __setattr__(self, name, value): "attribute notation mechanism for the setting of the value of an option" - if name.startswith('_impl_'): - object.__setattr__(self, name, value) - return self._setattr(name, value) def _setattr(self, name, value, force_permissive=False): - if '.' in name: + if name.startswith('_impl_'): + object.__setattr__(self, name, value) + return + if '.' in name: # pragma: optional cover homeconfig, name = self.cfgimpl_get_home_by_path(name) - return homeconfig.__setattr__(name, value) - child = getattr(self.cfgimpl_get_description(), name) + return homeconfig._setattr(name, value, force_permissive) + context = self._cfgimpl_get_context() + child = self.cfgimpl_get_description().__getattr__(name, + context=context) if isinstance(child, OptionDescription): - raise TypeError(_("can't assign to an OptionDescription")) - elif not isinstance(child, SymLinkOption): - if self._impl_path is None: - path = name - else: - path = self._impl_path + '.' + name - self.cfgimpl_get_values().setitem(child, value, path, - force_permissive=force_permissive) - else: - context = self._cfgimpl_get_context() + raise TypeError(_("can't assign to an OptionDescription")) # pragma: optional cover + elif isinstance(child, SymLinkOption) and \ + not isinstance(child, DynSymLinkOption): # pragma: no dynoptiondescription cover path = context.cfgimpl_get_description().impl_get_path_by_opt( child._opt) context._setattr(path, value, force_permissive=force_permissive) + else: + subpath = self._get_subpath(name) + self.cfgimpl_get_values().setitem(child, value, subpath, + force_permissive=force_permissive) def __delattr__(self, name): - child = getattr(self.cfgimpl_get_description(), name) + context = self._cfgimpl_get_context() + child = self.cfgimpl_get_description().__getattr__(name, context) self.cfgimpl_get_values().__delitem__(child) def __getattr__(self, name): return self.getattr(name) - def _getattr(self, name, force_permissive=False, validate=True): + def _getattr(self, name, force_permissive=False, validate=True): # pragma: optional cover """use getattr instead of _getattr """ return self.getattr(name, force_permissive, validate) + def _get_subpath(self, name): + if self._impl_path is None: + subpath = name + else: + subpath = self._impl_path + '.' + name + return subpath + def getattr(self, name, force_permissive=False, validate=True): """ attribute notation mechanism for accessing the value of an option @@ -234,21 +251,20 @@ class SubConfig(object): name, force_permissive=force_permissive) return homeconfig.getattr(name, force_permissive=force_permissive, validate=validate) - opt_or_descr = self.cfgimpl_get_description().__getattr__(name) - if self._impl_path is None: - subpath = name - else: - subpath = self._impl_path + '.' + name - # symlink options - #FIXME a gerer plutot dans l'option ca ... - #FIXME je n'en sais rien en fait ... :/ - if isinstance(opt_or_descr, SymLinkOption): - context = self._cfgimpl_get_context() + context = self._cfgimpl_get_context() + opt_or_descr = self.cfgimpl_get_description().__getattr__(name, context=context) + subpath = self._get_subpath(name) + if isinstance(opt_or_descr, DynSymLinkOption): + return self.cfgimpl_get_values()._get_cached_item( + opt_or_descr, path=subpath, + validate=validate, + force_permissive=force_permissive) + elif isinstance(opt_or_descr, SymLinkOption): # pragma: no dynoptiondescription cover path = context.cfgimpl_get_description().impl_get_path_by_opt( opt_or_descr._opt) return context.getattr(path, validate=validate, force_permissive=force_permissive) - elif isinstance(opt_or_descr, OptionDescription): + elif opt_or_descr.impl_is_optiondescription(): self.cfgimpl_get_settings().validate_properties( opt_or_descr, True, False, path=subpath, force_permissive=force_permissive) @@ -272,7 +288,7 @@ class SubConfig(object): return self._cfgimpl_get_context()._find(bytype, byname, byvalue, first=False, type_=type_, - _subpath=self.cfgimpl_get_path(), + _subpath=self.cfgimpl_get_path(False), check_properties=check_properties, force_permissive=force_permissive) @@ -289,7 +305,7 @@ class SubConfig(object): """ return self._cfgimpl_get_context()._find( bytype, byname, byvalue, first=True, type_=type_, - _subpath=self.cfgimpl_get_path(), display_error=display_error, + _subpath=self.cfgimpl_get_path(False), display_error=display_error, check_properties=check_properties, force_permissive=force_permissive) @@ -311,11 +327,11 @@ class SubConfig(object): return byvalue in value else: return value == byvalue - except PropertiesOptionError: # a property is a restriction - # upon the access of the value + # a property is a restriction upon the access of the value + except PropertiesOptionError: # pragma: optional cover return False - if type_ not in ('option', 'path', 'value'): + if type_ not in ('option', 'path', 'value'): # pragma: optional cover raise ValueError(_('unknown type_ type {0}' 'for _find').format(type_)) find_results = [] @@ -324,7 +340,7 @@ class SubConfig(object): # and so on only_first = first is True and byvalue is None and check_properties is None options = self.cfgimpl_get_description().impl_get_options_paths( - bytype, byname, _subpath, only_first) + bytype, byname, _subpath, only_first, self._cfgimpl_get_context()) for path, option in options: if not _filter_by_value(): continue @@ -333,7 +349,7 @@ class SubConfig(object): try: value = self.getattr(path, force_permissive=force_permissive) - except PropertiesOptionError: + except PropertiesOptionError: # pragma: optional cover # a property restricts the access of the value continue if type_ == 'value': @@ -349,7 +365,7 @@ class SubConfig(object): return self._find_return_results(find_results, display_error) def _find_return_results(self, find_results, display_error): - if find_results == []: + if find_results == []: # pragma: optional cover if display_error: raise AttributeError(_("no option found in config" " with these criteria")) @@ -400,21 +416,18 @@ class SubConfig(object): pathsvalues = [] if _currpath is None: _currpath = [] - if withoption is None and withvalue is not undefined: + if withoption is None and withvalue is not undefined: # pragma: optional cover raise ValueError(_("make_dict can't filtering with value without " "option")) if withoption is not None: - mypath = self.cfgimpl_get_path() - for path in self._cfgimpl_get_context()._find(bytype=None, - byname=withoption, - byvalue=withvalue, - first=False, - type_='path', - _subpath=mypath, - force_permissive=force_permissive): + context = self._cfgimpl_get_context() + for path in context._find(bytype=None, byname=withoption, + byvalue=withvalue, first=False, + type_='path', _subpath=self.cfgimpl_get_path(False), + force_permissive=force_permissive): path = '.'.join(path.split('.')[:-1]) - opt = self._cfgimpl_get_context().cfgimpl_get_description( - ).impl_get_opt_by_path(path) + opt = context.unwrap_from_path(path, force_permissive=True) + mypath = self.cfgimpl_get_path() if mypath is not None: if mypath == path: withoption = None @@ -422,7 +435,7 @@ class SubConfig(object): break else: tmypath = mypath + '.' - if not path.startswith(tmypath): + if not path.startswith(tmypath): # pragma: optional cover raise AttributeError(_('unexpected path {0}, ' 'should start with {1}' '').format(path, mypath)) @@ -443,7 +456,7 @@ class SubConfig(object): def _make_sub_dict(self, opt, path, pathsvalues, _currpath, flatten, force_permissive=False): try: - if isinstance(opt, OptionDescription): + if opt.impl_is_optiondescription(): pathsvalues += self.getattr(path, force_permissive=force_permissive).make_dict( flatten, @@ -457,13 +470,15 @@ class SubConfig(object): else: name = '.'.join(_currpath + [opt.impl_getname()]) pathsvalues.append((name, value)) - except PropertiesOptionError: + except PropertiesOptionError: # pragma: optional cover pass - def cfgimpl_get_path(self): + def cfgimpl_get_path(self, dyn=True): descr = self.cfgimpl_get_description() - context_descr = self._cfgimpl_get_context().cfgimpl_get_description() - return context_descr.impl_get_path_by_opt(descr) + if not dyn and descr.impl_is_dynoptiondescription(): + context_descr = self._cfgimpl_get_context().cfgimpl_get_description() + return context_descr.impl_get_path_by_opt(descr._opt) + return self._impl_path class _CommonConfig(SubConfig): @@ -488,7 +503,7 @@ class _CommonConfig(SubConfig): """convenience method to retrieve an option's owner from the config itself """ - if not isinstance(opt, Option) and not isinstance(opt, SymLinkOption): + if not isinstance(opt, Option) and not isinstance(opt, SymLinkOption): # pragma: optional cover raise TypeError(_('opt in getowner must be an option not {0}' '').format(type(opt))) return self.cfgimpl_get_values().getowner(opt, @@ -501,13 +516,14 @@ class _CommonConfig(SubConfig): :returns: Option() """ + context = self._cfgimpl_get_context() if '.' in path: homeconfig, path = self.cfgimpl_get_home_by_path( path, force_permissive=force_permissive) - return getattr(homeconfig.cfgimpl_get_description(), path) - return getattr(self.cfgimpl_get_description(), path) + return homeconfig.cfgimpl_get_description().__getattr__(path, context=context) + return self.cfgimpl_get_description().__getattr__(path, context=context) - def cfgimpl_get_path(self): + def cfgimpl_get_path(self, dyn=True): return None def cfgimpl_get_meta(self): @@ -533,7 +549,7 @@ class _CommonConfig(SubConfig): # ----- state def __getstate__(self): if self._impl_meta is not None: - raise ConfigError(_('cannot serialize Config with MetaConfig')) + raise ConfigError(_('cannot serialize Config with MetaConfig')) # pragma: optional cover slots = set() for subclass in self.__class__.__mro__: if subclass is not object: @@ -543,12 +559,12 @@ class _CommonConfig(SubConfig): for slot in slots: try: state[slot] = getattr(self, slot) - except AttributeError: + except AttributeError: # pragma: optional cover pass storage = self._impl_values._p_._storage if not storage.serializable: raise ConfigError(_('this storage is not serialisable, could be a ' - 'none persistent storage')) + 'none persistent storage')) # pragma: optional cover state['_storage'] = {'session_id': storage.session_id, 'persistent': storage.persistent} state['_impl_setting'] = _impl_getstate_setting() @@ -719,6 +735,6 @@ class MetaConfig(GroupConfig): super(MetaConfig, self).__init__(children, session_id, persistent, descr) -def mandatory_warnings(config): +def mandatory_warnings(config): # pragma: optional cover #only for retro-compatibility return config.cfgimpl_get_values().mandatory_warnings() diff --git a/tiramisu/error.py b/tiramisu/error.py index e4955bb..93e93f9 100644 --- a/tiramisu/error.py +++ b/tiramisu/error.py @@ -65,7 +65,7 @@ class ConstError(TypeError): #Warning -class ValueWarning(UserWarning): +class ValueWarning(UserWarning): # pragma: optional cover """Option could warn user and not raise ValueError. Example: diff --git a/tiramisu/i18n.py b/tiramisu/i18n.py index 0223ccf..2677d54 100644 --- a/tiramisu/i18n.py +++ b/tiramisu/i18n.py @@ -43,9 +43,9 @@ languages += DEFAULT_LANG mo_location = LOCALE_DIR -if sys.version_info[0] >= 3: +if sys.version_info[0] >= 3: # pragma: optional cover gettext.install(True, localedir=None) -else: +else: # pragma: optional cover gettext.install(True, localedir=None, unicode=1) gettext.find(APP_NAME, mo_location) gettext.textdomain(APP_NAME) diff --git a/tiramisu/option/__init__.py b/tiramisu/option/__init__.py index c17b243..76c3446 100644 --- a/tiramisu/option/__init__.py +++ b/tiramisu/option/__init__.py @@ -1,6 +1,7 @@ from .masterslave import MasterSlaves -from .optiondescription import OptionDescription -from .baseoption import Option, SymLinkOption, submulti +from .optiondescription import OptionDescription, DynOptionDescription, \ + SynDynOptionDescription +from .baseoption import Option, SymLinkOption, DynSymLinkOption, submulti from .option import (ChoiceOption, BoolOption, IntOption, FloatOption, StrOption, UnicodeOption, IPOption, PortOption, NetworkOption, NetmaskOption, BroadcastOption, @@ -8,9 +9,10 @@ from .option import (ChoiceOption, BoolOption, IntOption, FloatOption, FilenameOption) -__all__ = ('MasterSlaves', 'OptionDescription', 'Option', 'SymLinkOption', - 'ChoiceOption', 'BoolOption', 'IntOption', 'FloatOption', - 'StrOption', 'UnicodeOption', 'IPOption', 'PortOption', - 'NetworkOption', 'NetmaskOption', 'BroadcastOption', - 'DomainnameOption', 'EmailOption', 'URLOption', 'UsernameOption', - 'FilenameOption', 'submulti') +__all__ = ('MasterSlaves', 'OptionDescription', 'DynOptionDescription', + 'SynDynOptionDescription', 'Option', 'SymLinkOption', + 'DynSymLinkOption', 'ChoiceOption', 'BoolOption', + 'IntOption', 'FloatOption', 'StrOption', 'UnicodeOption', + 'IPOption', 'PortOption', 'NetworkOption', 'NetmaskOption', + 'BroadcastOption', 'DomainnameOption', 'EmailOption', 'URLOption', + 'UsernameOption', 'FilenameOption', 'submulti') diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index d7c1a69..06f4bb4 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -40,7 +40,8 @@ class SubMulti(object): submulti = SubMulti() -name_regexp = re.compile(r'^\d+') +allowed_character = '[a-z\d\-_]' +name_regexp = re.compile(r'^[a-z]{0}*$'.format(allowed_character)) forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first', 'make_dict', 'unwrap_from_path', 'read_only', 'read_write', 'getowner', 'set_contexts') @@ -48,51 +49,51 @@ forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first', def valid_name(name): "an option's name is a str and does not start with 'impl' or 'cfgimpl'" - if not isinstance(name, str): + if not isinstance(name, str): # pragma: optional cover return False - if re.match(name_regexp, name) is None and not name.startswith('_') \ + if re.match(name_regexp, name) is not None and not name.startswith('_') \ and name not in forbidden_names \ and not name.startswith('impl_') \ and not name.startswith('cfgimpl_'): return True - else: + else: # pragma: optional cover return False def validate_callback(callback, callback_params, type_): - if type(callback) != FunctionType: + if type(callback) != FunctionType: # pragma: optional cover raise ValueError(_('{0} must be a function').format(type_)) if callback_params is not None: - if not isinstance(callback_params, dict): + if not isinstance(callback_params, dict): # pragma: optional cover raise ValueError(_('{0}_params must be a dict').format(type_)) for key, callbacks in callback_params.items(): - if key != '' and len(callbacks) != 1: + if key != '' and len(callbacks) != 1: # pragma: optional cover raise ValueError(_("{0}_params with key {1} mustn't have " "length different to 1").format(type_, key)) - if not isinstance(callbacks, tuple): + if not isinstance(callbacks, tuple): # pragma: optional cover raise ValueError(_('{0}_params must be tuple for key "{1}"' ).format(type_, key)) for callbk in callbacks: if isinstance(callbk, tuple): if len(callbk) == 1: - if callbk != (None,): + if callbk != (None,): # pragma: optional cover raise ValueError(_('{0}_params with length of ' 'tuple as 1 must only have ' 'None as first value')) - elif len(callbk) != 2: + elif len(callbk) != 2: # pragma: optional cover raise ValueError(_('{0}_params must only have 1 or 2 ' 'as length')) else: option, force_permissive = callbk - if type_ == 'validator' and not force_permissive: + if type_ == 'validator' and not force_permissive: # pragma: optional cover raise ValueError(_('validator not support tuple')) if not isinstance(option, Option) and not \ - isinstance(option, SymLinkOption): + isinstance(option, SymLinkOption): # pragma: optional cover raise ValueError(_('{0}_params must have an option' ' not a {0} for first argument' ).format(type_, type(option))) - if force_permissive not in [True, False]: + if force_permissive not in [True, False]: # pragma: optional cover raise ValueError(_('{0}_params must have a boolean' ' not a {0} for second argument' ).format(type_, type( @@ -104,11 +105,23 @@ def validate_callback(callback, callback_params, type_): class Base(StorageBase): __slots__ = tuple() + def impl_set_callback(self, callback, callback_params=None): + if callback is None and callback_params is not None: # pragma: optional cover + raise ValueError(_("params defined for a callback function but " + "no callback defined" + " yet for option {0}").format( + self.impl_getname())) + self._validate_callback(callback, callback_params) + if callback is not None: + validate_callback(callback, callback_params, 'callback') + self._callback = callback + self._callback_params = callback_params + def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_params=None, properties=None, warnings_only=False): - if not valid_name(name): + if not valid_name(name): # pragma: optional cover raise ValueError(_("invalid name: {0} for option").format(name)) self._name = name self._readonly = False @@ -120,13 +133,13 @@ class Base(StorageBase): else: self._calc_properties = frozenset() self._requires = [] - if not multi and default_multi is not None: + if not multi and default_multi is not None: # pragma: optional cover raise ValueError(_("a default_multi is set whereas multi is False" " in option: {0}").format(name)) if default_multi is not None: try: self._validate(default_multi) - except ValueError as err: + except ValueError as err: # pragma: optional cover raise ValueError(_("invalid default_multi value {0} " "for option {1}: {2}").format( str(default_multi), name, err)) @@ -135,16 +148,9 @@ class Base(StorageBase): if default is None: default = [] self._default_multi = default_multi - if callback is not None and ((not multi and (default is not None or - default_multi is not None)) - or (multi and (default != [] or - default_multi is not None)) - ): - raise ValueError(_("default value not allowed if option: {0} " - "is calculated").format(name)) if properties is None: properties = tuple() - if not isinstance(properties, tuple): + if not isinstance(properties, tuple): # pragma: optional cover raise TypeError(_('invalid properties type {0} for {1},' ' must be a tuple').format( type(properties), @@ -154,16 +160,7 @@ class Base(StorageBase): self._validator = validator if validator_params is not None: self._validator_params = validator_params - if callback is None and callback_params is not None: - raise ValueError(_("params defined for a callback function but " - "no callback defined" - " yet for option {0}").format(name)) - if callback is not None: - validate_callback(callback, callback_params, 'callback') - self._callback = callback - if callback_params is not None: - self._callback_params = callback_params - if self._calc_properties != frozenset([]) and properties is not tuple(): + if self._calc_properties != frozenset([]) and properties is not tuple(): # pragma: optional cover set_forbidden_properties = self._calc_properties & set(properties) if set_forbidden_properties != frozenset(): raise ValueError('conflict: properties already set in ' @@ -173,12 +170,23 @@ class Base(StorageBase): self._default = [] else: self._default = default + if callback is not False: + self.impl_set_callback(callback, callback_params) self._properties = properties self._warnings_only = warnings_only ret = super(Base, self).__init__() self.impl_validate(self._default) return ret + def impl_is_optiondescription(self): + return self.__class__.__name__ in ['OptionDescription', + 'DynOptionDescription', + 'SynDynOptionDescription'] + + def impl_is_dynoptiondescription(self): + return self.__class__.__name__ in ['DynOptionDescription', + 'SynDynOptionDescription'] + class BaseOption(Base): """This abstract base class stands for attribute access @@ -204,9 +212,9 @@ class BaseOption(Base): """ if key in self._informations: return self._informations[key] - elif default is not undefined: + elif default is not undefined: # pragma: optional cover return default - else: + else: # pragma: optional cover raise ValueError(_("information's item not found: {0}").format( key)) @@ -246,6 +254,52 @@ class BaseOption(Base): else: self._state_requires = new_value + def _impl_convert_callback(self, descr, load=False): + if self.__class__.__name__ == 'OptionDescription' or \ + isinstance(self, SymLinkOption): + return + if not load and self._callback is None: + self._state_callback = None + self._state_callback_params = None + elif load and self._state_callback is None: + self._callback = None + self._callback_params = None + del(self._state_callback) + del(self._state_callback_params) + else: + if load: + callback = self._state_callback + callback_params = self._state_callback_params + else: + callback = self._callback + callback_params = self._callback_params + self._state_callback_params = None + if callback_params is not None: + cllbck_prms = {} + for key, values in callback_params.items(): + vls = [] + for value in values: + if isinstance(value, tuple): + if load: + value = (descr.impl_get_opt_by_path(value[0]), + value[1]) + else: + value = (descr.impl_get_path_by_opt(value[0]), + value[1]) + vls.append(value) + cllbck_prms[key] = tuple(vls) + else: + cllbck_prms = None + + if load: + del(self._state_callback) + del(self._state_callback_params) + self._callback = callback + self._callback_params = cllbck_prms + else: + self._state_callback = callback + self._state_callback_params = cllbck_prms + # serialize def _impl_getstate(self, descr): """the under the hood stuff that need to be done @@ -270,7 +324,7 @@ class BaseOption(Base): """ try: self._stated - except AttributeError: + except AttributeError: # pragma: optional cover raise SystemError(_('cannot serialize Option, ' 'only in OptionDescription')) slots = set() @@ -311,7 +365,7 @@ class BaseOption(Base): self._readonly = self._state_readonly del(self._state_readonly) del(self._stated) - except AttributeError: + except AttributeError: # pragma: optional cover pass def __setstate__(self, state): @@ -350,7 +404,7 @@ class BaseOption(Base): pass elif name != '_readonly': is_readonly = self.impl_is_readonly() - if is_readonly: + if is_readonly: # pragma: optional cover raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is" " read-only").format( self.__class__.__name__, @@ -369,6 +423,12 @@ class BaseOption(Base): def impl_getname(self): return self._name + def impl_getpath(self, context): + return context.cfgimpl_get_description().impl_get_path_by_opt(self) + + def impl_get_callback(self): + return self._callback, self._callback_params + class OnlyOption(BaseOption): __slots__ = tuple() @@ -440,25 +500,31 @@ class Option(OnlyOption): :param warnings_only: specific raise error for warning :type warnings_only: `boolean` """ - if context is not None: + if context is not undefined: descr = context.cfgimpl_get_description() all_cons_vals = [] for opt in all_cons_opts: #get value - if option == opt: + if (isinstance(opt, DynSymLinkOption) and option._dyn == opt._dyn) or \ + option == opt: opt_value = value else: #if context, calculate value, otherwise get default value - if context is not None: - opt_value = context.getattr( - descr.impl_get_path_by_opt(opt), validate=False, - force_permissive=True) + if context is not undefined: + if isinstance(opt, DynSymLinkOption): + path = opt.impl_getpath(context) + else: + path = descr.impl_get_path_by_opt(opt) + opt_value = context.getattr(path, validate=False, + force_permissive=True) else: opt_value = opt.impl_getdefault() #append value - if not self.impl_is_multi() or option == opt: + if not self.impl_is_multi() or (isinstance(opt, DynSymLinkOption) + and option._dyn == opt._dyn) or \ + option == opt: all_cons_vals.append(opt_value) elif self.impl_is_submulti(): try: @@ -476,8 +542,9 @@ class Option(OnlyOption): return getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only) - def impl_validate(self, value, context=None, validate=True, - force_index=None, force_submulti_index=None): + def impl_validate(self, value, context=undefined, validate=True, + force_index=None, force_submulti_index=None, + current_opt=undefined): """ :param value: the option's value :param context: Config's context @@ -493,6 +560,8 @@ class Option(OnlyOption): """ if not validate: return + if current_opt is undefined: + current_opt = self def val_validator(val): if self._validator is not None: @@ -520,7 +589,7 @@ class Option(OnlyOption): # option validation try: self._validate(_value, context) - except ValueError as err: + except ValueError as err: # pragma: optional cover log.debug('do_validation: value: {0}, index: {1}, ' 'submulti_index: {2}'.format(_value, _index, submulti_index), @@ -533,9 +602,9 @@ class Option(OnlyOption): # valid with self._validator val_validator(_value) # if not context launch consistency validation - if context is not None: - descr._valid_consistency(self, _value, context, _index, - submulti_index) + if context is not undefined: + descr._valid_consistency(current_opt, _value, context, + _index, submulti_index) self._second_level_validation(_value, self._warnings_only) except ValueError as error: log.debug(_('do_validation for {0}: error in value').format( @@ -550,9 +619,9 @@ class Option(OnlyOption): if error is None and warning is None: try: # if context launch consistency validation - if context is not None: - descr._valid_consistency(self, _value, context, _index, - submulti_index) + if context is not undefined: + descr._valid_consistency(current_opt, _value, context, + _index, submulti_index) except ValueError as error: log.debug(_('do_validation for {0}: error in consistency').format( self.impl_getname()), exc_info=True) @@ -572,14 +641,14 @@ class Option(OnlyOption): self._name, error)) # generic calculation - if context is not None: + if context is not undefined: descr = context.cfgimpl_get_description() if not self.impl_is_multi(): do_validation(value, None, None) elif force_index is not None: if self.impl_is_submulti() and force_submulti_index is None: - if not isinstance(value, list): + if not isinstance(value, list): # pragma: optional cover raise ValueError(_("invalid value {0} for option {1} which" " must be a list").format( value, self.impl_getname())) @@ -588,13 +657,13 @@ class Option(OnlyOption): else: do_validation(value, force_index, force_submulti_index) else: - if not isinstance(value, list): + if not isinstance(value, list): # pragma: optional cover raise ValueError(_("invalid value {0} for option {1} which " "must be a list").format(value, self.impl_getname())) for idx, val in enumerate(value): if self.impl_is_submulti() and force_submulti_index is None: - if not isinstance(val, list): + if not isinstance(val, list): # pragma: optional cover raise ValueError(_("invalid value {0} for option {1} " "which must be a list of list" "").format(value, @@ -651,9 +720,6 @@ class Option(OnlyOption): else: return True - def impl_get_callback(self): - return self._callback, self._callback_params - #def impl_getkey(self, value): # return value @@ -673,18 +739,33 @@ class Option(OnlyOption): :type other_opts: `list` of `tiramisu.option.Option` :param params: extra params (only warnings_only are allowed) """ - if self.impl_is_readonly(): + if self.impl_is_readonly(): # pragma: optional cover raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is" " read-only").format( self.__class__.__name__, self._name)) warnings_only = params.get('warnings_only', False) + if self._is_subdyn(): + dynod = self._subdyn + else: + dynod = None for opt in other_opts: - if not isinstance(opt, Option): + if not isinstance(opt, Option): # pragma: optional cover raise ConfigError(_('consistency must be set with an option')) - if self is opt: + if opt._is_subdyn(): + if dynod is None: + raise ConfigError(_('almost one option in consistency is ' + 'in a dynoptiondescription but not all')) + if dynod != opt._subdyn: + raise ConfigError(_('option in consistency must be in same' + ' dynoptiondescription')) + dynod = opt._subdyn + elif dynod is not None: + raise ConfigError(_('almost one option in consistency is in a ' + 'dynoptiondescription but not all')) + if self is opt: # pragma: optional cover raise ConfigError(_('cannot add consistency with itself')) - if self.impl_is_multi() != opt.impl_is_multi(): + if self.impl_is_multi() != opt.impl_is_multi(): # pragma: optional cover raise ConfigError(_('every options in consistency must be ' 'multi or none')) func = '_cons_{0}'.format(func) @@ -694,7 +775,7 @@ class Option(OnlyOption): if self.impl_is_multi(): for idx, val in enumerate(value): if not self.impl_is_submulti(): - self._launch_consistency(func, self, val, None, idx, + self._launch_consistency(func, self, val, undefined, idx, None, all_cons_opts, warnings_only) else: @@ -704,7 +785,7 @@ class Option(OnlyOption): all_cons_opts, warnings_only) else: - self._launch_consistency(func, self, value, None, None, + self._launch_consistency(func, self, value, undefined, None, None, all_cons_opts, warnings_only) self._add_consistency(func, all_cons_opts, params) self.impl_validate(self.impl_getdefault()) @@ -720,49 +801,6 @@ class Option(OnlyOption): raise ValueError(msg.format(opts[idx_inf].impl_getname(), opts[idx_inf + idx_sup + 1].impl_getname())) - def _impl_convert_callbacks(self, descr, load=False): - if not load and self._callback is None: - self._state_callback = None - self._state_callback_params = None - elif load and self._state_callback is None: - self._callback = None - self._callback_params = None - del(self._state_callback) - del(self._state_callback_params) - else: - if load: - callback = self._state_callback - callback_params = self._state_callback_params - else: - callback = self._callback - callback_params = self._callback_params - self._state_callback_params = None - if callback_params is not None: - cllbck_prms = {} - for key, values in callback_params.items(): - vls = [] - for value in values: - if isinstance(value, tuple): - if load: - value = (descr.impl_get_opt_by_path(value[0]), - value[1]) - else: - value = (descr.impl_get_path_by_opt(value[0]), - value[1]) - vls.append(value) - cllbck_prms[key] = tuple(vls) - else: - cllbck_prms = None - - if load: - del(self._state_callback) - del(self._state_callback_params) - self._callback = callback - self._callback_params = cllbck_prms - else: - self._state_callback = callback - self._state_callback_params = cllbck_prms - # serialize/unserialize def _impl_convert_consistencies(self, descr, load=False): """during serialization process, many things have to be done. @@ -801,6 +839,24 @@ class Option(OnlyOption): def _second_level_validation(self, value, warnings_only): pass + def _impl_to_dyn(self, name, path): + return DynSymLinkOption(name, self, dyn=path) + + def _validate_callback(self, callback, callback_params): + try: + default_multi = self._default_multi + except AttributeError: + default_multi = None + if callback is not None and ((not self._multi and + (self._default is not None or + default_multi is not None)) + or (self._multi and + (self._default != [] or + default_multi is not None)) + ): # pragma: optional cover + raise ValueError(_("default value not allowed if option: {0} " + "is calculated").format(self.impl_getname())) + def validate_requires_arg(requires, name): """check malformed requirements @@ -819,60 +875,60 @@ def validate_requires_arg(requires, name): # start parsing all requires given by user (has dict) # transforme it to a tuple for require in requires: - if not type(require) == dict: + if not type(require) == dict: # pragma: optional cover raise ValueError(_("malformed requirements type for option:" " {0}, must be a dict").format(name)) valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive', 'same_action') unknown_keys = frozenset(require.keys()) - frozenset(valid_keys) - if unknown_keys != frozenset(): - raise ValueError('malformed requirements for option: {0}' + if unknown_keys != frozenset(): # pragma: optional cover + raise ValueError(_('malformed requirements for option: {0}' ' unknown keys {1}, must only ' - '{2}'.format(name, - unknown_keys, - valid_keys)) + '{2}').format(name, + unknown_keys, + valid_keys)) # prepare all attributes try: option = require['option'] expected = require['expected'] action = require['action'] - except KeyError: + except KeyError: # pragma: optional cover raise ValueError(_("malformed requirements for option: {0}" " require must have option, expected and" " action keys").format(name)) - if action == 'force_store_value': + if action == 'force_store_value': # pragma: optional cover raise ValueError(_("malformed requirements for option: {0}" " action cannot be force_store_value" ).format(name)) inverse = require.get('inverse', False) - if inverse not in [True, False]: + if inverse not in [True, False]: # pragma: optional cover raise ValueError(_('malformed requirements for option: {0}' ' inverse must be boolean')) transitive = require.get('transitive', True) - if transitive not in [True, False]: + if transitive not in [True, False]: # pragma: optional cover raise ValueError(_('malformed requirements for option: {0}' ' transitive must be boolean')) same_action = require.get('same_action', True) - if same_action not in [True, False]: + if same_action not in [True, False]: # pragma: optional cover raise ValueError(_('malformed requirements for option: {0}' ' same_action must be boolean')) - if not isinstance(option, Option): + if not isinstance(option, Option): # pragma: optional cover raise ValueError(_('malformed requirements ' 'must be an option in option {0}').format(name)) - if option.impl_is_multi(): + if option.impl_is_multi(): # pragma: optional cover raise ValueError(_('malformed requirements option {0} ' 'must not be a multi for {1}').format( option.impl_getname(), name)) if expected is not None: try: option._validate(expected) - except ValueError as err: + except ValueError as err: # pragma: optional cover raise ValueError(_('malformed requirements second argument ' 'must be valid for option {0}' ': {1}').format(name, err)) if action in config_action: - if inverse != config_action[action]: + if inverse != config_action[action]: # pragma: optional cover raise ValueError(_("inconsistency in action types" " for option: {0}" " action: {1}").format(name, action)) @@ -897,23 +953,20 @@ def validate_requires_arg(requires, name): class SymLinkOption(OnlyOption): - #FIXME : et avec sqlalchemy ca marche vraiment ? __slots__ = ('_opt', '_state_opt') - #not return _opt consistencies - #_consistencies = None def __init__(self, name, opt): self._name = name - if not isinstance(opt, Option): + if not isinstance(opt, Option): # pragma: optional cover raise ValueError(_('malformed symlinkoption ' 'must be an option ' 'for symlink {0}').format(name)) self._opt = opt self._readonly = True - return super(Base, self).__init__() + super(Base, self).__init__() - def __getattr__(self, name): - if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'): + def __getattr__(self, name, context=undefined): + if name in ('_opt', '_opt_type', '_readonly', 'impl_getpath'): return object.__getattr__(self, name) else: return getattr(self._opt, name) @@ -929,3 +982,29 @@ class SymLinkOption(OnlyOption): def impl_get_information(self, key, default=undefined): return self._opt.impl_get_information(key, default) + + +class DynSymLinkOption(SymLinkOption): + __slots__ = ('_dyn',) + + def __init__(self, name, opt, dyn): + self._dyn = dyn + super(DynSymLinkOption, self).__init__(name, opt) + + def impl_getsuffix(self): + return self._dyn.split('.')[-1][len(self._opt.impl_getname()):] + + def impl_getpath(self, context): + path = self._opt.impl_getpath(context) + base_path = '.'.join(path.split('.')[:-2]) + if self.impl_is_master_slaves() and base_path is not '': + base_path = base_path + self.impl_getsuffix() + if base_path == '': + return self._dyn + else: + return base_path + '.' + self._dyn + + def impl_validate(self, value, context=undefined, validate=True, + force_index=None, force_submulti_index=None): + return self._opt.impl_validate(value, context, validate, force_index, + force_submulti_index, current_opt=self) diff --git a/tiramisu/option/masterslave.py b/tiramisu/option/masterslave.py index b962a98..f7dadf9 100644 --- a/tiramisu/option/masterslave.py +++ b/tiramisu/option/masterslave.py @@ -22,63 +22,92 @@ from tiramisu.i18n import _ from tiramisu.setting import log, undefined from tiramisu.error import SlaveError, ConfigError -from .baseoption import SymLinkOption, Option +from .baseoption import DynSymLinkOption, SymLinkOption, Option class MasterSlaves(object): __slots__ = ('master', 'slaves') - def __init__(self, name, childs): + def __init__(self, name, childs, validate=True): #if master (same name has group) is set #for collect all slaves self.master = None slaves = [] for child in childs: - if isinstance(child, SymLinkOption): + if isinstance(child, SymLinkOption): # pragma: optional cover raise ValueError(_("master group {0} shall not have " "a symlinkoption").format(name)) - if not isinstance(child, Option): + if not isinstance(child, Option): # pragma: optional cover raise ValueError(_("master group {0} shall not have " "a subgroup").format(name)) - if not child.impl_is_multi(): + if not child.impl_is_multi(): # pragma: optional cover raise ValueError(_("not allowed option {0} " "in group {1}" ": this option is not a multi" - "").format(child._name, name)) - if child._name == name: + "").format(child.impl_getname(), name)) + if child.impl_getname() == name: self.master = child else: slaves.append(child) - if self.master is None: + if self.master is None: # pragma: optional cover raise ValueError(_('master group with wrong' ' master name for {0}' ).format(name)) - callback, callback_params = self.master.impl_get_callback() - if callback is not None and callback_params is not None: - for key, callbacks in callback_params.items(): - for callbk in callbacks: - if isinstance(callbk, tuple): - if callbk[0] in slaves: - raise ValueError(_("callback of master's option shall " - "not refered a slave's ones")) + if validate: + callback, callback_params = self.master.impl_get_callback() + if callback is not None and callback_params is not None: # pragma: optional cover + for key, callbacks in callback_params.items(): + for callbk in callbacks: + if isinstance(callbk, tuple): + if callbk[0] in slaves: + raise ValueError(_("callback of master's option shall " + "not refered a slave's ones")) #everything is ok, store references self.slaves = tuple(slaves) for child in childs: child._master_slaves = self def is_master(self, opt): - return opt == self.master + return opt == self.master or (isinstance(opt, DynSymLinkOption) and + opt._opt == self.master) + + def getmaster(self, opt): + if isinstance(opt, DynSymLinkOption): + suffix = opt.impl_getsuffix() + name = self.master.impl_getname() + suffix + base_path = opt._dyn.split('.')[0] + '.' + path = base_path + name + master = self.master._impl_to_dyn(name, path) + else: # pragma: no dynoptiondescription cover + master = self.master + return master + + def getslaves(self, opt): + if isinstance(opt, DynSymLinkOption): + for slave in self.slaves: + suffix = opt.impl_getsuffix() + name = slave.impl_getname() + suffix + base_path = opt._dyn.split('.')[0] + '.' + path = base_path + name + yield slave._impl_to_dyn(name, path) + else: # pragma: no dynoptiondescription cover + for slave in self.slaves: + yield slave def in_same_group(self, opt): - return opt == self.master or opt in self.slaves + if isinstance(opt, DynSymLinkOption): + return opt._opt == self.master or opt._opt in self.slaves + else: # pragma: no dynoptiondescription cover + return opt == self.master or opt in self.slaves - def reset(self, values): - for slave in self.slaves: + def reset(self, opt, values): + #FIXME pas de opt ??? + for slave in self.getslaves(opt): values.reset(slave) - def pop(self, values, index): + def pop(self, opt, values, index): #FIXME pas test de meta ... - for slave in self.slaves: + for slave in self.getslaves(opt): if not values.is_default_owner(slave, validate_properties=False, validate_meta=False): values._get_cached_item(slave, validate=False, @@ -89,7 +118,7 @@ class MasterSlaves(object): def getitem(self, values, opt, path, validate, force_permissive, force_properties, validate_properties, slave_path=undefined, slave_value=undefined): - if opt == self.master: + if self.is_master(opt): return self._getmaster(values, opt, path, validate, force_permissive, force_properties, validate_properties, slave_path, @@ -108,9 +137,9 @@ class MasterSlaves(object): validate_properties) if validate is True: masterlen = len(value) - for slave in self.slaves: + for slave in self.getslaves(opt): try: - slave_path = values._get_opt_path(slave) + slave_path = slave.impl_getpath(values._getcontext()) if c_slave_path == slave_path: slave_value = c_slave_value else: @@ -121,8 +150,8 @@ class MasterSlaves(object): None, False, None) # not undefined slavelen = len(slave_value) - self.validate_slave_length(masterlen, slavelen, slave._name) - except ConfigError: + self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt) + except ConfigError: # pragma: optional cover pass return value @@ -136,10 +165,10 @@ class MasterSlaves(object): return self.get_slave_value(values, opt, value, validate, validate_properties) def setitem(self, values, opt, value, path): - if opt == self.master: + if self.is_master(opt): masterlen = len(value) - for slave in self.slaves: - slave_path = values._get_opt_path(slave) + for slave in self.getslaves(opt): + slave_path = slave.impl_getpath(values._getcontext()) slave_value = values._get_validated_value(slave, slave_path, False, @@ -147,24 +176,28 @@ class MasterSlaves(object): None, False, None) # not undefined slavelen = len(slave_value) - self.validate_slave_length(masterlen, slavelen, slave._name) + self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt) else: - self.validate_slave_length(self.get_length(values), len(value), - opt._name, setitem=True) + self.validate_slave_length(self.get_length(values, opt, + slave_path=path), len(value), + opt.impl_getname(), opt, setitem=True) - def get_length(self, values, validate=True, slave_path=undefined, + def get_length(self, values, opt, validate=True, slave_path=undefined, slave_value=undefined): - masterp = values._get_opt_path(self.master) - return len(self.getitem(values, self.master, masterp, validate, False, + """get master len with slave option""" + masterp = self.getmaster(opt).impl_getpath(values._getcontext()) + if slave_value is undefined: + slave_path = undefined + return len(self.getitem(values, self.getmaster(opt), masterp, validate, False, None, True, slave_path, slave_value)) - def validate_slave_length(self, masterlen, valuelen, name, setitem=False): - if valuelen > masterlen or (valuelen < masterlen and setitem): + def validate_slave_length(self, masterlen, valuelen, name, opt, setitem=False): + if valuelen > masterlen or (valuelen < masterlen and setitem): # pragma: optional cover log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, ' 'setitem: {2}'.format(masterlen, valuelen, setitem)) raise SlaveError(_("invalid len for the slave: {0}" " which has {1} as master").format( - name, self.master._name)) + name, self.getmaster(opt).impl_getname())) def get_slave_value(self, values, opt, value, validate=True, validate_properties=True): @@ -190,11 +223,11 @@ class MasterSlaves(object): list is greater than master: raise SlaveError """ #if slave, had values until master's one - path = values._get_opt_path(opt) - masterlen = self.get_length(values, validate, path, value) + path = opt.impl_getpath(values._getcontext()) + masterlen = self.get_length(values, opt, validate, path, value) valuelen = len(value) if validate: - self.validate_slave_length(masterlen, valuelen, opt._name) + self.validate_slave_length(masterlen, valuelen, opt.impl_getname(), opt) if valuelen < masterlen: for num in range(0, masterlen - valuelen): index = valuelen + num diff --git a/tiramisu/option/option.py b/tiramisu/option/option.py index 584d94b..48541b4 100644 --- a/tiramisu/option/option.py +++ b/tiramisu/option/option.py @@ -23,7 +23,7 @@ import re import sys from IPy import IP from types import FunctionType -from tiramisu.setting import log +from tiramisu.setting import log, undefined from tiramisu.error import ConfigError, ContextError from tiramisu.i18n import _ @@ -48,7 +48,7 @@ class ChoiceOption(Option): """ if isinstance(values, FunctionType): validate_callback(values, values_params, 'values') - elif not isinstance(values, tuple): + elif not isinstance(values, tuple): # pragma: optional cover raise TypeError(_('values must be a tuple or a function for {0}' ).format(name)) self._extra = {'_choice_values': values, @@ -74,20 +74,20 @@ class ChoiceOption(Option): values = carry_out_calculation(self, config=context, callback=values, callback_params=values_params) - if not isinstance(values, list): + if not isinstance(values, list): # pragma: optional cover raise ConfigError(_('calculated values for {0} is not a list' '').format(self.impl_getname())) return values - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): try: values = self.impl_get_values(context) - if not value in values: + if not value in values: # pragma: optional cover raise ValueError(_('value {0} is not permitted, ' 'only {1} is allowed' '').format(value, values)) - except ContextError: + except ContextError: # pragma: optional cover log.debug('ChoiceOption validation, disabled because no context') @@ -95,39 +95,39 @@ class BoolOption(Option): "represents a choice between ``True`` and ``False``" __slots__ = tuple() - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): if not isinstance(value, bool): - raise ValueError(_('invalid boolean')) + raise ValueError(_('invalid boolean')) # pragma: optional cover class IntOption(Option): "represents a choice of an integer" __slots__ = tuple() - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): if not isinstance(value, int): - raise ValueError(_('invalid integer')) + raise ValueError(_('invalid integer')) # pragma: optional cover class FloatOption(Option): "represents a choice of a floating point number" __slots__ = tuple() - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): if not isinstance(value, float): - raise ValueError(_('invalid float')) + raise ValueError(_('invalid float')) # pragma: optional cover class StrOption(Option): "represents the choice of a string" __slots__ = tuple() - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): if not isinstance(value, str): - raise ValueError(_('invalid string')) + raise ValueError(_('invalid string')) # pragma: optional cover -if sys.version_info[0] >= 3: +if sys.version_info[0] >= 3: # pragma: optional cover #UnicodeOption is same as StrOption in python 3+ class UnicodeOption(StrOption): __slots__ = tuple() @@ -138,9 +138,9 @@ else: __slots__ = tuple() _empty = u'' - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): if not isinstance(value, unicode): - raise ValueError(_('invalid unicode')) + raise ValueError(_('invalid unicode')) # pragma: optional cover class IPOption(Option): @@ -165,31 +165,31 @@ class IPOption(Option): properties=properties, warnings_only=warnings_only) - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): # sometimes an ip term starts with a zero # but this does not fit in some case, for example bind does not like it try: for val in value.split('.'): if val.startswith("0") and len(val) > 1: - raise ValueError(_('invalid IP')) - except AttributeError: + raise ValueError(_('invalid IP')) # pragma: optional cover + except AttributeError: # pragma: optional cover #if integer for example raise ValueError(_('invalid IP')) # 'standard' validation try: IP('{0}/32'.format(value)) - except ValueError: + except ValueError: # pragma: optional cover raise ValueError(_('invalid IP')) def _second_level_validation(self, value, warnings_only): ip = IP('{0}/32'.format(value)) - if not self._extra['_allow_reserved'] and ip.iptype() == 'RESERVED': + if not self._extra['_allow_reserved'] and ip.iptype() == 'RESERVED': # pragma: optional cover if warnings_only: msg = _("IP is in reserved class") else: msg = _("invalid IP, mustn't be in reserved class") raise ValueError(msg) - if self._extra['_private_only'] and not ip.iptype() == 'PRIVATE': + if self._extra['_private_only'] and not ip.iptype() == 'PRIVATE': # pragma: optional cover if warnings_only: msg = _("IP is not in private class") else: @@ -198,11 +198,11 @@ class IPOption(Option): def _cons_in_network(self, opts, vals, warnings_only): if len(vals) != 3: - raise ConfigError(_('invalid len for vals')) + raise ConfigError(_('invalid len for vals')) # pragma: optional cover if None in vals: return ip, network, netmask = vals - if IP(ip) not in IP('{0}/{1}'.format(network, netmask)): + if IP(ip) not in IP('{0}/{1}'.format(network, netmask)): # pragma: optional cover if warnings_only: msg = _('IP {0} ({1}) not in network {2} ({3}) with netmask {4}' ' ({5})') @@ -247,12 +247,12 @@ class PortOption(Option): elif not allowed: is_finally = True elif allowed and is_finally: - raise ValueError(_('inconsistency in allowed range')) + raise ValueError(_('inconsistency in allowed range')) # pragma: optional cover if allowed: extra['_max_value'] = ports_max[index] if extra['_max_value'] is None: - raise ValueError(_('max value is empty')) + raise ValueError(_('max value is empty')) # pragma: optional cover self._extra = extra super(PortOption, self).__init__(name, doc, default=default, @@ -266,8 +266,8 @@ class PortOption(Option): properties=properties, warnings_only=warnings_only) - def _validate(self, value, context=None): - if self._extra['_allow_range'] and ":" in str(value): + def _validate(self, value, context=undefined): + if self._extra['_allow_range'] and ":" in str(value): # pragma: optional cover value = str(value).split(':') if len(value) != 2: raise ValueError(_('invalid port, range must have two values ' @@ -281,9 +281,9 @@ class PortOption(Option): for val in value: try: val = int(val) - except ValueError: + except ValueError: # pragma: optional cover raise ValueError(_('invalid port')) - if not self._extra['_min_value'] <= val <= self._extra['_max_value']: + if not self._extra['_min_value'] <= val <= self._extra['_max_value']: # pragma: optional cover raise ValueError(_('invalid port, must be an between {0} ' 'and {1}').format(self._extra['_min_value'], self._extra['_max_value'])) @@ -293,15 +293,15 @@ class NetworkOption(Option): "represents the choice of a network" __slots__ = tuple() - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): try: IP(value) - except ValueError: + except ValueError: # pragma: optional cover raise ValueError(_('invalid network address')) def _second_level_validation(self, value, warnings_only): ip = IP(value) - if ip.iptype() == 'RESERVED': + if ip.iptype() == 'RESERVED': # pragma: optional cover if warnings_only: msg = _("network address is in reserved class") else: @@ -313,10 +313,10 @@ class NetmaskOption(Option): "represents the choice of a netmask" __slots__ = tuple() - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): try: IP('0.0.0.0/{0}'.format(value)) - except ValueError: + except ValueError: # pragma: optional cover raise ValueError(_('invalid netmask address')) def _cons_network_netmask(self, opts, vals, warnings_only): @@ -334,7 +334,7 @@ class NetmaskOption(Option): def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net, warnings_only): if len(opts) != 2: - raise ConfigError(_('invalid len for opts')) + raise ConfigError(_('invalid len for opts')) # pragma: optional cover msg = None try: ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask), @@ -347,14 +347,14 @@ class NetmaskOption(Option): except ValueError: pass else: - if make_net: + if make_net: # pragma: optional cover msg = _("invalid IP {0} ({1}) with netmask {2}," " this IP is a network") - except ValueError: + except ValueError: # pragma: optional cover if not make_net: msg = _('invalid network {0} ({1}) with netmask {2}') - if msg is not None: + if msg is not None: # pragma: optional cover raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(), val_netmask)) @@ -362,15 +362,15 @@ class NetmaskOption(Option): class BroadcastOption(Option): __slots__ = tuple() - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): try: IP('{0}/32'.format(value)) - except ValueError: + except ValueError: # pragma: optional cover raise ValueError(_('invalid broadcast address')) def _cons_broadcast(self, opts, vals, warnings_only): if len(vals) != 3: - raise ConfigError(_('invalid len for vals')) + raise ConfigError(_('invalid len for vals')) # pragma: optional cover if None in vals: return broadcast, network, netmask = vals @@ -378,7 +378,7 @@ class BroadcastOption(Option): raise ValueError(_('invalid broadcast {0} ({1}) with network {2} ' '({3}) and netmask {4} ({5})').format( broadcast, opts[0].impl_getname(), network, - opts[1].impl_getname(), netmask, opts[2].impl_getname())) + opts[1].impl_getname(), netmask, opts[2].impl_getname())) # pragma: optional cover class DomainnameOption(Option): @@ -396,12 +396,12 @@ class DomainnameOption(Option): properties=None, allow_ip=False, type_='domainname', warnings_only=False, allow_without_dot=False): if type_ not in ['netbios', 'hostname', 'domainname']: - raise ValueError(_('unknown type_ {0} for hostname').format(type_)) + raise ValueError(_('unknown type_ {0} for hostname').format(type_)) # pragma: optional cover self._extra = {'_dom_type': type_} if allow_ip not in [True, False]: - raise ValueError(_('allow_ip must be a boolean')) + raise ValueError(_('allow_ip must be a boolean')) # pragma: optional cover if allow_without_dot not in [True, False]: - raise ValueError(_('allow_without_dot must be a boolean')) + raise ValueError(_('allow_without_dot must be a boolean')) # pragma: optional cover self._extra['_allow_ip'] = allow_ip self._extra['_allow_without_dot'] = allow_without_dot end = '' @@ -410,17 +410,17 @@ class DomainnameOption(Option): if self._extra['_dom_type'] != 'netbios': allow_number = '\d' else: - allow_number = '' + allow_number = '' # pragma: optional cover if self._extra['_dom_type'] == 'netbios': - length = 14 + length = 14 # pragma: optional cover elif self._extra['_dom_type'] == 'hostname': - length = 62 + length = 62 # pragma: optional cover elif self._extra['_dom_type'] == 'domainname': length = 62 if allow_without_dot is False: extrachar_mandatory = '\.' else: - extrachar = '\.' + extrachar = '\.' # pragma: optional cover end = '+[a-z]*' self._extra['_domain_re'] = re.compile(r'^(?:[a-z{0}][a-z\d\-{1}]{{,{2}}}{3}){4}$' ''.format(allow_number, extrachar, length, @@ -436,37 +436,37 @@ class DomainnameOption(Option): properties=properties, warnings_only=warnings_only) - def _validate(self, value, context=None): - if self._extra['_allow_ip'] is True: + def _validate(self, value, context=undefined): + if self._extra['_allow_ip'] is True: # pragma: optional cover try: IP('{0}/32'.format(value)) return except ValueError: pass if self._extra['_dom_type'] == 'domainname' and not self._extra['_allow_without_dot'] and \ - '.' not in value: + '.' not in value: # pragma: optional cover raise ValueError(_("invalid domainname, must have dot")) if len(value) > 255: - raise ValueError(_("invalid domainname's length (max 255)")) + raise ValueError(_("invalid domainname's length (max 255)")) # pragma: optional cover if len(value) < 2: - raise ValueError(_("invalid domainname's length (min 2)")) + raise ValueError(_("invalid domainname's length (min 2)")) # pragma: optional cover if not self._extra['_domain_re'].search(value): - raise ValueError(_('invalid domainname')) + raise ValueError(_('invalid domainname')) # pragma: optional cover class EmailOption(DomainnameOption): __slots__ = tuple() username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$") - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): splitted = value.split('@', 1) try: username, domain = splitted - except ValueError: + except ValueError: # pragma: optional cover raise ValueError(_('invalid email address, must contains one @' )) if not self.username_re.search(username): - raise ValueError(_('invalid username in email address')) + raise ValueError(_('invalid username in email address')) # pragma: optional cover super(EmailOption, self)._validate(domain) @@ -475,9 +475,9 @@ class URLOption(DomainnameOption): proto_re = re.compile(r'(http|https)://') path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$") - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): match = self.proto_re.search(value) - if not match: + if not match: # pragma: optional cover raise ValueError(_('invalid url, must start with http:// or ' 'https://')) value = value[len(match.group(0)):] @@ -493,17 +493,17 @@ class URLOption(DomainnameOption): try: domain, port = splitted - except ValueError: + except ValueError: # pragma: optional cover domain = splitted[0] port = 0 if not 0 <= int(port) <= 65535: raise ValueError(_('invalid url, port must be an between 0 and ' - '65536')) + '65536')) # pragma: optional cover # validate domainname super(URLOption, self)._validate(domain) # validate file if files is not None and files != '' and not self.path_re.search(files): - raise ValueError(_('invalid url, must ends with filename')) + raise ValueError(_('invalid url, must ends with filename')) # pragma: optional cover class UsernameOption(Option): @@ -511,17 +511,17 @@ class UsernameOption(Option): #regexp build with 'man 8 adduser' informations username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$") - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): match = self.username_re.search(value) if not match: - raise ValueError(_('invalid username')) + raise ValueError(_('invalid username')) # pragma: optional cover class FilenameOption(Option): __slots__ = tuple() path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$") - def _validate(self, value, context=None): + def _validate(self, value, context=undefined): match = self.path_re.search(value) if not match: - raise ValueError(_('invalid filename')) + raise ValueError(_('invalid filename')) # pragma: optional cover diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py index 29c1e43..f8f3f75 100644 --- a/tiramisu/option/optiondescription.py +++ b/tiramisu/option/optiondescription.py @@ -19,16 +19,21 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ from copy import copy +import re + from tiramisu.i18n import _ -from tiramisu.setting import groups # , log -from .baseoption import BaseOption +from tiramisu.setting import groups, undefined # , log +from .baseoption import BaseOption, DynSymLinkOption, SymLinkOption, \ + allowed_character from . import MasterSlaves from tiramisu.error import ConfigError, ConflictError, ValueWarning from tiramisu.storage import get_storages_option +from tiramisu.autolib import carry_out_calculation StorageOptionDescription = get_storages_option('optiondescription') +name_regexp = re.compile(r'^{0}*$'.format(allowed_character)) class OptionDescription(BaseOption, StorageOptionDescription): @@ -42,16 +47,31 @@ class OptionDescription(BaseOption, StorageOptionDescription): :param children: a list of options (including optiondescriptions) """ - super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties) - child_names = [child.impl_getname() for child in children] + super(OptionDescription, self).__init__(name, doc=doc, + requires=requires, + properties=properties, + callback=False) + child_names = [] + dynopt_names = [] + for child in children: + name = child.impl_getname() + child_names.append(name) + if isinstance(child, DynOptionDescription): + dynopt_names.append(name) + #better performance like this valid_child = copy(child_names) valid_child.sort() old = None for child in valid_child: - if child == old: + if child == old: # pragma: optional cover raise ConflictError(_('duplicate option name: ' '{0}').format(child)) + if dynopt_names: + for dynopt in dynopt_names: + if child != dynopt and child.startswith(dynopt): + raise ConflictError(_('option must not start as ' + 'dynoptiondescription')) old = child self._add_children(child_names, children) self._cache_paths = None @@ -74,19 +94,7 @@ class OptionDescription(BaseOption, StorageOptionDescription): """returns a list of all paths in self, recursively _currpath should not be provided (helps with recursion) """ - if _currpath is None: - _currpath = [] - paths = [] - for option in self.impl_getchildren(): - attr = option.impl_getname() - if isinstance(option, OptionDescription): - if include_groups: - paths.append('.'.join(_currpath + [attr])) - paths += option.impl_getpaths(include_groups=include_groups, - _currpath=_currpath + [attr]) - else: - paths.append('.'.join(_currpath + [attr])) - return paths + return _impl_getpaths(self, include_groups, _currpath) def impl_build_cache_consistency(self, _consistencies=None, cache_option=None): #FIXME cache_option ! @@ -96,7 +104,7 @@ class OptionDescription(BaseOption, StorageOptionDescription): cache_option = [] else: init = False - for option in self.impl_getchildren(): + for option in self._impl_getchildren(dyn=False): cache_option.append(option._get_id()) if not isinstance(option, OptionDescription): for func, all_cons_opts, params in option._get_consistencies(): @@ -111,7 +119,7 @@ class OptionDescription(BaseOption, StorageOptionDescription): self._cache_consistencies = {} for opt, cons in _consistencies.items(): #FIXME dans le cache ... - if opt._get_id() not in cache_option: + if opt._get_id() not in cache_option: # pragma: optional cover raise ConfigError(_('consistency with option {0} ' 'which is not in Config').format( opt.impl_getname())) @@ -125,13 +133,13 @@ class OptionDescription(BaseOption, StorageOptionDescription): cache_option = [] else: init = False - for option in self.impl_getchildren(): + for option in self._impl_getchildren(dyn=False): #FIXME specifique id for sqlalchemy? #FIXME avec sqlalchemy ca marche le multi parent ? (dans des configs différentes) #if option.id is None: # raise SystemError(_("an option's id should not be None " # "for {0}").format(option.impl_getname())) - if option._get_id() in cache_option: + if option._get_id() in cache_option: # pragma: optional cover raise ConflictError(_('duplicate option: {0}').format(option)) cache_option.append(option._get_id()) option._readonly = True @@ -147,7 +155,7 @@ class OptionDescription(BaseOption, StorageOptionDescription): :param group_type: an instance of `GroupType` or `MasterGroupType` that lives in `setting.groups` """ - if self._group_type != groups.default: + if self._group_type != groups.default: # pragma: optional cover raise TypeError(_('cannot change group_type if already set ' '(old {0}, new {1})').format(self._group_type, group_type)) @@ -155,7 +163,7 @@ class OptionDescription(BaseOption, StorageOptionDescription): self._group_type = group_type if isinstance(group_type, groups.MasterGroupType): MasterSlaves(self.impl_getname(), self.impl_getchildren()) - else: + else: # pragma: optional cover raise ValueError(_('group_type: {0}' ' not allowed').format(group_type)) @@ -166,18 +174,29 @@ class OptionDescription(BaseOption, StorageOptionDescription): if self._cache_consistencies is None: return True #consistencies is something like [('_cons_not_equal', (opt1, opt2))] - consistencies = self._cache_consistencies.get(option) + if isinstance(option, DynSymLinkOption): + consistencies = self._cache_consistencies.get(option._opt) + else: + consistencies = self._cache_consistencies.get(option) if consistencies is not None: for func, all_cons_opts, params in consistencies: warnings_only = params.get('warnings_only', False) #all_cons_opts[0] is the option where func is set + if isinstance(option, DynSymLinkOption): + subpath = '.'.join(option._dyn.split('.')[:-1]) + namelen = len(option._opt.impl_getname()) + suffix = option.impl_getname()[namelen:] + opts = [] + for opt in all_cons_opts: + name = opt.impl_getname() + suffix + path = subpath + '.' + name + opts.append(opt._impl_to_dyn(name, path)) + else: + opts = all_cons_opts try: - all_cons_opts[0]._launch_consistency(func, option, - value, - context, index, - submulti_idx, - all_cons_opts, - warnings_only) + opts[0]._launch_consistency(func, option, value, context, + index, submulti_idx, opts, + warnings_only) except ValueError as err: if warnings_only: raise ValueWarning(err.message, option) @@ -189,12 +208,13 @@ class OptionDescription(BaseOption, StorageOptionDescription): :param descr: parent :class:`tiramisu.option.OptionDescription` """ if descr is None: - self.impl_build_cache_consistency() + #FIXME faut le desactiver ? + #self.impl_build_cache_consistency() self.impl_build_cache_option() descr = self super(OptionDescription, self)._impl_getstate(descr) self._state_group_type = str(self._group_type) - for option in self.impl_getchildren(): + for option in self._impl_getchildren(): option._impl_getstate(descr) def __getstate__(self): @@ -224,9 +244,12 @@ class OptionDescription(BaseOption, StorageOptionDescription): self.impl_build_cache_option() descr = self self._group_type = getattr(groups, self._state_group_type) + if isinstance(self._group_type, groups.MasterGroupType): + MasterSlaves(self.impl_getname(), self.impl_getchildren(), + validate=False) del(self._state_group_type) super(OptionDescription, self)._impl_setstate(descr) - for option in self.impl_getchildren(): + for option in self._impl_getchildren(dyn=False): option._impl_setstate(descr) def __setstate__(self, state): @@ -235,3 +258,129 @@ class OptionDescription(BaseOption, StorageOptionDescription): self._stated except AttributeError: self._impl_setstate() + + def _impl_get_suffixes(self, context): + callback, callback_params = self.impl_get_callback() + if callback_params is None: + callback_params = {} + values = carry_out_calculation(self, config=context, + callback=callback, + callback_params=callback_params) + if len(values) > len(set(values)): + raise ConfigError(_('DynOptionDescription callback return not uniq value')) + for val in values: + if not isinstance(val, str) or re.match(name_regexp, val) is None: + raise ValueError(_("invalid suffix: {0} for option").format(val)) + return values + + def _impl_search_dynchild(self, name=undefined, context=undefined): + ret = [] + for child in self._impl_st_getchildren(): + cname = child.impl_getname() + if isinstance(child, DynOptionDescription) and \ + (name is undefined or name.startswith(cname)): + path = cname + for value in child._impl_get_suffixes(context): + if name is undefined: + ret.append(SynDynOptionDescription(child, cname + value, path + value, value)) + elif name == cname + value: + return SynDynOptionDescription(child, name, path + value, value) + return ret + + def _impl_get_dynchild(self, child, suffix): + name = child.impl_getname() + suffix + path = self._name + suffix + '.' + name + if isinstance(child, OptionDescription): + return SynDynOptionDescription(child, name, path, suffix) + else: + return child._impl_to_dyn(name, path) + + def _impl_getchildren(self, dyn=True, context=undefined): + for child in self._impl_st_getchildren(): + cname = child._name + if dyn and child.impl_is_dynoptiondescription(): + path = cname + for value in child._impl_get_suffixes(context): + yield SynDynOptionDescription(child, + cname + value, + path + value, value) + else: + yield child + + def impl_getchildren(self): + return list(self._impl_getchildren()) + + +class DynOptionDescription(OptionDescription): + def __init__(self, name, doc, children, requires=None, properties=None, + callback=None, callback_params=None): + for child in children: + if isinstance(child, OptionDescription): + if child.impl_get_group_type() != groups.master: + raise ConfigError(_('cannot set optiondescription in an ' + 'dynoptiondescription')) + for chld in child._impl_getchildren(): + chld._subdyn = self + if isinstance(child, SymLinkOption): + raise ConfigError(_('cannot set symlinkoption in an ' + 'dynoptiondescription')) + child._subdyn = self + super(DynOptionDescription, self).__init__(name, doc, children, + requires, properties) + self.impl_set_callback(callback, callback_params) + + def _validate_callback(self, callback, callback_params): + if callback is None: + raise ConfigError(_('callback is mandatory for dynoptiondescription')) + + +class SynDynOptionDescription(object): + __slots__ = ('_opt', '_name', '_path', '_suffix') + + def __init__(self, opt, name, path, suffix): + self._opt = opt + self._name = name + self._path = path + self._suffix = suffix + + def __getattr__(self, name, context=undefined): + if name in dir(self._opt): + return getattr(self._opt, name) + return self._opt._getattr(name, self._name, self._suffix, context) + + def impl_getname(self): + return self._name + + def _impl_getchildren(self, dyn=True, context=undefined): + children = [] + for child in self._opt._impl_getchildren(): + children.append(self._opt._impl_get_dynchild(child, self._suffix)) + return children + + def impl_getchildren(self): + return self._impl_getchildren() + + def impl_getpath(self, context): + return self._path + + def impl_getpaths(self, include_groups=False, _currpath=None): + return _impl_getpaths(self, include_groups, _currpath) + + +def _impl_getpaths(klass, include_groups, _currpath): + """returns a list of all paths in klass, recursively + _currpath should not be provided (helps with recursion) + """ + if _currpath is None: + _currpath = [] + paths = [] + for option in klass._impl_getchildren(): + attr = option.impl_getname() + if option.impl_is_optiondescription(): + if include_groups: + paths.append('.'.join(_currpath + [attr])) + paths += option.impl_getpaths(include_groups=include_groups, + _currpath=_currpath + [attr]) + else: + paths.append('.'.join(_currpath + [attr])) + return paths diff --git a/tiramisu/setting.py b/tiramisu/setting.py index fa29b07..e5871ad 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -114,11 +114,11 @@ class _NameSpace(object): """ def __setattr__(self, name, value): - if name in self.__dict__: + if name in self.__dict__: # pragma: optional cover raise ConstError(_("can't rebind {0}").format(name)) self.__dict__[name] = value - def __delattr__(self, name): + def __delattr__(self, name): # pragma: optional cover if name in self.__dict__: raise ConstError(_("can't unbind {0}").format(name)) raise ValueError(name) @@ -248,7 +248,7 @@ class Property(object): :type propname: string """ if self._opt is not None and self._opt.impl_getrequires() is not None \ - and propname in self._opt._calc_properties: + and propname in self._opt._calc_properties: # pragma: optional cover raise ValueError(_('cannot append {0} property for option {1}: ' 'this property is calculated').format( propname, self._opt.impl_getname())) @@ -315,7 +315,7 @@ class Settings(object): old `SubConfig`, `Values`, `Multi` or `Settings`) """ context = self.context() - if context is None: + if context is None: # pragma: optional cover raise ConfigError(_('the context does not exist anymore')) return context @@ -329,24 +329,24 @@ class Settings(object): return str(list(self._getproperties())) def __getitem__(self, opt): - path = self._get_path_by_opt(opt) + path = opt.impl_getpath(self._getcontext()) return self._getitem(opt, path) def _getitem(self, opt, path): return Property(self, self._getproperties(opt, path), opt, path) - def __setitem__(self, opt, value): + def __setitem__(self, opt, value): # pragma: optional cover raise ValueError('you should only append/remove properties') def reset(self, opt=None, _path=None, all_properties=False): - if all_properties and (_path or opt): + if all_properties and (_path or opt): # pragma: optional cover raise ValueError(_('opt and all_properties must not be set ' 'together in reset')) if all_properties: self._p_.reset_all_properties() else: if opt is not None and _path is None: - _path = self._get_path_by_opt(opt) + _path = opt.impl_getpath(self._getcontext()) self._p_.reset_properties(_path) self._getcontext().cfgimpl_reset_cache() @@ -357,7 +357,7 @@ class Settings(object): if opt is None: props = copy(self._p_.getproperties(path, default_properties)) else: - if path is None: + if path is None: # pragma: optional cover raise ValueError(_('if opt is not None, path should not be' ' None in _getproperties')) ntime = None @@ -491,15 +491,15 @@ class Settings(object): instead of passing a :class:`tiramisu.option.Option()` object. """ if opt is not None and path is None: - path = self._get_path_by_opt(opt) - if not isinstance(permissive, tuple): + path = opt.impl_getpath(self._getcontext()) + if not isinstance(permissive, tuple): # pragma: optional cover raise TypeError(_('permissive must be a tuple')) self._p_.setpermissive(path, permissive) #____________________________________________________________ def setowner(self, owner): ":param owner: sets the default value for owner at the Config level" - if not isinstance(owner, owners.Owner): + if not isinstance(owner, owners.Owner): # pragma: optional cover raise TypeError(_("invalid generic owner {0}").format(str(owner))) self._owner = owner @@ -586,8 +586,8 @@ class Settings(object): for require in requires: option, expected, action, inverse, \ transitive, same_action = require - reqpath = self._get_path_by_opt(option) - if reqpath == path or reqpath.startswith(path + '.'): + reqpath = option.impl_getpath(context) + if reqpath == path or reqpath.startswith(path + '.'): # pragma: optional cover raise RequirementError(_("malformed requirements " "imbrication detected for option:" " '{0}' with requirement on: " @@ -598,7 +598,7 @@ class Settings(object): if not transitive: continue properties = err.proptype - if same_action and action not in properties: + if same_action and action not in properties: # pragma: optional cover raise RequirementError(_("option '{0}' has " "requirement's property " "error: " @@ -616,14 +616,6 @@ class Settings(object): break return calc_properties - def _get_path_by_opt(self, opt): - """just a wrapper to get path in optiondescription's cache - - :param opt: `Option`'s object - :returns: path - """ - return self._getcontext().cfgimpl_get_description().impl_get_path_by_opt(opt) - def get_modified_properties(self): return self._p_.get_modified_properties() diff --git a/tiramisu/storage/__init__.py b/tiramisu/storage/__init__.py index 4763bad..3cba5c6 100644 --- a/tiramisu/storage/__init__.py +++ b/tiramisu/storage/__init__.py @@ -40,7 +40,7 @@ class StorageType(object): storage_type = None mod = None - def set(self, name): + def set(self, name): # pragma: optional cover if self.storage_type is not None: if self.storage_type == name: return @@ -63,7 +63,7 @@ storage_type = StorageType() storage_option_type = StorageType() -def set_storage(type_, name, **kwargs): +def set_storage(type_, name, **kwargs): # pragma: optional cover """Change storage's configuration :params name: is the storage name. If storage is already set, cannot @@ -95,7 +95,7 @@ def _impl_getstate_setting(): return state -def get_storage(type_, session_id, persistent, test): +def get_storage(type_, session_id, persistent, test): # pragma: optional cover """all used when __setstate__ a Config """ if type_ == 'option': @@ -123,7 +123,7 @@ def get_storages_option(type_): return imp.OptionDescription -def list_sessions(type_): +def list_sessions(type_): # pragma: optional cover """List all available session (persistent or not persistent) """ if type_ == 'option': @@ -132,7 +132,7 @@ def list_sessions(type_): return storage_type.get().list_sessions() -def delete_session(type_, session_id): +def delete_session(type_, session_id): # pragma: optional cover """Delete a selected session, be careful, you can deleted a session use by an other instance :params session_id: id of session to delete diff --git a/tiramisu/storage/dictionary/option.py b/tiramisu/storage/dictionary/option.py index f7a7f92..38a739d 100644 --- a/tiramisu/storage/dictionary/option.py +++ b/tiramisu/storage/dictionary/option.py @@ -18,7 +18,8 @@ # # ____________________________________________________________ from tiramisu.i18n import _ -from tiramisu.setting import groups +from tiramisu.setting import groups, undefined +from tiramisu.error import ConfigError #____________________________________________________________ @@ -32,9 +33,14 @@ class Base(object): '_default', '_default_multi', '_state_callback', '_callback', '_state_callback_params', '_callback_params', '_multitype', '_consistencies', '_warnings_only', '_master_slaves', - '_state_consistencies', '_extra', '__weakref__') + '_state_consistencies', '_extra', '_subdyn', '__weakref__', + '_state_master_slaves') def __init__(self): + try: + self._subdyn + except AttributeError: + self._subdyn = False try: self._consistencies except AttributeError: @@ -65,6 +71,12 @@ class Base(object): def _get_id(self): return id(self) + def _is_subdyn(self): + try: + return self._subdyn is not False + except AttributeError: + return False + class OptionDescription(Base): __slots__ = ('_children', '_cache_paths', '_cache_consistencies', @@ -82,20 +94,21 @@ class OptionDescription(Base): def impl_get_opt_by_path(self, path): try: return self._cache_paths[0][self._cache_paths[1].index(path)] - except ValueError: + except ValueError: # pragma: optional cover raise AttributeError(_('no option for path {0}').format(path)) def impl_get_path_by_opt(self, opt): try: return self._cache_paths[1][self._cache_paths[0].index(opt)] - except ValueError: + except ValueError: # pragma: optional cover raise AttributeError(_('no option {0} found').format(opt)) - def impl_get_group_type(self): + def impl_get_group_type(self): # pragma: optional cover return getattr(groups, self._group_type) - def impl_build_cache_option(self, _currpath=None, cache_path=None, cache_option=None): - if _currpath is None and self._cache_paths is not None: + def impl_build_cache_option(self, _currpath=None, cache_path=None, + cache_option=None): + if _currpath is None and self._cache_paths is not None: # pragma: optional cover # cache already set return if _currpath is None: @@ -106,11 +119,12 @@ class OptionDescription(Base): if cache_path is None: cache_path = [] cache_option = [] - for option in self.impl_getchildren(): + for option in self._impl_getchildren(dyn=False): attr = option._name + path = str('.'.join(_currpath + [attr])) cache_option.append(option) - cache_path.append(str('.'.join(_currpath + [attr]))) - if option.__class__.__name__ == 'OptionDescription': + cache_path.append(path) + if option.impl_is_optiondescription(): _currpath.append(attr) option.impl_build_cache_option(_currpath, cache_path, cache_option) @@ -118,52 +132,141 @@ class OptionDescription(Base): if save: self._cache_paths = (tuple(cache_option), tuple(cache_path)) - def impl_get_options_paths(self, bytype, byname, _subpath, only_first): - def _filter_by_name(): - if byname is None or path == byname or \ - path.endswith('.' + byname): - return True - return False - - def _filter_by_type(): - if bytype is None: - return True - if isinstance(option, bytype): - return True - return False - + def impl_get_options_paths(self, bytype, byname, _subpath, only_first, context): find_results = [] + + def _rebuild_dynpath(path, suffix, dynopt): + found = False + spath = path.split('.') + for length in xrange(1, len(spath)): + subpath = '.'.join(spath[0:length]) + subopt = self.impl_get_opt_by_path(subpath) + if dynopt == subopt: + found = True + break + if not found: + #FIXME + raise ConfigError(_('hu?')) + subpath = subpath + suffix + for slength in xrange(length, len(spath)): + subpath = subpath + '.' + spath[slength] + suffix + return subpath + + def _filter_by_name(path, option): + name = option.impl_getname() + if option._is_subdyn(): + if byname.startswith(name): + found = False + for suffix in option._subdyn._impl_get_suffixes( + context): + if byname == name + suffix: + found = True + path = _rebuild_dynpath(path, suffix, + option._subdyn) + option = option._impl_to_dyn( + name + suffix, path) + break + if not found: + return False + else: + if not byname == name: + return False + find_results.append((path, option)) + return True + + def _filter_by_type(path, option): + if isinstance(option, bytype): + #if byname is not None, check option byname in _filter_by_name + #not here + if byname is None: + if option._is_subdyn(): + name = option.impl_getname() + for suffix in option._subdyn._impl_get_suffixes( + context): + spath = _rebuild_dynpath(path, suffix, + option._subdyn) + find_results.append((spath, option._impl_to_dyn( + name + suffix, spath))) + else: + find_results.append((path, option)) + return True + return False + + def _filter(path, option): + if bytype is not None: + retval = _filter_by_type(path, option) + if byname is None: + return retval + if byname is not None: + return _filter_by_name(path, option) + opts, paths = self._cache_paths for index in range(0, len(paths)): option = opts[index] - if option.__class__.__name__ == 'OptionDescription': + if option.impl_is_optiondescription(): continue path = paths[index] if _subpath is not None and not path.startswith(_subpath + '.'): continue - if not _filter_by_name(): - continue - if not _filter_by_type(): - continue - retval = (path, option) + if bytype == byname is None: + if option._is_subdyn(): + name = option.impl_getname() + for suffix in option._subdyn._impl_get_suffixes( + context): + spath = _rebuild_dynpath(path, suffix, + option._subdyn) + find_results.append((spath, option._impl_to_dyn( + name + suffix, spath))) + else: + find_results.append((path, option)) + else: + if _filter(path, option) is False: + continue if only_first: - return retval - find_results.append(retval) + return find_results[0] return find_results - def impl_getchildren(self): + def _impl_st_getchildren(self): return self._children[1] - def __getattr__(self, name): + def __getattr__(self, name, context=undefined): if name == '_name': return object.__getattribute__(self, name) - try: - if name == '_readonly': - raise AttributeError("{0} instance has no attribute " - "'_readonly'".format( - self.__class__.__name__)) - return self._children[1][self._children[0].index(name)] - except ValueError: + return self._getattr(name, context=context) + + def _getattr(self, name, dyn_od=undefined, suffix=undefined, + context=undefined, dyn=True): + error = False + if suffix is not undefined: + try: + if undefined in [dyn_od, suffix, context]: # pragma: optional cover + raise ConfigError(_("dyn_od, suffix and context needed if " + "it's a dyn option")) + if name.endswith(suffix): + oname = name[:-len(suffix)] + child = self._children[1][self._children[0].index(oname)] + return self._impl_get_dynchild(child, suffix) + else: + error = True + except ValueError: # pragma: optional cover + error = True + else: + try: # pragma: optional cover + if name == '_readonly': + raise AttributeError(_("{0} instance has no attribute " + "'_readonly'").format( + self.__class__.__name__)) + child = self._children[1][self._children[0].index(name)] + if dyn and child.impl_is_dynoptiondescription(): + error = True + else: + return child + except ValueError: + child = self._impl_search_dynchild(name, context=context) + if child != []: + return child + error = True + if error: raise AttributeError(_('unknown Option {0} ' 'in OptionDescription {1}' '').format(name, self._name)) diff --git a/tiramisu/storage/dictionary/storage.py b/tiramisu/storage/dictionary/storage.py index 702ded4..252d29e 100644 --- a/tiramisu/storage/dictionary/storage.py +++ b/tiramisu/storage/dictionary/storage.py @@ -29,11 +29,11 @@ setting = Setting() _list_sessions = [] -def list_sessions(): +def list_sessions(): # pragma: optional cover return _list_sessions -def delete_session(session_id): +def delete_session(session_id): # pragma: optional cover raise ConfigError(_('dictionary storage cannot delete session')) @@ -44,9 +44,9 @@ class Storage(object): serializable = True def __init__(self, session_id, persistent, test=False): - if not test and session_id in _list_sessions: + if not test and session_id in _list_sessions: # pragma: optional cover raise ValueError(_('session already used')) - if persistent: + if persistent: # pragma: optional cover raise ValueError(_('a dictionary cannot be persistent')) self.session_id = session_id self.persistent = persistent @@ -55,5 +55,5 @@ class Storage(object): def __del__(self): try: _list_sessions.remove(self.session_id) - except AttributeError: + except AttributeError: # pragma: optional cover pass diff --git a/tiramisu/storage/dictionary/value.py b/tiramisu/storage/dictionary/value.py index bf3196c..2876d72 100644 --- a/tiramisu/storage/dictionary/value.py +++ b/tiramisu/storage/dictionary/value.py @@ -88,5 +88,5 @@ class Values(Cache): """ if key in self._informations: return self._informations[key] - else: + else: # pragma: optional cover raise ValueError("not found") diff --git a/tiramisu/value.py b/tiramisu/value.py index b208e9e..23dbaae 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "takes care of the option's values and multi values" -# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2014 Team tiramisu (see AUTHORS for all contributors) # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the @@ -22,7 +22,7 @@ from tiramisu.error import ConfigError, SlaveError, PropertiesOptionError from tiramisu.setting import owners, expires_time, undefined from tiramisu.autolib import carry_out_calculation from tiramisu.i18n import _ -from tiramisu.option import SymLinkOption, OptionDescription +from tiramisu.option import SymLinkOption, DynSymLinkOption class Values(object): @@ -50,7 +50,7 @@ class Values(object): old `SubConfig`, `Values`, `Multi` or `Settings`) """ context = self.context() - if context is None: + if context is None: # pragma: optional cover raise ConfigError(_('the context does not exist anymore')) return context @@ -60,9 +60,11 @@ class Values(object): :param opt: the `option.Option()` object :returns: the option's value (or the default value if not set) """ + if opt.impl_is_optiondescription(): # pragma: optional cover + raise ValueError(_('optiondescription has no value')) setting = self._getcontext().cfgimpl_get_settings() - force_default = 'frozen' in setting[opt] and \ - 'force_default_on_freeze' in setting[opt] + force_default = 'frozen' in setting._getitem(opt, path) and \ + 'force_default_on_freeze' in setting._getitem(opt, path) if not is_default and not force_default: value = self._p_.getvalue(path) if index is not undefined: @@ -101,7 +103,7 @@ class Values(object): #FIXME : problème de longueur si meta + slave #doit passer de meta à pas meta #en plus il faut gérer la longueur avec les meta ! - #FIXME SymlinkOption + #FIXME SymLinkOption value = meta.cfgimpl_get_values()[opt] if isinstance(value, Multi): if index is not undefined: @@ -136,7 +138,7 @@ class Values(object): :param opt: the `option.Option()` object """ - path = self._get_opt_path(opt) + path = opt.impl_getpath(self._getcontext()) return self._contains(path) def _contains(self, path): @@ -148,7 +150,7 @@ class Values(object): def reset(self, opt, path=None): if path is None: - path = self._get_opt_path(opt) + path = opt.impl_getpath(self._getcontext()) if self._p_.hasvalue(path): context = self._getcontext() setting = context.cfgimpl_get_settings() @@ -156,7 +158,7 @@ class Values(object): context, 'validator' in setting) context.cfgimpl_reset_cache() if opt.impl_is_master_slaves('master'): - opt.impl_get_master_slaves().reset(self) + opt.impl_get_master_slaves().reset(opt, self) self._p_.resetvalue(path) def _isempty(self, opt, value): @@ -186,7 +188,7 @@ class Values(object): force_permissive=False, force_properties=None, validate_properties=True): if path is None: - path = self._get_opt_path(opt) + path = opt.impl_getpath(self._getcontext()) ntime = None setting = self._getcontext().cfgimpl_get_settings() if 'cache' in setting and self._p_.hascache(path): @@ -275,13 +277,13 @@ class Values(object): force_submulti_index = None else: force_submulti_index = submulti_index - opt.impl_validate(value, context, 'validator' in setting, force_index=force_index, force_submulti_index=force_submulti_index) #FIXME pas de test avec les metas ... #FIXME et les symlinkoption ... - if is_default and 'force_store_value' in setting[opt]: + if is_default and 'force_store_value' in setting._getitem(opt, + path): if isinstance(value, Multi): item = list(value) else: @@ -297,7 +299,7 @@ class Values(object): raise config_error return value - def __setitem__(self, opt, value): + def __setitem__(self, opt, value): # pragma: optional cover raise ValueError(_('you should only set value with config')) def setitem(self, opt, value, path, force_permissive=False, @@ -342,9 +344,10 @@ class Values(object): was present :returns: a `setting.owners.Owner` object """ - if isinstance(opt, SymLinkOption): + if isinstance(opt, SymLinkOption) and \ + not isinstance(opt, DynSymLinkOption): opt = opt._opt - path = self._get_opt_path(opt) + path = opt.impl_getpath(self._getcontext()) return self._getowner(opt, path, force_permissive=force_permissive) def _getowner(self, opt, path, validate_properties=True, @@ -365,14 +368,14 @@ class Values(object): :param opt: the `option.Option` object :param owner: a valid owner, that is a `setting.owners.Owner` object """ - if not isinstance(owner, owners.Owner): + if not isinstance(owner, owners.Owner): # pragma: optional cover raise TypeError(_("invalid generic owner {0}").format(str(owner))) - path = self._get_opt_path(opt) + path = opt.impl_getpath(self._getcontext()) self._setowner(opt, path, owner) def _setowner(self, opt, path, owner): - if self._getowner(opt, path) == owners.default: + if self._getowner(opt, path) == owners.default: # pragma: optional cover raise ConfigError(_('no value for {0} cannot change owner to {1}' '').format(path, owner)) self._p_.setowner(path, owner) @@ -384,7 +387,7 @@ class Values(object): (not the toplevel config) :return: boolean """ - path = self._get_opt_path(opt) + path = opt.impl_getpath(self._getcontext()) return self._is_default_owner(opt, path, validate_properties=validate_properties, validate_meta=validate_meta) @@ -403,16 +406,6 @@ class Values(object): else: self._p_.reset_all_cache() - def _get_opt_path(self, opt): - """ - retrieve the option's path in the config - - :param opt: the `option.Option` object - :returns: a string with points like "gc.dummy.my_option" - """ - return self._getcontext().cfgimpl_get_description( - ).impl_get_path_by_opt(opt) - # information def set_information(self, key, value): """updates the information's attribute @@ -429,7 +422,7 @@ class Values(object): """ try: return self._p_.get_information(key) - except ValueError: + except ValueError: # pragma: optional cover if default is not undefined: return default else: @@ -445,22 +438,26 @@ class Values(object): """ def _mandatory_warnings(description): #if value in cache, properties are not calculated - for opt in description.impl_getchildren(): - if isinstance(opt, OptionDescription): - _mandatory_warnings(opt) - elif isinstance(opt, SymLinkOption): + _ret = [] + for opt in description._impl_getchildren( + context=self._getcontext()): + if opt.impl_is_optiondescription(): + _ret.extend(_mandatory_warnings(opt)) + elif isinstance(opt, SymLinkOption) and \ + not isinstance(opt, DynSymLinkOption): pass else: - path = self._get_opt_path(opt) + path = opt.impl_getpath(self._getcontext()) try: self._get_cached_item(opt, path=path, force_properties=frozenset(('mandatory',))) except PropertiesOptionError as err: if err.proptype == ['mandatory']: - yield path + _ret.append(path) + return _ret self.reset_cache(False) descr = self._getcontext().cfgimpl_get_description() - ret = list(_mandatory_warnings(descr)) + ret = _mandatory_warnings(descr) self.reset_cache(False) return ret @@ -507,12 +504,12 @@ class Multi(list): """ if value is None: value = [] - if not opt.impl_is_submulti() and isinstance(value, Multi): + if not opt.impl_is_submulti() and isinstance(value, Multi): # pragma: optional cover raise ValueError(_('{0} is already a Multi ').format( opt.impl_getname())) self.opt = opt self.path = path - if not isinstance(context, weakref.ReferenceType): + if not isinstance(context, weakref.ReferenceType): # pragma: optional cover raise ValueError('context must be a Weakref') self.context = context if not isinstance(value, list): @@ -542,7 +539,7 @@ class Multi(list): old `SubConfig`, `Values`, `Multi` or `Settings`) """ context = self.context() - if context is None: + if context is None: # pragma: optional cover raise ConfigError(_('the context does not exist anymore')) return context @@ -574,7 +571,7 @@ class Multi(list): only if the option is a master """ if not force: - if self.opt.impl_is_master_slaves('slave'): + if self.opt.impl_is_master_slaves('slave'): # pragma: optional cover raise SlaveError(_("cannot append a value on a multi option {0}" " which is a slave").format(self.opt.impl_getname())) index = self.__len__() @@ -650,12 +647,12 @@ class Multi(list): """ context = self._getcontext() if not force: - if self.opt.impl_is_master_slaves('slave'): + if self.opt.impl_is_master_slaves('slave'): # pragma: optional cover raise SlaveError(_("cannot pop a value on a multi option {0}" " which is a slave").format(self.opt.impl_getname())) if self.opt.impl_is_master_slaves('master'): - self.opt.impl_get_master_slaves().pop( - context.cfgimpl_get_values(), index) + self.opt.impl_get_master_slaves().pop(self.opt, + context.cfgimpl_get_values(), index) #set value without valid properties ret = super(Multi, self).pop(index) self._store(force)