diff --git a/ChangeLog b/ChangeLog index e89d792..4794942 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +XXXXXXXXXXXXX Emmanuel Garette + + * add SubMulti: + a SubMulti is a multi in a multi variable + Sat Apr 12 11:37:27 CEST 2014 Emmanuel Garette * behavior change in master/slave part of code: diff --git a/test/test_dereference.py b/test/test_dereference.py index 0e59f36..c39bfcb 100644 --- a/test/test_dereference.py +++ b/test/test_dereference.py @@ -3,7 +3,7 @@ import autopath #from py.test import raises from tiramisu.config import Config, GroupConfig, MetaConfig -from tiramisu.option import BoolOption, IntOption, OptionDescription +from tiramisu.option import BoolOption, IntOption, StrOption, OptionDescription, submulti import weakref @@ -137,3 +137,21 @@ def test_deref_metaconfig(): assert w() is not None del(meta) assert w() is None + + +def test_deref_submulti(): + multi = StrOption('multi', '', multi=submulti) + od = OptionDescription('od', '', [multi]) + cfg = Config(od) + cfg.cfgimpl_get_settings().remove('cache') + w = weakref.ref(cfg.multi) + assert w() is None + cfg.multi.append([]) + w = weakref.ref(cfg.multi) + assert w() is None + m = cfg.multi + w = weakref.ref(m) + z = weakref.ref(w()[0]) + del(m) + assert w() is None + assert z() is None diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index 3199308..881d661 100644 --- a/test/test_option_calculation.py +++ b/test/test_option_calculation.py @@ -438,8 +438,9 @@ def test_callback_multi_callback(): cfg = Config(maconfig) cfg.read_write() assert cfg.val1.val1 == ['val'] + cfg.val1.val1 = ['val1'] cfg.val1.val1.append() - assert cfg.val1.val1 == ['val', 'val'] + assert cfg.val1.val1 == ['val1', 'val'] def test_callback_master_and_slaves_master(): diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index 46bd82d..3c3175d 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -355,6 +355,7 @@ def test_multi_insert(): c.var.insert(0, 'nok') assert c.var == ['nok', 'ok'] assert c.getowner(var) != owners.default + raises(ValueError, 'c.var.insert(0, 1)') def test_multi_insert_master(): @@ -427,6 +428,7 @@ def test_multi_extend(): c.var.extend(['pok']) assert c.var == ['ok', 'nok', 'pok'] assert c.getowner(var) != owners.default + raises(ValueError, 'c.var.extend([1])') def test_multi_extend_master(): diff --git a/test/test_submulti.py b/test/test_submulti.py new file mode 100644 index 0000000..6b5108f --- /dev/null +++ b/test/test_submulti.py @@ -0,0 +1,639 @@ +# coding: utf-8 +import autopath +from tiramisu.setting import groups, owners +from tiramisu.config import Config +from tiramisu.option import StrOption, OptionDescription, submulti +from tiramisu.value import SubMulti, Multi +from tiramisu.error import SlaveError + +from py.test import raises + + +def return_val(): + return 'val' + + +def return_list(value=None): + return ['val', 'val'] + + +def return_list2(value=None): + return [['val', 'val']] + + +def test_submulti(): + multi = StrOption('multi', '', multi=submulti) + multi2 = StrOption('multi2', '', default_multi='yes', multi=submulti) + multi3 = StrOption('multi3', '', default=[['yes']], multi=submulti) + od = OptionDescription('od', '', [multi, multi2, multi3]) + cfg = Config(od) + assert cfg.getowner(multi) == owners.default + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + assert cfg.getowner(multi) == owners.default + assert cfg.multi3 == [['yes']] + assert cfg.multi3[0] == ['yes'] + assert cfg.multi3[0][0] == 'yes' + cfg.multi3[0] + assert cfg.getowner(multi) == owners.default + + +def test_append_submulti(): + multi = StrOption('multi', '', multi=submulti) + multi2 = StrOption('multi2', '', default_multi='yes', multi=submulti) + multi3 = StrOption('multi3', '', default=[['yes']], multi=submulti) + od = OptionDescription('od', '', [multi, multi2, multi3]) + cfg = Config(od) + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + cfg.multi.append() + assert cfg.getowner(multi) == owner + assert cfg.multi == [[]] + cfg.multi.append(['no']) + assert cfg.multi == [[], ['no']] + # + assert cfg.multi2 == [] + assert cfg.getowner(multi2) == owners.default + cfg.multi2.append() + assert cfg.getowner(multi2) == owner + assert cfg.multi2 == [['yes']] + cfg.multi2.append(['no']) + assert cfg.multi2 == [['yes'], ['no']] + # + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + cfg.multi3.append() + assert cfg.getowner(multi3) == owner + assert cfg.multi3 == [['yes'], []] + cfg.multi3.append(['no']) + assert cfg.multi3 == [['yes'], [], ['no']] + + +def test_append_unvalide_submulti(): + multi = StrOption('multi', '', multi=submulti) + multi2 = StrOption('multi2', '', default_multi='yes', multi=submulti) + multi3 = StrOption('multi3', '', default=[['yes']], multi=submulti) + od = OptionDescription('od', '', [multi, multi2, multi3]) + cfg = Config(od) + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + raises(ValueError, "cfg.multi.append(1)") + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + # + assert cfg.multi2 == [] + raises(ValueError, "cfg.multi2.append('no')") + assert cfg.getowner(multi) == owners.default + assert cfg.multi2 == [] + # + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + raises(ValueError, "cfg.multi3[0].append(1)") + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + raises(ValueError, "cfg.multi3[0].append([])") + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + + +def test_pop_submulti(): + multi = StrOption('multi', '', multi=submulti) + multi2 = StrOption('multi2', '', default_multi='yes', multi=submulti) + multi3 = StrOption('multi3', '', default=[['yes']], multi=submulti) + od = OptionDescription('od', '', [multi, multi2, multi3]) + cfg = Config(od) + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.multi == [] + assert cfg.getowner(multi3) == owners.default + cfg.multi = [['no', 'yes'], ['peharps']] + assert cfg.getowner(multi) == owner + assert cfg.multi == [['no', 'yes'], ['peharps']] + cfg.multi[0].pop(1) + assert cfg.multi == [['no'], ['peharps']] + cfg.multi[0].pop(0) + assert cfg.multi == [[], ['peharps']] + cfg.multi.pop(1) + assert cfg.multi == [[]] + cfg.multi.pop(0) + assert cfg.multi == [] + # + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + cfg.multi3.pop(0) + assert cfg.getowner(multi) == owner + assert cfg.multi3 == [] + del(cfg.multi3) + assert cfg.getowner(multi3) == owners.default + cfg.multi3[0].pop(0) + assert cfg.getowner(multi3) == owner + assert cfg.multi3 == [[]] + + +def test_sort_submulti(): + multi = StrOption('multi', '', multi=submulti) + multi2 = StrOption('multi2', '', default_multi='yes', multi=submulti) + multi3 = StrOption('multi3', '', default=[['yes']], multi=submulti) + od = OptionDescription('od', '', [multi, multi2, multi3]) + cfg = Config(od) + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + cfg.multi.sort() + assert cfg.getowner(multi) == owner + cfg.multi = [['no', 'yes'], ['peharps']] + cfg.multi.sort() + assert cfg.multi == [['no', 'yes'], ['peharps']] + cfg.multi.sort(reverse=True) + assert cfg.multi == [['peharps'], ['no', 'yes']] + cfg.multi[1].sort(reverse=True) + assert cfg.multi == [['peharps'], ['yes', 'no']] + cfg.multi[1].sort() + assert cfg.multi == [['peharps'], ['no', 'yes']] + # + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + cfg.multi3.sort() + assert cfg.getowner(multi) == owner + assert cfg.multi3 == [['yes']] + del(cfg.multi3) + assert cfg.getowner(multi3) == owners.default + cfg.multi3[0].sort() + assert cfg.getowner(multi) == owner + assert cfg.multi3 == [['yes']] + + +def test_reverse_submulti(): + multi = StrOption('multi', '', multi=submulti) + multi2 = StrOption('multi2', '', default_multi='yes', multi=submulti) + multi3 = StrOption('multi3', '', default=[['yes']], multi=submulti) + od = OptionDescription('od', '', [multi, multi2, multi3]) + cfg = Config(od) + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + cfg.multi.reverse() + assert cfg.getowner(multi) == owner + cfg.multi = [['no', 'yes'], ['peharps']] + cfg.multi.reverse() + assert cfg.multi == [['peharps'], ['no', 'yes']] + cfg.multi[1].reverse() + assert cfg.multi == [['peharps'], ['yes', 'no']] + # + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + cfg.multi3.reverse() + assert cfg.getowner(multi) == owner + assert cfg.multi3 == [['yes']] + del(cfg.multi3) + assert cfg.getowner(multi3) == owners.default + cfg.multi3[0].reverse() + assert cfg.getowner(multi) == owner + assert cfg.multi3 == [['yes']] + + +def test_insert_submulti(): + multi = StrOption('multi', '', multi=submulti) + multi2 = StrOption('multi2', '', default_multi='yes', multi=submulti) + multi3 = StrOption('multi3', '', default=[['yes']], multi=submulti) + od = OptionDescription('od', '', [multi, multi2, multi3]) + cfg = Config(od) + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + cfg.multi.insert(0, ['no']) + assert cfg.getowner(multi) == owner + assert cfg.multi == [['no']] + assert isinstance(cfg.multi, Multi) + assert isinstance(cfg.multi[0], SubMulti) + # + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + cfg.multi3.insert(1, []) + assert cfg.getowner(multi3) == owner + assert cfg.multi3 == [['yes'], []] + cfg.multi3.insert(0, ['no']) + assert cfg.multi3 == [['no'], ['yes'], []] + del(cfg.multi3) + assert cfg.getowner(multi3) == owners.default + cfg.multi3[0].insert(0, 'no') + assert cfg.getowner(multi3) == owner + assert cfg.multi3 == [['no', 'yes']] + + +def test_insert_unvalide_submulti(): + multi = StrOption('multi', '', multi=submulti) + multi2 = StrOption('multi2', '', default_multi='yes', multi=submulti) + multi3 = StrOption('multi3', '', default=[['yes']], multi=submulti) + od = OptionDescription('od', '', [multi, multi2, multi3]) + cfg = Config(od) + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + raises(ValueError, "cfg.multi.insert(0, 1)") + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + # + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + raises(ValueError, "cfg.multi3[0].insert(0, 1)") + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + + +def test_extend_submulti(): + multi = StrOption('multi', '', multi=submulti) + multi2 = StrOption('multi2', '', default_multi='yes', multi=submulti) + multi3 = StrOption('multi3', '', default=[['yes']], multi=submulti) + od = OptionDescription('od', '', [multi, multi2, multi3]) + cfg = Config(od) + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + cfg.multi.extend([['no']]) + assert cfg.getowner(multi) == owner + assert cfg.multi == [['no']] + assert isinstance(cfg.multi, Multi) + assert isinstance(cfg.multi[0], SubMulti) + # + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + cfg.multi3.extend([[]]) + assert cfg.getowner(multi3) == owner + assert cfg.multi3 == [['yes'], []] + cfg.multi3.extend([['no']]) + assert cfg.multi3 == [['yes'], [], ['no']] + del(cfg.multi3) + assert cfg.getowner(multi3) == owners.default + cfg.multi3[0].extend(['no']) + assert cfg.getowner(multi3) == owner + assert cfg.multi3 == [['yes', 'no']] + + +def test_extend_unvalide_submulti(): + multi = StrOption('multi', '', multi=submulti) + multi2 = StrOption('multi2', '', default_multi='yes', multi=submulti) + multi3 = StrOption('multi3', '', default=[['yes']], multi=submulti) + od = OptionDescription('od', '', [multi, multi2, multi3]) + cfg = Config(od) + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + raises(ValueError, "cfg.multi.extend([[1]])") + assert cfg.multi == [] + assert cfg.getowner(multi) == owners.default + # + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + raises(ValueError, "cfg.multi3[0].extend([1])") + assert cfg.multi3 == [['yes']] + assert cfg.getowner(multi3) == owners.default + + +def test_callback_submulti_str(): + multi = StrOption('multi', '', multi=submulti, callback=return_val) + od = OptionDescription('od', '', [multi]) + cfg = Config(od) + cfg.read_write() + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.getowner(multi) == owners.default + assert cfg.multi == [['val']] + cfg.multi.append() + assert cfg.getowner(multi) == owner + assert cfg.multi == [['val'], ['val']] + del(cfg.multi) + assert cfg.getowner(multi) == owners.default + cfg.multi[0].append() + assert cfg.getowner(multi) == owner + assert cfg.multi == [['val', 'val']] + + +def test_callback_submulti_list(): + multi = StrOption('multi', '', multi=submulti, callback=return_list) + od = OptionDescription('od', '', [multi]) + cfg = Config(od) + cfg.read_write() + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.multi == [['val', 'val']] + assert cfg.getowner(multi) == owners.default + cfg.multi.append() + assert cfg.getowner(multi) == owner + assert cfg.multi == [['val', 'val'], ['val', 'val']] + del(cfg.multi) + assert cfg.getowner(multi) == owners.default + cfg.multi[0].append() + assert cfg.getowner(multi) == owner + assert cfg.multi == [['val', 'val', None]] + + +def test_callback_submulti_list_list(): + multi = StrOption('multi', '', multi=submulti, callback=return_list2) + od = OptionDescription('od', '', [multi]) + cfg = Config(od) + cfg.read_write() + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.multi == [['val', 'val']] + assert cfg.getowner(multi) == owners.default + cfg.multi.append() + assert cfg.getowner(multi) == owner + assert cfg.multi == [['val', 'val'], []] + del(cfg.multi) + assert cfg.getowner(multi) == owners.default + cfg.multi[0].append() + assert cfg.getowner(multi) == owner + assert cfg.multi == [['val', 'val', None]] + + +#FIXME multi sur une master + + +def test_groups_with_master_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + assert interface1.impl_get_group_type() == groups.master + + +def test_groups_with_master_in_config_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + Config(interface1) + assert interface1.impl_get_group_type() == groups.master + + +def test_values_with_master_and_slaves_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + owner = cfg.cfgimpl_get_settings().getowner() + assert interface1.impl_get_group_type() == groups.master + assert cfg.getowner(ip_admin_eth0) == owners.default + assert cfg.getowner(netmask_admin_eth0) == owners.default + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [] + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + assert cfg.ip_admin_eth0.ip_admin_eth0 == ["192.168.230.145"] + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [[]] + assert cfg.getowner(ip_admin_eth0) == owner + assert cfg.getowner(netmask_admin_eth0) == owners.default + cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145", "192.168.230.147"] + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [[], []] + raises(SlaveError, 'cfg.ip_admin_eth0.netmask_admin_eth0.append(None)') + raises(SlaveError, 'cfg.ip_admin_eth0.netmask_admin_eth0.pop(0)') + cfg.ip_admin_eth0.netmask_admin_eth0[0].append('255.255.255.0') + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [['255.255.255.0'], []] + cfg.ip_admin_eth0.netmask_admin_eth0[0].pop(0) + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [[], []] + raises(ValueError, 'cfg.ip_admin_eth0.netmask_admin_eth0 = ["255.255.255.0", "255.255.255.0"]') + + +def test_reset_values_with_master_and_slaves_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + owner = cfg.cfgimpl_get_settings().getowner() + assert interface1.impl_get_group_type() == groups.master + assert cfg.getowner(ip_admin_eth0) == owners.default + assert cfg.getowner(netmask_admin_eth0) == owners.default + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + assert cfg.getowner(ip_admin_eth0) == owner + assert cfg.getowner(netmask_admin_eth0) == owners.default + del(cfg.ip_admin_eth0.ip_admin_eth0) + assert cfg.getowner(ip_admin_eth0) == owners.default + assert cfg.getowner(netmask_admin_eth0) == owners.default + assert cfg.ip_admin_eth0.ip_admin_eth0 == [] + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [] + # + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + cfg.ip_admin_eth0.netmask_admin_eth0[0].append('255.255.255.0') + assert cfg.getowner(ip_admin_eth0) == owner + assert cfg.getowner(netmask_admin_eth0) == owner + del(cfg.ip_admin_eth0.ip_admin_eth0) + assert cfg.getowner(ip_admin_eth0) == owners.default + assert cfg.getowner(netmask_admin_eth0) == owners.default + assert cfg.ip_admin_eth0.ip_admin_eth0 == [] + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [] + + +def test_values_with_master_and_slaves_slave_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [] + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0']]") + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0']] + cfg.ip_admin_eth0.netmask_admin_eth0[0] = ['255.255.255.0'] + cfg.ip_admin_eth0.netmask_admin_eth0[0][0] = '255.255.255.0' + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0'], ['255.255.255.0']]") + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = []") + del(cfg.ip_admin_eth0.netmask_admin_eth0) + cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0']] + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [['255.255.255.0'], []] + cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0'], ['255.255.255.0']] + raises(SlaveError, 'cfg.ip_admin_eth0.netmask_admin_eth0.pop(1)') + + +def test_values_with_master_and_slaves_master_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145"] + cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145", "192.168.230.145"] + cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0'], ['255.255.255.0']] + raises(SlaveError, 'cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145"]') + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [['255.255.255.0'], ['255.255.255.0']] + cfg.ip_admin_eth0.ip_admin_eth0.pop(1) + assert cfg.ip_admin_eth0.ip_admin_eth0 == ["192.168.230.145"] + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [['255.255.255.0']] + del(cfg.ip_admin_eth0.ip_admin_eth0) + assert cfg.ip_admin_eth0.ip_admin_eth0 == [] + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [] + + +def test_values_with_master_and_slaves_master_error_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145", "192.168.230.145"] + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0']]") + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0'], ['255.255.255.0'], ['255.255.255.0']]") + cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0'], ['255.255.255.0']] + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0']]") + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = [['255.255.255.0'], ['255.255.255.0'], ['255.255.255.0']]") + + +def test_values_with_master_owner_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + owner = cfg.cfgimpl_get_settings().getowner() + assert cfg.getowner(ip_admin_eth0) == owners.default + assert cfg.getowner(netmask_admin_eth0) == owners.default + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + assert cfg.getowner(ip_admin_eth0) == owner + assert cfg.getowner(netmask_admin_eth0) == owners.default + cfg.ip_admin_eth0.ip_admin_eth0.pop(0) + assert cfg.getowner(ip_admin_eth0) == owner + assert cfg.getowner(netmask_admin_eth0) == owners.default + + +def test_values_with_master_disabled_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + cfg.ip_admin_eth0.ip_admin_eth0.pop(0) + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + cfg.ip_admin_eth0.netmask_admin_eth0 = [["192.168.230.145"]] + cfg.ip_admin_eth0.ip_admin_eth0.pop(0) + del(cfg.ip_admin_eth0.netmask_admin_eth0) + cfg.cfgimpl_get_settings()[netmask_admin_eth0].append('disabled') + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + cfg.ip_admin_eth0.ip_admin_eth0.pop(0) + + #delete with value in disabled var + cfg.cfgimpl_get_settings()[netmask_admin_eth0].remove('disabled') + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + cfg.ip_admin_eth0.netmask_admin_eth0 = [["192.168.230.145"]] + cfg.cfgimpl_get_settings()[netmask_admin_eth0].append('disabled') + cfg.ip_admin_eth0.ip_admin_eth0.pop(0) + + #append with value in disabled var + cfg.cfgimpl_get_settings()[netmask_admin_eth0].remove('disabled') + cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") + cfg.ip_admin_eth0.netmask_admin_eth0 = [["192.168.230.145"]] + cfg.cfgimpl_get_settings()[netmask_admin_eth0].append('disabled') + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.230.43') + + +def test_multi_insert_master_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + raises(SlaveError, "cfg.ip_admin_eth0.ip_admin_eth0.insert(0, 'nok')") + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0.insert(0, 'nok')") + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.1.1') + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0[0].insert(0, 'nok')") + + +def test_multi_sort_master_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + raises(SlaveError, "cfg.ip_admin_eth0.ip_admin_eth0.sort()") + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0.sort()") + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.1.1') + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0[0].sort()") + + +def test_multi_reverse_master_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + raises(SlaveError, "cfg.ip_admin_eth0.ip_admin_eth0.reverse()") + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0.reverse()") + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.1.1') + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0[0].reverse()") + + +def test_multi_extend_master_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + raises(SlaveError, "cfg.ip_admin_eth0.ip_admin_eth0.extend(['ok'])") + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0.extend(['ok'])") + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.1.1') + raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0[0].extend(['ok'])") + + +def test_slave_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=submulti) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + assert cfg.ip_admin_eth0.ip_admin_eth0.__class__.__name__ == 'Multi' + assert cfg.ip_admin_eth0.netmask_admin_eth0.__class__.__name__ == 'Multi' + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.1.1') + assert cfg.ip_admin_eth0.ip_admin_eth0.__class__.__name__ == 'Multi' + assert cfg.ip_admin_eth0.netmask_admin_eth0.__class__.__name__ == 'Multi' + assert cfg.ip_admin_eth0.ip_admin_eth0[0].__class__.__name__ == 'str' + assert cfg.ip_admin_eth0.netmask_admin_eth0[0].__class__.__name__ == 'SubMulti' + + +def test__master_is_submulti(): + ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=submulti) + netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) + interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) + interface1.impl_set_group_type(groups.master) + maconfig = OptionDescription('toto', '', [interface1]) + cfg = Config(maconfig) + cfg.read_write() + owner = cfg.cfgimpl_get_settings().getowner() + assert interface1.impl_get_group_type() == groups.master + assert cfg.getowner(ip_admin_eth0) == owners.default + assert cfg.getowner(netmask_admin_eth0) == owners.default + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [] + cfg.ip_admin_eth0.ip_admin_eth0.append(["192.168.230.145"]) + assert cfg.ip_admin_eth0.ip_admin_eth0 == [["192.168.230.145"]] + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [None] + assert cfg.getowner(ip_admin_eth0) == owner + assert cfg.getowner(netmask_admin_eth0) == owners.default + cfg.ip_admin_eth0.ip_admin_eth0 = [["192.168.230.145"], ["192.168.230.147"]] + assert cfg.ip_admin_eth0.netmask_admin_eth0 == [None, None] + raises(SlaveError, 'cfg.ip_admin_eth0.netmask_admin_eth0.append(None)') + raises(SlaveError, 'cfg.ip_admin_eth0.netmask_admin_eth0.pop(0)') + cfg.ip_admin_eth0.ip_admin_eth0[0].append('192.168.1.1') + assert cfg.ip_admin_eth0.ip_admin_eth0 == [["192.168.230.145", '192.168.1.1'], ["192.168.230.147"]] + cfg.ip_admin_eth0.ip_admin_eth0[0].pop(0) + assert cfg.ip_admin_eth0.ip_admin_eth0 == [["192.168.1.1"], ["192.168.230.147"]] + raises(ValueError, 'cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.1.1", "192.168.1.1"]') diff --git a/tiramisu/option/__init__.py b/tiramisu/option/__init__.py index 3be1c50..c17b243 100644 --- a/tiramisu/option/__init__.py +++ b/tiramisu/option/__init__.py @@ -1,6 +1,6 @@ from .masterslave import MasterSlaves from .optiondescription import OptionDescription -from .baseoption import Option, SymLinkOption +from .baseoption import Option, SymLinkOption, submulti from .option import (ChoiceOption, BoolOption, IntOption, FloatOption, StrOption, UnicodeOption, IPOption, PortOption, NetworkOption, NetmaskOption, BroadcastOption, @@ -13,4 +13,4 @@ __all__ = ('MasterSlaves', 'OptionDescription', 'Option', 'SymLinkOption', 'StrOption', 'UnicodeOption', 'IPOption', 'PortOption', 'NetworkOption', 'NetmaskOption', 'BroadcastOption', 'DomainnameOption', 'EmailOption', 'URLOption', 'UsernameOption', - 'FilenameOption') + 'FilenameOption', 'submulti') diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index b3720eb..2ad51dc 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -32,6 +32,14 @@ from tiramisu.storage import get_storages_option StorageBase = get_storages_option('base') + +class SubMulti(object): + pass + + +submulti = SubMulti() + + name_regexp = re.compile(r'^\d+') forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first', 'make_dict', 'unwrap_from_path', 'read_only', @@ -123,7 +131,7 @@ class Base(StorageBase): "for option {1}: {2}").format( str(default_multi), name, err)) self._multi = multi - if self._multi: + if self._multi is not False: if default is None: default = [] self._default_multi = default_multi @@ -414,7 +422,7 @@ class Option(OnlyOption): return self._requires def _launch_consistency(self, func, option, value, context, index, - all_cons_opts, warnings_only): + submulti_index, all_cons_opts, warnings_only): """Launch consistency now :param func: function name, this name should start with _cons_ @@ -452,17 +460,24 @@ class Option(OnlyOption): #append value if not self.impl_is_multi() or option == opt: all_cons_vals.append(opt_value) + elif self.impl_is_submulti(): + try: + all_cons_vals.append(opt_value[index][submulti_index]) + except IndexError: + #value is not already set, could be higher index + #so return if no value + return else: - #value is not already set, could be higher index try: all_cons_vals.append(opt_value[index]) except IndexError: + #value is not already set, could be higher index #so return if no value 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_index=None, force_submulti_index=None): """ :param value: the option's value :param context: Config's context @@ -470,8 +485,11 @@ class Option(OnlyOption): :param validate: if true enables ``self._validator`` validation :type validate: boolean :param force_index: if multi, value has to be a list - not if force_index is not None + not if force_index is not None :type force_index: integer + :param force_submulti_index: if submulti, value has to be a list + not if force_submulti_index is not None + :type force_submulti_index: integer """ if not validate: return @@ -496,15 +514,17 @@ class Option(OnlyOption): callback=self._validator, callback_params=validator_params) - def do_validation(_value, _index=None): + def do_validation(_value, _index, submulti_index): if _value is None: return # option validation try: self._validate(_value) except ValueError as err: - log.debug('do_validation: value: {0} index: {1}'.format( - _value, _index), exc_info=True) + log.debug('do_validation: value: {0}, index: {1}, ' + 'submulti_index: {2}'.format(_value, _index, + submulti_index), + exc_info=True) raise ValueError(_('invalid value for option {0}: {1}' '').format(self.impl_getname(), err)) error = None @@ -514,7 +534,8 @@ class Option(OnlyOption): val_validator(_value) # if not context launch consistency validation if context is not None: - descr._valid_consistency(self, _value, context, _index) + descr._valid_consistency(self, _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( @@ -530,7 +551,8 @@ class Option(OnlyOption): try: # if context launch consistency validation if context is not None: - descr._valid_consistency(self, _value, context, _index) + descr._valid_consistency(self, _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) @@ -553,13 +575,34 @@ class Option(OnlyOption): if context is not None: descr = context.cfgimpl_get_description() - if not self._multi or force_index is not None: - do_validation(value, force_index) + 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): + raise ValueError(_("invalid value {0} for option {1} which" + " must be a list").format( + value, self.impl_getname())) + for idx, val in enumerate(value): + do_validation(val, force_index, idx) + else: + do_validation(value, force_index, force_submulti_index) else: if not isinstance(value, list): - raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self.impl_getname())) - for index, val in enumerate(value): - do_validation(val, index) + 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): + raise ValueError(_("invalid value {0} for option {1} " + "which must be a list of list" + "").format(value, + self.impl_getname())) + for slave_idx, slave_val in enumerate(val): + do_validation(slave_val, idx, slave_idx) + else: + do_validation(val, idx, force_submulti_index) def impl_getdefault(self): "accessing the default value" @@ -615,7 +658,10 @@ class Option(OnlyOption): # return value def impl_is_multi(self): - return self._multi + return self._multi is True or self._multi is submulti + + def impl_is_submulti(self): + return self._multi is submulti def impl_add_consistency(self, func, *other_opts, **params): """Add consistency means that value will be validate with other_opts @@ -647,10 +693,18 @@ class Option(OnlyOption): if value is not None: if self.impl_is_multi(): for idx, val in enumerate(value): - self._launch_consistency(func, self, val, None, - idx, all_cons_opts, warnings_only) + if not self.impl_is_submulti(): + self._launch_consistency(func, self, val, None, idx, + None, all_cons_opts, + warnings_only) + else: + for slave_idx, val in enumerate(value): + self._launch_consistency(func, self, val, None, + idx, slave_idx, + all_cons_opts, + warnings_only) else: - self._launch_consistency(func, self, value, None, + self._launch_consistency(func, self, value, None, None, None, all_cons_opts, warnings_only) self._add_consistency(func, all_cons_opts, params) self.impl_validate(self.impl_getdefault()) @@ -808,7 +862,8 @@ def validate_requires_arg(requires, name): 'must be an option in option {0}').format(name)) if option.impl_is_multi(): raise ValueError(_('malformed requirements option {0} ' - 'must not be a multi').format(name)) + 'must not be a multi for {1}').format( + option.impl_getname(), name)) if expected is not None: try: option._validate(expected) diff --git a/tiramisu/option/masterslave.py b/tiramisu/option/masterslave.py index 7a9057f..b962a98 100644 --- a/tiramisu/option/masterslave.py +++ b/tiramisu/option/masterslave.py @@ -20,7 +20,7 @@ # the whole pypy projet is under MIT licence # ____________________________________________________________ from tiramisu.i18n import _ -from tiramisu.setting import log +from tiramisu.setting import log, undefined from tiramisu.error import SlaveError, ConfigError from .baseoption import SymLinkOption, Option @@ -87,18 +87,21 @@ class MasterSlaves(object): pass def getitem(self, values, opt, path, validate, force_permissive, - force_properties, validate_properties): + force_properties, validate_properties, slave_path=undefined, + slave_value=undefined): if opt == self.master: return self._getmaster(values, opt, path, validate, force_permissive, force_properties, - validate_properties) + validate_properties, slave_path, + slave_value) else: return self._getslave(values, opt, path, validate, force_permissive, force_properties, validate_properties) def _getmaster(self, values, opt, path, validate, force_permissive, - force_properties, validate_properties): + force_properties, validate_properties, c_slave_path, + c_slave_value): value = values._get_validated_value(opt, path, validate, force_permissive, force_properties, @@ -108,12 +111,15 @@ class MasterSlaves(object): for slave in self.slaves: try: slave_path = values._get_opt_path(slave) - slave_value = values._get_validated_value(slave, - slave_path, - False, - False, - None, False, - None) # not undefined + if c_slave_path == slave_path: + slave_value = c_slave_value + else: + slave_value = values._get_validated_value(slave, + slave_path, + False, + False, + None, False, + None) # not undefined slavelen = len(slave_value) self.validate_slave_length(masterlen, slavelen, slave._name) except ConfigError: @@ -146,10 +152,11 @@ class MasterSlaves(object): self.validate_slave_length(self.get_length(values), len(value), opt._name, setitem=True) - def get_length(self, values, validate=True): + def get_length(self, values, 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, - None, True)) + None, True, slave_path, slave_value)) def validate_slave_length(self, masterlen, valuelen, name, setitem=False): if valuelen > masterlen or (valuelen < masterlen and setitem): @@ -183,11 +190,11 @@ class MasterSlaves(object): list is greater than master: raise SlaveError """ #if slave, had values until master's one - masterlen = self.get_length(values, validate) + path = values._get_opt_path(opt) + masterlen = self.get_length(values, validate, path, value) valuelen = len(value) if validate: self.validate_slave_length(masterlen, valuelen, opt._name) - path = values._get_opt_path(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 d707940..1b39012 100644 --- a/tiramisu/option/option.py +++ b/tiramisu/option/option.py @@ -134,7 +134,8 @@ class IPOption(Option): callback_params=None, validator=None, validator_params=None, properties=None, private_only=False, allow_reserved=False, warnings_only=False): - self._extra = {'_private_only': private_only, '_allow_reserved': allow_reserved} + self._extra = {'_private_only': private_only, + '_allow_reserved': allow_reserved} super(IPOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, diff --git a/tiramisu/option/optiondescription.py b/tiramisu/option/optiondescription.py index 7a1a7c1..29c1e43 100644 --- a/tiramisu/option/optiondescription.py +++ b/tiramisu/option/optiondescription.py @@ -21,7 +21,7 @@ from copy import copy from tiramisu.i18n import _ -from tiramisu.setting import groups, log +from tiramisu.setting import groups # , log from .baseoption import BaseOption from . import MasterSlaves from tiramisu.error import ConfigError, ConflictError, ValueWarning @@ -162,7 +162,7 @@ class OptionDescription(BaseOption, StorageOptionDescription): def impl_get_group_type(self): return self._group_type - def _valid_consistency(self, option, value, context, index): + def _valid_consistency(self, option, value, context, index, submulti_idx): if self._cache_consistencies is None: return True #consistencies is something like [('_cons_not_equal', (opt1, opt2))] @@ -175,6 +175,7 @@ class OptionDescription(BaseOption, StorageOptionDescription): all_cons_opts[0]._launch_consistency(func, option, value, context, index, + submulti_idx, all_cons_opts, warnings_only) except ValueError as err: diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 887c16c..fa29b07 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -223,7 +223,7 @@ populate_owners() # ____________________________________________________________ -class Undefined(): +class Undefined(object): pass diff --git a/tiramisu/storage/dictionary/option.py b/tiramisu/storage/dictionary/option.py index f86d970..f7a7f92 100644 --- a/tiramisu/storage/dictionary/option.py +++ b/tiramisu/storage/dictionary/option.py @@ -28,8 +28,8 @@ class Base(object): __slots__ = ('_name', '_requires', '_properties', '_readonly', '_calc_properties', '_informations', '_state_readonly', '_state_requires', '_stated', - '_multi', '_validator', '_validator_params', '_default', - '_default_multi', '_state_callback', '_callback', + '_multi', '_validator', '_validator_params', + '_default', '_default_multi', '_state_callback', '_callback', '_state_callback_params', '_callback_params', '_multitype', '_consistencies', '_warnings_only', '_master_slaves', '_state_consistencies', '_extra', '__weakref__') diff --git a/tiramisu/value.py b/tiramisu/value.py index 757bcdb..b208e9e 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -86,7 +86,13 @@ class Values(object): index=index) try: if isinstance(value, list) and index is not undefined: - return value[index] + #if submulti and return a list of list, just return list + if opt.impl_is_submulti(): + val = value[index] + if isinstance(val, list): + value = val + else: + value = value[index] return value except IndexError: pass @@ -219,9 +225,10 @@ class Values(object): def _get_validated_value(self, opt, path, validate, force_permissive, force_properties, validate_properties, - index=undefined): - """same has getitem but don't touch the cache""" - #FIXME expliquer la différence entre index == undefined et index == None + index=undefined, submulti_index=undefined): + """same has getitem but don't touch the cache + index is None for slave value, if value returned is not a list, just return [] + """ context = self._getcontext() setting = context.cfgimpl_get_settings() is_default = self._is_default_owner(opt, path, @@ -254,13 +261,24 @@ class Values(object): else: force_index = index if opt.impl_is_multi(): + #for slave is a multi if index is None and not isinstance(value, list): value = [] if force_index is None: value = Multi(value, self.context, opt, path) + elif opt.impl_is_submulti() and submulti_index is undefined: + value = SubMulti(value, self.context, opt, path, + force_index) + if validate: + if submulti_index is undefined: + force_submulti_index = None + else: + force_submulti_index = submulti_index + opt.impl_validate(value, context, 'validator' in setting, - force_index=force_index) + 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]: @@ -291,7 +309,7 @@ class Values(object): opt.impl_validate(value, context, 'validator' in context.cfgimpl_get_settings()) if opt.impl_is_multi(): - value = Multi(value, self.context, opt, path) + #value = Multi(value, self.context, opt, path) if opt.impl_is_master_slaves(): opt.impl_get_master_slaves().setitem(self, opt, value, path) self._setvalue(opt, path, value, force_permissive=force_permissive, @@ -309,6 +327,10 @@ class Values(object): owner = context.cfgimpl_get_settings().getowner() if isinstance(value, Multi): value = list(value) + if opt.impl_is_submulti(): + for idx, val in enumerate(value): + if isinstance(val, SubMulti): + value[idx] = list(val) self._p_.setvalue(path, value, owner) def getowner(self, opt, force_permissive=False): @@ -471,28 +493,47 @@ class Values(object): # ____________________________________________________________ # multi types - class Multi(list): """multi options values container that support item notation for the values of multi options""" - __slots__ = ('opt', 'path', 'context') + __slots__ = ('opt', 'path', 'context', '__weakref__') def __init__(self, value, context, opt, path): """ :param value: the Multi wraps a list value :param context: the home config that has the values :param opt: the option object that have this Multi value + :param path: path of the option """ - if isinstance(value, Multi): - raise ValueError(_('{0} is already a Multi ').format(opt.impl_getname())) + if value is None: + value = [] + if not opt.impl_is_submulti() and isinstance(value, Multi): + raise ValueError(_('{0} is already a Multi ').format( + opt.impl_getname())) self.opt = opt self.path = path if not isinstance(context, weakref.ReferenceType): raise ValueError('context must be a Weakref') self.context = context if not isinstance(value, list): + if not '_index' in self.__slots__ and opt.impl_is_submulti(): + value = [[value]] + else: + value = [value] + elif value != [] and not '_index' in self.__slots__ and \ + opt.impl_is_submulti() and not isinstance(value[0], list): value = [value] super(Multi, self).__init__(value) + if opt.impl_is_submulti(): + if not '_index' in self.__slots__: + for idx, val in enumerate(self): + if not isinstance(val, SubMulti): + super(Multi, self).__setitem__(idx, SubMulti(val, + context, + opt, path, + idx)) + #FIXME weakref ?? + self[idx].submulti = weakref.ref(self) def _getcontext(self): """context could be None, we need to test it @@ -506,24 +547,32 @@ class Multi(list): return context def __setitem__(self, index, value): - self._validate(value, index) + self._setitem(index, value) + + def _setitem(self, index, value): + self._validate(value, index, True) #assume not checking mandatory property super(Multi, self).__setitem__(index, value) self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, self) - def __repr__(self, *args, **kwargs): - print args, kwargs - return super(Multi, self).__repr__(*args, **kwargs) + #def __repr__(self, *args, **kwargs): + # print args, kwargs + # return super(Multi, self).__repr__(*args, **kwargs) - def __getitem__(self, y): - return super(Multi, self).__getitem__(y) + #def __getitem__(self, y): + # return super(Multi, self).__getitem__(y) + + def _get_validated_value(self, index): + values = self._getcontext().cfgimpl_get_values() + return values._get_validated_value(self.opt, self.path, + True, False, None, True, + index=index) def append(self, value=undefined, force=False, setitem=True): """the list value can be updated (appened) only if the option is a master """ - values = self._getcontext().cfgimpl_get_values() if not force: if self.opt.impl_is_master_slaves('slave'): raise SlaveError(_("cannot append a value on a multi option {0}" @@ -531,16 +580,17 @@ class Multi(list): index = self.__len__() if value is undefined: try: - value = values._get_validated_value(self.opt, self.path, - True, False, None, True, - index=index) + value = self._get_validated_value(index) except IndexError: value = None - self._validate(value, index) + self._validate(value, index, True) + if not '_index' in self.__slots__ and self.opt.impl_is_submulti(): + if not isinstance(value, SubMulti): + value = SubMulti(value, self.context, self.opt, self.path, index) + value.submulti = weakref.ref(self) super(Multi, self).append(value) if setitem: - values._setvalue(self.opt, self.path, self, - validate_properties=not force) + self._store(force) def sort(self, cmp=None, key=None, reverse=False): if self.opt.impl_is_master_slaves(): @@ -553,43 +603,40 @@ class Multi(list): super(Multi, self).sort(key=key, reverse=reverse) else: super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse) - self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, - self) + self._store() def reverse(self): if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot reverse multi option {0} if master or " "slave").format(self.opt.impl_getname())) super(Multi, self).reverse() - self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, - self) + self._store() def insert(self, index, obj): + #FIXME obj should be undefined if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot insert multi option {0} if master or " "slave").format(self.opt.impl_getname())) + self._validate(obj, index, True) super(Multi, self).insert(index, obj) - self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, - self) + self._store() def extend(self, iterable): if self.opt.impl_is_master_slaves(): raise SlaveError(_("cannot extend multi option {0} if master or " "slave").format(self.opt.impl_getname())) + try: + index = self._index + except: + index = None + self._validate(iterable, index) super(Multi, self).extend(iterable) - self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, - self) + self._store() - def _validate(self, value, force_index): + def _validate(self, value, force_index, submulti=False): if value is not None: - try: - self.opt.impl_validate(value, context=self._getcontext(), - force_index=force_index) - except ValueError as err: - raise ValueError(_("invalid value {0} " - "for option {1}: {2}" - "").format(str(value), - self.opt.impl_getname(), err)) + self.opt.impl_validate(value, context=self._getcontext(), + force_index=force_index) def pop(self, index, force=False): """the list value can be updated (poped) @@ -611,6 +658,53 @@ class Multi(list): context.cfgimpl_get_values(), index) #set value without valid properties ret = super(Multi, self).pop(index) - context.cfgimpl_get_values()._setvalue(self.opt, self.path, self, - validate_properties=not force) + self._store(force) return ret + + def _store(self, force=False): + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, self.path, + self, + validate_properties=not force) + + +class SubMulti(Multi): + __slots__ = ('_index', 'submulti') + + def __init__(self, value, context, opt, path, index): + """ + :param index: index (only for slave with submulti) + :type index: `int` + """ + self._index = index + super(SubMulti, self).__init__(value, context, opt, path) + + def append(self, value=undefined): + super(SubMulti, self).append(value, force=True) + + def pop(self, index): + return super(SubMulti, self).pop(index, force=True) + + def __setitem__(self, index, value): + self._setitem(index, value) + + def _store(self, force=False): + #force is unused here + self._getcontext().cfgimpl_get_values()._setvalue(self.opt, + self.path, + self.submulti()) + + def _validate(self, value, force_index, submulti=False): + if value is not None: + if submulti is False: + super(SubMulti, self)._validate(value, force_index) + else: + self.opt.impl_validate(value, context=self._getcontext(), + force_index=self._index, + force_submulti_index=force_index) + + def _get_validated_value(self, index): + values = self._getcontext().cfgimpl_get_values() + return values._get_validated_value(self.opt, self.path, + True, False, None, True, + index=index, + submulti_index=self._index)