add SubMulti
authorEmmanuel Garette <egarette@cadoles.com>
Fri, 25 Apr 2014 20:57:08 +0000 (22:57 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Sat, 26 Apr 2014 20:37:01 +0000 (22:37 +0200)
13 files changed:
ChangeLog
test/test_dereference.py
test/test_option_calculation.py
test/test_parsing_group.py
test/test_submulti.py [new file with mode: 0644]
tiramisu/option/__init__.py
tiramisu/option/baseoption.py
tiramisu/option/masterslave.py
tiramisu/option/option.py
tiramisu/option/optiondescription.py
tiramisu/setting.py
tiramisu/storage/dictionary/option.py
tiramisu/value.py

index e89d792..4794942 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+XXXXXXXXXXXXX Emmanuel Garette <egarette@cadoles.com>
+
+       * add SubMulti:
+       a SubMulti is a multi in a multi variable
+
 Sat Apr 12 11:37:27 CEST 2014  Emmanuel Garette <egarette@cadoles.com>
 
        * behavior change in master/slave part of code:
 Sat Apr 12 11:37:27 CEST 2014  Emmanuel Garette <egarette@cadoles.com>
 
        * behavior change in master/slave part of code:
index 0e59f36..c39bfcb 100644 (file)
@@ -3,7 +3,7 @@ import autopath
 #from py.test import raises
 
 from tiramisu.config import Config, GroupConfig, MetaConfig
 #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
 
 
 import weakref
 
 
@@ -137,3 +137,21 @@ def test_deref_metaconfig():
     assert w() is not None
     del(meta)
     assert w() is None
     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
index 3199308..881d661 100644 (file)
@@ -438,8 +438,9 @@ def test_callback_multi_callback():
     cfg = Config(maconfig)
     cfg.read_write()
     assert cfg.val1.val1 == ['val']
     cfg = Config(maconfig)
     cfg.read_write()
     assert cfg.val1.val1 == ['val']
+    cfg.val1.val1 = ['val1']
     cfg.val1.val1.append()
     cfg.val1.val1.append()
-    assert cfg.val1.val1 == ['val', 'val']
+    assert cfg.val1.val1 == ['val1', 'val']
 
 
 def test_callback_master_and_slaves_master():
 
 
 def test_callback_master_and_slaves_master():
index 46bd82d..3c3175d 100644 (file)
@@ -355,6 +355,7 @@ def test_multi_insert():
     c.var.insert(0, 'nok')
     assert c.var == ['nok', 'ok']
     assert c.getowner(var) != owners.default
     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():
 
 
 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
     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():
 
 
 def test_multi_extend_master():
diff --git a/test/test_submulti.py b/test/test_submulti.py
new file mode 100644 (file)
index 0000000..6b5108f
--- /dev/null
@@ -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"]')
index 3be1c50..c17b243 100644 (file)
@@ -1,6 +1,6 @@
 from .masterslave import MasterSlaves
 from .optiondescription import OptionDescription
 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,
 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',
            'StrOption', 'UnicodeOption', 'IPOption', 'PortOption',
            'NetworkOption', 'NetmaskOption', 'BroadcastOption',
            'DomainnameOption', 'EmailOption', 'URLOption', 'UsernameOption',
-           'FilenameOption')
+           'FilenameOption', 'submulti')
index b3720eb..2ad51dc 100644 (file)
@@ -32,6 +32,14 @@ from tiramisu.storage import get_storages_option
 
 StorageBase = get_storages_option('base')
 
 
 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',
 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
                                    "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
             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,
         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_
         """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)
             #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:
             else:
-                #value is not already set, could be higher index
                 try:
                     all_cons_vals.append(opt_value[index])
                 except IndexError:
                 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,
                     #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
         """
         :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
         :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
         :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
         """
         if not validate:
             return
@@ -496,15 +514,17 @@ class Option(OnlyOption):
                                       callback=self._validator,
                                       callback_params=validator_params)
 
                                       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:
             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
                 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:
                 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(
                 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:
                 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)
                 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 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):
         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"
 
     def impl_getdefault(self):
         "accessing the default value"
@@ -615,7 +658,10 @@ class Option(OnlyOption):
     #    return value
 
     def impl_is_multi(self):
     #    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
 
     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):
         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:
             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())
                                          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 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)
         if expected is not None:
             try:
                 option._validate(expected)
index 7a9057f..b962a98 100644 (file)
@@ -20,7 +20,7 @@
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
 from tiramisu.i18n import _
 # 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
 
 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,
         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,
         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,
         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,
         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)
             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:
                     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)
 
             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,
         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):
 
     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
                 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)
         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
         if valuelen < masterlen:
             for num in range(0, masterlen - valuelen):
                 index = valuelen + num
index d707940..1b39012 100644 (file)
@@ -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):
                  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,
         super(IPOption, self).__init__(name, doc, default=default,
                                        default_multi=default_multi,
                                        callback=callback,
index 7a1a7c1..29c1e43 100644 (file)
@@ -21,7 +21,7 @@
 from copy import copy
 
 from tiramisu.i18n import _
 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
 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 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))]
         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,
                     all_cons_opts[0]._launch_consistency(func, option,
                                                          value,
                                                          context, index,
+                                                         submulti_idx,
                                                          all_cons_opts,
                                                          warnings_only)
                 except ValueError as err:
                                                          all_cons_opts,
                                                          warnings_only)
                 except ValueError as err:
index 887c16c..fa29b07 100644 (file)
@@ -223,7 +223,7 @@ populate_owners()
 
 
 # ____________________________________________________________
 
 
 # ____________________________________________________________
-class Undefined():
+class Undefined(object):
     pass
 
 
     pass
 
 
index f86d970..f7a7f92 100644 (file)
@@ -28,8 +28,8 @@ class Base(object):
     __slots__ = ('_name', '_requires', '_properties', '_readonly',
                  '_calc_properties', '_informations',
                  '_state_readonly', '_state_requires', '_stated',
     __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__')
                  '_state_callback_params', '_callback_params', '_multitype',
                  '_consistencies', '_warnings_only', '_master_slaves',
                  '_state_consistencies', '_extra', '__weakref__')
index 757bcdb..b208e9e 100644 (file)
@@ -86,7 +86,13 @@ class Values(object):
                                           index=index)
             try:
                 if isinstance(value, list) and index is not undefined:
                                           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
                 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,
 
     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,
         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():
             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)
                 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 validate:
+                if submulti_index is undefined:
+                    force_submulti_index = None
+                else:
+                    force_submulti_index = submulti_index
+
                 opt.impl_validate(value, context, 'validator' in setting,
                 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]:
             #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():
         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,
             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)
         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):
         self._p_.setvalue(path, value, owner)
 
     def getowner(self, opt, force_permissive=False):
@@ -471,28 +493,47 @@ class Values(object):
 # ____________________________________________________________
 # multi types
 
 # ____________________________________________________________
 # multi types
 
-
 class Multi(list):
     """multi options values container
     that support item notation for the values of multi options"""
 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
 
     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):
         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)
             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
 
     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):
         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)
 
         #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
         """
 
     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}"
         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:
         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
             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:
         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():
 
     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)
             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()
 
     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):
 
     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()))
         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)
         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()))
 
     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)
         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:
         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)
 
     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(), 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
         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)