separate value in slave
authorEmmanuel Garette <egarette@cadoles.com>
Thu, 19 Nov 2015 21:25:00 +0000 (22:25 +0100)
committerEmmanuel Garette <egarette@cadoles.com>
Thu, 19 Nov 2015 21:25:00 +0000 (22:25 +0100)
16 files changed:
ChangeLog
test/test_dyn_optiondescription.py
test/test_metaconfig.py
test/test_multi.py
test/test_option_calculation.py
test/test_parsing_group.py
test/test_submulti.py
tiramisu/autolib.py
tiramisu/config.py
tiramisu/option/baseoption.py
tiramisu/option/masterslave.py
tiramisu/setting.py
tiramisu/storage/dictionary/option.py
tiramisu/storage/dictionary/value.py
tiramisu/storage/util.py
tiramisu/value.py

index b8f6f5b..6c8f3ca 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -10,7 +10,7 @@ Sun Jul 26 19:09:29 2015 +0200 Emmanuel Garette <egarette@cadoles.com>
 
 Fri Jul 24 18:03:59 2015 +0200 Emmanuel Garette <egarette@cadoles.com>
        * add duplicate option to Config, to generate new Config with same
-       value, properties, Option. Option are not duplication.
+       value, properties, Option. Option are not duplicated.
 
 Mon Apr 20 14:44:15 2015 +0200 Emmanuel Garette <egarette@cadoles.com>
        * if option is multi, the properties disallow [None] for a multi but
@@ -44,7 +44,7 @@ Sun Dec  7 14:37:32 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
 Mon Dec  1 22:58:13 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
        * propertyerror are transitive in consistency, now it's possible to set
        non-transitive consistency
-       * if consistency with multiple option return if transitive
+       * support transitive in consistency with multiple option return
        * can reset slave value in all case when deleting master value
        * in_network's consistency now verify that IP is not network or
        broadcast's IP + ip_netmask's consistency now verify that IP is not
@@ -52,11 +52,11 @@ Mon Dec  1 22:58:13 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
 
 Sun Oct 26 08:50:38 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
        * if option is frozen with force_default_on_freeze property, owner
-       must be 'default' check property when tried to change owner
+       must be 'default', check property when tried to change owner
        * bad characters in DomainnameOption could be in warning level
        * frozen with force_default_on_freeze can change owner
        * add force_permissive to config __iter__
-       * pass force_permissive to slave for a master or to master for a slave
+       * add force_permissive to slave for a master or to master for a slave
        * remove mandatory_warnings in config.py
        * add force_permissive in mandatory_warnings
 
@@ -78,13 +78,13 @@ Thu Jun 19 23:20:29 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
 Sun Apr 27 10:32:40 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
 
        * behavior change in ChoiceOption:
-       remove open_values, that no sens (no type validation is possible) if
+       remove open_values, that no sens (we cannot validate type) if
        you want something like open_values, please use a typed option and
        add impl_(s|g)et_information to add proposed values and use it in your
        code
        * add dynamic ChoiceOption:
        we can have dynamic ChoiceOption. Parameter values can be a function
-       and as callback, we can add values_params
+       and, as callback, we can add values_params
 
 Fri Apr 25 22:57:08 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
 
@@ -99,8 +99,8 @@ Sat Apr 12 11:37:27 2014 +0200 Emmanuel Garette <egarette@cadoles.com>
        * tiramisu/config.py (in cfgimpl_get_home_by_path and getattr) and
        tiramisu/value.py (in getitem): arity change, remove force_properties
        * tiramisu/option.py: split into tiramisu/option directory
-       * tiramisu/option/masterslave.py: master/slaves have no a special
-       object MasterSlaves for all code related to master/slaves options
-       * tiramisu/option/masterslave.py: master and slaves values (length,
-       consistency, ...) are now check every time
+       * tiramisu/option/masterslave.py: add special object MasterSlaves for
+       all code related to master/slaves options
+       * tiramisu/option/masterslave.py: check every time master and slaves
+       values (length, consistency, ...)
        * change None to undefined when needed
index 9dab50b..381bfd6 100644 (file)
@@ -904,8 +904,8 @@ def test_masterslaves_dyndescription():
     assert cfg.od.stval2.st1val2.st2val2 == []
     assert cfg.getowner(st1val1) == owner
     assert cfg.getowner(st1val2) == owners.default
-    assert cfg.getowner(st2val1) == owner
-    assert cfg.getowner(st2val2) == owners.default
+    assert cfg.getowner(st2val1, 0) == owner
+#    assert cfg.getowner(st2val2) == owners.default
     #
     cfg.od.stval1.st1val1.st1val1.pop(0)
     assert cfg.od.stval1.st1val1.st1val1 == []
@@ -914,18 +914,18 @@ def test_masterslaves_dyndescription():
     assert cfg.od.stval2.st1val2.st2val2 == []
     assert cfg.getowner(st1val1) == owner
     assert cfg.getowner(st1val2) == owners.default
-    assert cfg.getowner(st2val1) == owner
-    assert cfg.getowner(st2val2) == owners.default
+#    assert cfg.getowner(st2val1) == owner
+#    assert cfg.getowner(st2val2) == owners.default
     #
     cfg.od.stval1.st1val1.st1val1 = ['yes']
     cfg.od.stval1.st1val1.st2val1 = ['yes']
     assert cfg.getowner(st1val1) == owner
-    assert cfg.getowner(st2val1) == owner
+    assert cfg.getowner(st2val1, 0) == owner
     del(cfg.od.stval1.st1val1.st2val1)
     assert cfg.getowner(st1val1) == owner
     assert cfg.getowner(st1val2) == owners.default
-    assert cfg.getowner(st2val1) == owners.default
-    assert cfg.getowner(st2val2) == owners.default
+#    assert cfg.getowner(st2val1) == owners.default
+#    assert cfg.getowner(st2val2) == owners.default
     #
     cfg.od.stval1.st1val1.st1val1 = ['yes']
     cfg.od.stval1.st1val1.st2val1 = ['yes']
@@ -1012,8 +1012,8 @@ def test_masterslaves_submulti_dyndescription():
     assert cfg.od.stval2.st1val2.st2val2 == []
     assert cfg.getowner(st1val1) == owner
     assert cfg.getowner(st1val2) == owners.default
-    assert cfg.getowner(st2val1) == owner
-    assert cfg.getowner(st2val2) == owners.default
+    assert cfg.getowner(st2val1, 0) == owner
+#    assert cfg.getowner(st2val2) == owners.default
 
 
 def test_masterslaves_consistency_ip_dyndescription():
@@ -1085,8 +1085,8 @@ def test_masterslaves_callback_dyndescription():
     assert cfg.od.stval2.st1val2.st2val2 == []
     assert cfg.getowner(st1val1) == owner
     assert cfg.getowner(st1val2) == owners.default
-    assert cfg.getowner(st2val1) == owner
-    assert cfg.getowner(st2val2) == owners.default
+    assert cfg.getowner(st2val1, 0) == owner
+#    assert cfg.getowner(st2val2) == owners.default
     #
     cfg.od.stval1.st1val1.st1val1.pop(0)
     assert cfg.od.stval1.st1val1.st1val1 == []
@@ -1095,13 +1095,13 @@ def test_masterslaves_callback_dyndescription():
     assert cfg.od.stval2.st1val2.st2val2 == []
     assert cfg.getowner(st1val1) == owner
     assert cfg.getowner(st1val2) == owners.default
-    assert cfg.getowner(st2val1) == owner
-    assert cfg.getowner(st2val2) == owners.default
+#    assert cfg.getowner(st2val1) == owner
+#    assert cfg.getowner(st2val2) == owners.default
     #
     cfg.od.stval1.st1val1.st1val1 = ['yes']
     cfg.od.stval1.st1val1.st2val1 = ['yes']
     assert cfg.getowner(st1val1) == owner
-    assert cfg.getowner(st2val1) == owner
+    assert cfg.getowner(st2val1, 0) == owner
     del(cfg.od.stval1.st1val1.st2val1)
     assert cfg.getowner(st1val1) == owner
     assert cfg.getowner(st1val2) == owners.default
@@ -1159,31 +1159,6 @@ def test_masterslaves_callback_nomulti_dyndescription():
     assert cfg.od.stval1.st1val1.st2val1 == ['val']
 
 
-def test_masterslaves_callback_multi_dyndescription():
-    v1 = StrOption('v1', '', multi=True)
-    st1 = StrOption('st1', "", multi=True)
-    st2 = StrOption('st2', "", multi=True, callback=return_dynval, callback_params={'': ((v1, False),)})
-    stm = OptionDescription('st1', '', [st1, st2])
-    stm.impl_set_group_type(groups.master)
-    st = DynOptionDescription('st', '', [stm], callback=return_list)
-    od = OptionDescription('od', '', [st])
-    od2 = OptionDescription('od', '', [od, v1])
-    cfg = Config(od2)
-    assert cfg.od.stval1.st1val1.st1val1 == []
-    assert cfg.od.stval1.st1val1.st2val1 == []
-    cfg.od.stval1.st1val1.st1val1.append('yes')
-    assert cfg.od.stval1.st1val1.st1val1 == ['yes']
-    assert cfg.od.stval1.st1val1.st2val1 == [None]
-    cfg.od.stval1.st1val1.st2val1 = ['no']
-    cfg.v1 = ['no', 'no', 'no']
-    cfg.od.stval1.st1val1.st1val1.append('yes')
-    assert cfg.od.stval1.st1val1.st1val1 == ['yes', 'yes']
-    assert cfg.od.stval1.st1val1.st2val1 == ['no', 'no']
-    cfg.od.stval1.st1val1.st1val1.pop(1)
-    assert cfg.od.stval1.st1val1.st1val1 == ['yes']
-    assert cfg.od.stval1.st1val1.st2val1 == ['no']
-
-
 def test_masterslaves_callback_samegroup_dyndescription():
     st1 = StrOption('st1', "", multi=True)
     st2 = StrOption('st2', "", multi=True)
@@ -1242,11 +1217,11 @@ def test_masterslaves_callback_samegroup_dyndescription():
                                'od.stval2.st1val2.st2val2': [],
                                'od.stval2.st1val2.st3val2': []}
     assert cfg.getowner(st1val1) == owner
+    assert cfg.getowner(st2val1, 0) == owner
+    assert cfg.getowner(st3val1, 0) == owners.default
     assert cfg.getowner(st1val2) == owners.default
-    assert cfg.getowner(st2val1) == owner
-    assert cfg.getowner(st2val2) == owners.default
-    assert cfg.getowner(st3val1) == owners.default
-    assert cfg.getowner(st3val2) == owners.default
+    #assert cfg.getowner(st2val2) == owners.default
+    #assert cfg.getowner(st3val2) == owners.default
 
 
 def test_state_config():
index b395fcb..b9487fb 100644 (file)
@@ -295,13 +295,13 @@ def test_meta_master_slaves_owners():
     assert meta.conf1.getowner(netmask_admin_eth0) == owners.default
     meta.netmask_admin_eth0 = ['255.255.255.0']
     assert meta.conf1.getowner(ip_admin_eth0) == owners.meta
-    assert meta.conf1.getowner(netmask_admin_eth0) == owners.meta
+    assert meta.conf1.getowner(netmask_admin_eth0, 0) == owners.meta
     meta.netmask_admin_eth0 = ['255.255.0.0']
     assert meta.conf1.getowner(ip_admin_eth0) == owners.meta
-    assert meta.conf1.getowner(netmask_admin_eth0) == owners.meta
+    assert meta.conf1.getowner(netmask_admin_eth0, 0) == owners.meta
     meta.conf1.ip_admin_eth0 = ['192.168.1.1']
     assert meta.conf1.getowner(ip_admin_eth0) == owners.user
-    assert meta.conf1.getowner(netmask_admin_eth0) == owners.default
+    assert meta.conf1.getowner(netmask_admin_eth0, 0) == owners.default
 
 
 def test_meta_force_default():
index 29c9ef1..5f51942 100644 (file)
@@ -6,6 +6,7 @@ from tiramisu.value import Multi
 from tiramisu.option import IntOption, StrOption, OptionDescription
 from tiramisu.config import Config
 from tiramisu.error import ConfigError, PropertiesOptionError
+from tiramisu.setting import groups
 
 import weakref
 from py.test import raises
index 6e6bb5f..a269702 100644 (file)
@@ -548,33 +548,6 @@ def test_callback_master_and_slaves_slave_cal():
     assert cfg.val1.val2 == ['val1', 'val2', 'val']
 
 
-def test_callback_master_and_slaves_slave_cal2():
-    val3 = StrOption('val3', "", ['val', 'val'], multi=True)
-    val1 = StrOption('val1', "", multi=True, callback=return_value, callback_params={'': ((val3, False),)})
-    val2 = StrOption('val2', "", ['val2', 'val2'], multi=True)
-    interface1 = OptionDescription('val1', '', [val1, val2])
-    interface1.impl_set_group_type(groups.master)
-    maconfig = OptionDescription('rootconfig', '', [interface1, val3])
-    cfg = Config(maconfig)
-    cfg.read_write()
-    assert cfg.val3 == ['val', 'val']
-    assert cfg.val1.val1 == ['val', 'val']
-    assert cfg.val1.val2 == ['val2', 'val2']
-    cfg.val3.pop(1)
-    # cannot remove slave's value because master is calculated
-    # so raise
-    raises(SlaveError, "cfg.val1.val1")
-    raises(SlaveError, "cfg.val1.val2")
-    cfg.val3 = ['val', 'val']
-    assert cfg.val3 == ['val', 'val']
-    assert cfg.val1.val1 == ['val', 'val']
-    assert cfg.val1.val2 == ['val2', 'val2']
-    raises(SlaveError, "cfg.val1.val1 = ['val']")
-    assert cfg.val3 == ['val', 'val']
-    assert cfg.val1.val1 == ['val', 'val']
-    assert cfg.val1.val2 == ['val2', 'val2']
-
-
 def test_callback_master_and_slaves_master_disabled():
     #properties must be transitive
     val1 = StrOption('val1', "", multi=True, properties=('disabled',))
@@ -643,35 +616,16 @@ def test_callback_master_and_slaves_slave_callback_disabled():
     cfg = Config(maconfig)
     cfg.read_write()
     assert cfg.val1.val1 == []
-    raises(ConfigError, "cfg.val1.val2")
+    assert cfg.val1.val2 == []
     cfg.val1.val1.append('yes')
     assert cfg.val1.val1 == ['yes']
     cfg.cfgimpl_get_settings().remove('disabled')
-    assert cfg.val1.val2 == [None]
+    raises(SlaveError, "cfg.val1.val2")
     cfg.val1.val2 = ['no']
     cfg.val1.val1.append('yes1')
-    cfg.val1.val2[1] = 'no1'
-    cfg.cfgimpl_get_settings().append('disabled')
-    cfg.val1.val1.pop(0)
-    assert cfg.val1.val1 == ['yes1']
-    assert cfg.val1.val2 == ['no1']
-
-
-def test_callback_master_and_slaves_slave_list():
-    val1 = StrOption('val1', "", multi=True)
-    val2 = StrOption('val2', "", multi=True, callback=return_list)
-    interface1 = OptionDescription('val1', '', [val1, val2])
-    interface1.impl_set_group_type(groups.master)
-    maconfig = OptionDescription('rootconfig', '', [interface1])
-    cfg = Config(maconfig)
-    cfg.read_write()
-    #len is equal to 2 for slave and 0 for master
     raises(SlaveError, "cfg.val1.val2")
-    cfg.val1.val1 = ['val1', 'val2']
-    assert cfg.val1.val1 == ['val1', 'val2']
-    assert cfg.val1.val2 == ['val', 'val']
-    #wrong len
-    raises(SlaveError, "cfg.val1.val1 = ['val1']")
+    cfg.cfgimpl_get_settings().append('disabled')
+    raises(ConfigError, "cfg.val1.val1.pop(0)")
 
 
 def test_callback_master_and_slaves_value():
@@ -687,35 +641,40 @@ def test_callback_master_and_slaves_value():
     cfg = Config(maconfig)
     cfg.read_write()
     cfg.val4 == ['val10', 'val11']
-    raises(SlaveError, "cfg.val1.val1")
-    raises(SlaveError, "cfg.val1.val2")
-    raises(SlaveError, "cfg.val1.val3")
-    raises(SlaveError, "cfg.val1.val5")
-    raises(SlaveError, "cfg.val1.val6")
+    assert cfg.val1.val1 == []
+    assert cfg.val1.val2 == []
+    assert cfg.val1.val3 == []
+    assert cfg.val1.val5 == []
+    assert cfg.val1.val6 == []
+    #raises(SlaveError, "cfg.val1.val1")
+    #raises(SlaveError, "cfg.val1.val2")
+    #raises(SlaveError, "cfg.val1.val3")
+    #raises(SlaveError, "cfg.val1.val5")
+    #raises(SlaveError, "cfg.val1.val6")
     #
     #default calculation has greater length
-    raises(SlaveError, "cfg.val1.val1 = ['val1']")
+    #raises(SlaveError, "cfg.val1.val1 = ['val1']")
     #
     cfg.val1.val1 = ['val1', 'val2']
     assert cfg.val1.val1 == ['val1', 'val2']
     assert cfg.val1.val2 == ['val1', 'val2']
     assert cfg.val1.val3 == ['yes', 'yes']
-    assert cfg.val1.val5 == ['val10', 'val11']
-    assert cfg.val1.val6 == ['val10', 'val11']
+    raises(SlaveError, "cfg.val1.val5")
+    raises(SlaveError, "cfg.val1.val6")
     #
     cfg.val1.val1.append('val3')
     assert cfg.val1.val1 == ['val1', 'val2', 'val3']
     assert cfg.val1.val2 == ['val1', 'val2', 'val3']
     assert cfg.val1.val3 == ['yes', 'yes', 'yes']
-    assert cfg.val1.val5 == ['val10', 'val11', None]
-    assert cfg.val1.val6 == ['val10', 'val11', None]
+    raises(SlaveError, "cfg.val1.val5")
+    raises(SlaveError, "cfg.val1.val6")
     #
     cfg.val1.val1.pop(2)
     assert cfg.val1.val1 == ['val1', 'val2']
     assert cfg.val1.val2 == ['val1', 'val2']
     assert cfg.val1.val3 == ['yes', 'yes']
-    assert cfg.val1.val5 == ['val10', 'val11']
-    assert cfg.val1.val6 == ['val10', 'val11']
+    raises(SlaveError, "cfg.val1.val5")
+    raises(SlaveError, "cfg.val1.val6")
     #
     cfg.val1.val2 = ['val2', 'val2']
     cfg.val1.val3 = ['val2', 'val2']
@@ -728,121 +687,16 @@ def test_callback_master_and_slaves_value():
     cfg.val1.val1.append('val3')
     assert cfg.val1.val2 == ['val2', 'val2', 'val3']
     assert cfg.val1.val3 == ['val2', 'val2', 'yes']
-    assert cfg.val1.val5 == ['val2', 'val2', None]
-    assert cfg.val1.val6 == ['val2', 'val2', None]
+    raises(SlaveError, "cfg.val1.val5")
+    raises(SlaveError, "cfg.val1.val6")
     cfg.cfgimpl_get_settings().remove('cache')
     cfg.val4 = ['val10', 'val11', 'val12']
     #if value is already set, not updated !
     cfg.val1.val1.pop(2)
     cfg.val1.val1.append('val3')
     cfg.val1.val1 = ['val1', 'val2', 'val3']
-    assert cfg.val1.val5 == ['val2', 'val2', 'val12']
-    assert cfg.val1.val6 == ['val2', 'val2', 'val12']
-
-
-def test_callback_master_and_slaves_disabled():
-    val = BoolOption('val', '', multi=True)
-    val1 = StrOption('val1', "", multi=True)
-    val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val, False),)})
-    interface1 = OptionDescription('val1', '', [val1, val2])
-    interface1.impl_set_group_type(groups.master)
-    maconfig = OptionDescription('rootconfig', '', [val, interface1])
-    cfg = Config(maconfig)
-    cfg.read_write()
-    #
-    assert cfg.val == []
-    assert cfg.val1.val1 == []
-    assert cfg.val1.val2 == []
-    cfg.val1.val1.append('val1')
-    assert cfg.val == []
-    assert cfg.val1.val1 == ['val1']
-    assert cfg.val1.val2 == [None]
-    #
-    cfg.val.append(True)
-    cfg.cfgimpl_get_settings()[cfg.cfgimpl_get_description().val1.val2].append('disabled')
-    assert cfg.val == [True]
-    assert cfg.val1.val1 == ['val1']
-    raises(PropertiesOptionError, 'cfg.val1.val2')
-    #
-    cfg.val1.val1.append('val1_1')
-    assert cfg.val == [True]
-    assert cfg.val1.val1 == ['val1', 'val1_1']
-    raises(PropertiesOptionError, 'cfg.val1.val2')
-    #
-    cfg.cfgimpl_get_settings()[cfg.cfgimpl_get_description().val1.val2].remove('disabled')
-    assert cfg.val == [True]
-    assert cfg.val1.val1 == ['val1', 'val1_1']
-    raises(ValueError, 'cfg.val1.val2')
-    #
-    cfg.val = []
-    assert cfg.val1.val1 == ['val1', 'val1_1']
-    assert cfg.val1.val2 == [None, None]
-    #
-    cfg.val1.val2 = [None, None]
-    cfg.val = [True, True]
-    cfg.cfgimpl_get_settings()[cfg.cfgimpl_get_description().val1.val2].append('disabled')
-    cfg.val1.val1.pop(1)
-    assert cfg.val == [True, True]
-    assert cfg.val1.val1 == ['val1']
-    raises(PropertiesOptionError, 'cfg.val1.val2')
-    #
-    cfg.val1.val1.append('val1_2')
-    assert cfg.val == [True, True]
-    assert cfg.val1.val1 == ['val1', 'val1_2']
-    raises(PropertiesOptionError, 'cfg.val1.val2')
-    #
-    cfg.cfgimpl_get_settings()[cfg.cfgimpl_get_description().val1.val2].remove('disabled')
-    assert cfg.val == [True, True]
-    assert cfg.val1.val1 == ['val1', 'val1_2']
-    #[None, None] + pop() + append() => [None, True]
-    raises(ValueError, 'assert cfg.val1.val2')
-
-
-def test_callback_master_and_slaves_requires():
-    val = StrOption('val', '', 'val')
-    valreq = StrOption('valreq', '', 'val')
-    val1 = StrOption('val1', "", multi=True)
-    val2 = StrOption('val2', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
-    val3 = StrOption('val3', "", multi=True, callback=return_value, callback_params={'': ('yes',)})
-    val4 = StrOption('val4', '', multi=True, default=[])
-    val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val4, False),)})
-    val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val, False),)},
-                     requires=({'option': valreq, 'expected': 'val_disabled', 'action': 'disabled'},))
-    interface1 = OptionDescription('val1', '', [val1, val2, val3, val5, val6])
-    interface1.impl_set_group_type(groups.master)
-    maconfig = OptionDescription('rootconfig', '', [val, valreq, interface1, val4])
-    cfg = Config(maconfig)
-    cfg.read_write()
-    assert cfg.val1.val1 == []
-    assert cfg.val1.val2 == []
-    assert cfg.val1.val3 == []
-    assert cfg.val1.val5 == []
-    assert cfg.val1.val6 == []
-    #
-    cfg.val1.val1 = ['val1']
-    assert cfg.val1.val1 == ['val1']
-    assert cfg.val1.val2 == ['val1']
-    assert cfg.val1.val3 == ['yes']
-    assert cfg.val1.val5 == [None]
-    assert cfg.val1.val6 == ['val']
-    cfg.val4 = ['val10']
-    assert cfg.val1.val5 == ['val10']
-    #
-    cfg.valreq = 'val_disabled'
-    assert cfg.val1.val1 == ['val1']
-    assert cfg.val1.val2 == ['val1']
-    assert cfg.val1.val3 == ['yes']
-    assert cfg.val1.val5 == ['val10']
-    raises(PropertiesOptionError, 'cfg.val1.val6')
-    assert cfg.make_dict() == {'val1.val2': ['val1'], 'val1.val3': ['yes'], 'val1.val1': ['val1'], 'val1.val5': ['val10'], 'val': 'val', 'valreq': 'val_disabled', 'val4': ['val10']}
-    #
-    cfg.cfgimpl_get_settings()[cfg.cfgimpl_get_description().val].append('disabled')
-    cfg.cfgimpl_get_settings()[cfg.cfgimpl_get_description().val1.val3].append('disabled')
-    raises(PropertiesOptionError, 'cfg.val1.val6')
-    assert cfg.make_dict() == {'val1.val2': ['val1'], 'val1.val1': ['val1'], 'val1.val5': ['val10'], 'val4': ['val10'], 'valreq': 'val_disabled'}
-    #
-    cfg.valreq = 'val'
-    raises(ConfigError, 'cfg.val1.val6')
+    raises(SlaveError, "cfg.val1.val5")
+    raises(SlaveError, "cfg.val1.val6")
 
 
 def test_callback_master():
@@ -853,42 +707,6 @@ def test_callback_master():
     raises(ValueError, "interface1.impl_set_group_type(groups.master)")
 
 
-def test_callback_master_and_other_master_slave():
-    val1 = StrOption('val1', "", multi=True)
-    val2 = StrOption('val2', "", multi=True)
-    val3 = StrOption('val3', "", multi=True)
-    val4 = StrOption('val4', '', multi=True, default=['val10', 'val11'])
-    val5 = StrOption('val5', "", multi=True, callback=return_value, callback_params={'': ((val1, False),)})
-    val6 = StrOption('val6', "", multi=True, callback=return_value, callback_params={'': ((val2, False),)})
-    interface1 = OptionDescription('val1', '', [val1, val2, val3])
-    interface1.impl_set_group_type(groups.master)
-    interface2 = OptionDescription('val4', '', [val4, val5, val6])
-    interface2.impl_set_group_type(groups.master)
-    maconfig = OptionDescription('rootconfig', '', [interface1, interface2])
-    cfg = Config(maconfig)
-    cfg.read_write()
-    assert cfg.val4.val4 == ['val10', 'val11']
-    assert cfg.val4.val5 == [None, None]
-    assert cfg.val4.val6 == [None, None]
-    cfg.val1.val1 = ['yes']
-    assert cfg.val4.val4 == ['val10', 'val11']
-    assert cfg.val4.val5 == ['yes', None]
-    assert cfg.val4.val6 == [None, None]
-    cfg.val1.val2 = ['no']
-    assert cfg.val4.val4 == ['val10', 'val11']
-    assert cfg.val4.val5 == ['yes', None]
-    assert cfg.val4.val6 == ['no', None]
-    cfg.val1.val1 = ['yes', 'yes', 'yes']
-    cfg.val1.val2 = ['no', 'no', 'no']
-    raises(SlaveError, "cfg.val4.val4")
-    raises(SlaveError, "cfg.val4.val5")
-    raises(SlaveError, "cfg.val4.val6")
-    cfg.val4.getattr('val4', validate=False).append('val12')
-    assert cfg.val4.val4 == ['val10', 'val11', 'val12']
-    assert cfg.val4.val5 == ['yes', 'yes', 'yes']
-    assert cfg.val4.val6 == ['no', 'no', 'no']
-
-
 #FIXME: slave est un symlink
 
 
index 36d06d4..ca65579 100644 (file)
@@ -361,16 +361,16 @@ def test_reset_values_with_master_and_slaves_default():
     cfg.ip_admin_eth0.ip_admin_eth0[0] = "192.168.230.146"
     cfg.ip_admin_eth0.netmask_admin_eth0[0] = "255.255.255.0"
     assert cfg.getowner(ip_admin_eth0) == owner
-    assert cfg.getowner(netmask_admin_eth0) == owner
+    assert cfg.getowner(netmask_admin_eth0, 0) == 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.getowner(netmask_admin_eth0, 0) == owners.default
     assert cfg.ip_admin_eth0.ip_admin_eth0 == ['192.168.230.145']
     assert cfg.ip_admin_eth0.netmask_admin_eth0 == [None]
 
     cfg.ip_admin_eth0.netmask_admin_eth0[0] = "255.255.255.0"
     assert cfg.getowner(ip_admin_eth0) == owners.default
-    assert cfg.getowner(netmask_admin_eth0) == owner
+    assert cfg.getowner(netmask_admin_eth0, 0) == 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
index ab9c03b..b97ec2a 100644 (file)
@@ -417,10 +417,10 @@ def test_reset_values_with_master_and_slaves_submulti():
     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
+    assert cfg.getowner(netmask_admin_eth0, 0) == 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.getowner(netmask_admin_eth0) == owners.default
     assert cfg.ip_admin_eth0.ip_admin_eth0 == []
     assert cfg.ip_admin_eth0.netmask_admin_eth0 == []
 
index 9096f8b..73a8503 100644 (file)
@@ -18,7 +18,8 @@
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
 "enables us to carry out a calculation and return an option's value"
-from tiramisu.error import PropertiesOptionError, ConfigError, ContextError
+from tiramisu.error import PropertiesOptionError, ConfigError, ContextError, \
+    SlaveError
 from tiramisu.i18n import _
 from tiramisu.setting import undefined
 # ____________________________________________________________
@@ -237,6 +238,9 @@ def carry_out_calculation(option, context, callback, callback_params,
         ret = calculate(callback, args, kwargs)
         if callback_params != {}:
             if isinstance(ret, list) and index is not undefined:
+                if option.impl_is_master_slaves('slave'):
+                    raise SlaveError(_("callback cannot return a list for a "
+                                       "slave option ({0})").format(path))
                 if len(ret) < index + 1:
                     ret = None
                 else:
index ead89db..d7dd69f 100644 (file)
@@ -518,7 +518,7 @@ class _CommonConfig(SubConfig):
         "read write is a global config's setting, see `settings.py`"
         self.cfgimpl_get_settings().read_write()
 
-    def getowner(self, opt, force_permissive=False):
+    def getowner(self, opt, index=None, force_permissive=False):
         """convenience method to retrieve an option's owner
         from the config itself
         """
@@ -527,7 +527,7 @@ class _CommonConfig(SubConfig):
                 not isinstance(opt, DynSymLinkOption):  # pragma: optional cover
             raise TypeError(_('opt in getowner must be an option not {0}'
                               '').format(type(opt)))
-        return self.cfgimpl_get_values().getowner(opt,
+        return self.cfgimpl_get_values().getowner(opt, index=index,
                                                   force_permissive=force_permissive)
 
     def unwrap_from_path(self, path, force_permissive=False):
@@ -608,12 +608,12 @@ class _CommonConfig(SubConfig):
         fake_config = Config(self._impl_descr, persistent=False,
                              force_values=get_storages_validation(),
                              force_settings=self.cfgimpl_get_settings())
-        fake_config.cfgimpl_get_values()._p_._values = self.cfgimpl_get_values()._p_.get_modified_values()
+        fake_config.cfgimpl_get_values()._p_._values = self.cfgimpl_get_values()._p_._values
         return fake_config
 
     def duplicate(self):
         config = Config(self._impl_descr)
-        config.cfgimpl_get_values()._p_._values = self.cfgimpl_get_values()._p_.get_modified_values()
+        config.cfgimpl_get_values()._p_._values = self.cfgimpl_get_values()._p_._values
         config.cfgimpl_get_settings()._p_._properties = self.cfgimpl_get_settings()._p_.get_modified_properties()
         config.cfgimpl_get_settings()._p_._permissives = self.cfgimpl_get_settings()._p_.get_modified_permissives()
         return config
index 155bc31..3f1867e 100644 (file)
@@ -98,7 +98,8 @@ class Base(StorageBase):
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
                  callback_params=None, validator=None, validator_params=None,
-                 properties=None, warnings_only=False, extra=None, allow_empty_list=undefined):
+                 properties=None, warnings_only=False, extra=None,
+                 allow_empty_list=undefined):
         if not valid_name(name):  # pragma: optional cover
             raise ValueError(_("invalid name: {0} for option").format(name))
         if requires is not None:
@@ -108,7 +109,7 @@ class Base(StorageBase):
             calc_properties = frozenset()
             requires = undefined
         if not multi and default_multi is not None:  # pragma: optional cover
-            raise ValueError(_("default_multi is set whereas multi is False"
+            raise ValueError(_("default_multi is set whereas multi is False"
                              " in option: {0}").format(name))
         if multi is True:
             _multi = 0
@@ -133,7 +134,8 @@ class Base(StorageBase):
                                  'requirement {0}'.format(
                                      list(set_forbidden_properties)))
         StorageBase.__init__(self, name, _multi, warnings_only, doc, extra,
-                             calc_properties, requires, properties, allow_empty_list)
+                             calc_properties, requires, properties,
+                             allow_empty_list)
         if multi is not False and default is None:
             default = []
         self.impl_validate(default)
index 19a756a..eaf8579 100644 (file)
@@ -48,6 +48,10 @@ class MasterSlaves(object):
             if child.impl_getname() == name:
                 self.master = child
             else:
+                if child.impl_getdefault() != []:
+                    raise ValueError(_("not allowed default value for option {0} "
+                                    "in group {1}").format(child.impl_getname(),
+                                                            name))
                 slaves.append(child)
         if self.master is None:  # pragma: optional cover
             raise ValueError(_('master group with wrong'
@@ -107,7 +111,7 @@ class MasterSlaves(object):
     def pop(self, opt, values, index):
         for slave in self.getslaves(opt):
             if not values.is_default_owner(slave, validate_properties=False,
-                                           validate_meta=False):
+                                           validate_meta=False, index=index):
                 values._get_cached_item(slave, validate=False,
                                         validate_properties=False
                                         ).pop(index, force=True)
@@ -139,17 +143,19 @@ class MasterSlaves(object):
             for slave in self.getslaves(opt):
                 try:
                     slave_path = slave.impl_getpath(values._getcontext())
-                    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,
-                                                                  self_properties=self_properties)
-                    slavelen = len(slave_value)
+                    slavelen = values._p_.get_max_length(slave_path)
+                    #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,
+                    #                                              self_properties=self_properties,
+                    #                                              masterlen=masterlen)
+                    #slavelen = len(slave_value)
                     self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
                 except ConfigError:  # pragma: optional cover
                     pass
@@ -186,39 +192,44 @@ class MasterSlaves(object):
                                     undefined, force_permissive,
                                     master=master)
         master_is_meta = values._is_meta(opt, masterp)
-        value = values._get_validated_value(opt, path, validate,
-                                            force_permissive,
-                                            force_properties,
-                                            validate_properties,
-                                            None,  # not undefined
-                                            with_meta=master_is_meta,
-                                            self_properties=self_properties)
+        #value = values._get_validated_value(opt, path, validate,
+        #                                    force_permissive,
+        #                                    force_properties,
+        #                                    validate_properties,
+        #                                    None,  # not undefined
+        #                                    with_meta=master_is_meta,
+        #                                    self_properties=self_properties)
         #if slave, had values until master's one
-        path = opt.impl_getpath(context)
-        valuelen = len(value)
-        if validate:
-            self.validate_slave_length(masterlen, valuelen,
-                                       opt.impl_getname(), opt)
-        if valuelen < masterlen:
-            for num in range(0, masterlen - valuelen):
-                index = valuelen + num
-                value.append(values._get_validated_value(opt, path, True,
-                                                         False, None,
-                                                         validate_properties=False,
-                                                         with_meta=master_is_meta,
-                                                         index=index,
-                                                         self_properties=self_properties),
-                             setitem=False,
-                             force=True,
-                             validate=validate)
-            if validate_properties:
-                context.cfgimpl_get_settings().validate_properties(opt, False,
-                                                                   False,
-                                                                   value=value,
-                                                                   path=path,
-                                                                   force_permissive=force_permissive,
-                                                                   force_properties=force_properties,
-                                                                   setting_properties=setting_properties)
+        #path = opt.impl_getpath(context)
+        #valuelen = len(value)
+        #if validate:
+        #    self.validate_slave_length(masterlen, valuelen,
+        #                               opt.impl_getname(), opt)
+        #if valuelen < masterlen:
+
+        #FIXME voir si pas de plus grande valeur !
+        value = values._get_multi(opt, path)
+        for index in range(0, masterlen):
+            #index = valuelen + num
+            value.append(values._get_validated_value(opt, path, validate,
+                                                     force_permissive, force_properties,
+                                                     validate_properties,
+                                                     with_meta=master_is_meta,
+                                                     index=index,
+                                                     self_properties=self_properties,
+                                                     masterlen=masterlen),
+                         setitem=False,
+                         force=True,
+                         validate=validate)
+        #FIXME hu?
+        if validate_properties:
+            context.cfgimpl_get_settings().validate_properties(opt, False,
+                                                               False,
+                                                               value=value,
+                                                               path=path,
+                                                               force_permissive=force_permissive,
+                                                               force_properties=force_properties,
+                                                               setting_properties=setting_properties)
         return value
 
     def setitem(self, values, opt, value, path):
@@ -228,14 +239,17 @@ class MasterSlaves(object):
             base_path = '.'.join(path.split('.')[:-1]) + '.'
             for slave in self.getslaves(opt):
                 slave_path = base_path + slave.impl_getname()
-                slave_value = values._get_validated_value(slave,
-                                                          slave_path,
-                                                          False,
-                                                          False,
-                                                          None, False,
-                                                          None)  # not undefined
-                slavelen = len(slave_value)
+                slavelen = values._p_.get_max_length(slave_path)
                 self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
+                #slave_value = values._get_validated_value(slave,
+                #                                          slave_path,
+                #                                          False,
+                #                                          False,
+                #                                          None, False,
+                #                                          None,
+                #                                          masterlen=masterlen)  # not undefined
+                #slavelen = len(slave_value)
+                #self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
         else:
             self.validate_slave_length(self.get_length(values, opt,
                                                        slave_path=path), len(value),
index 746608c..8e26688 100644 (file)
@@ -433,7 +433,8 @@ class Settings(object):
                             value=None, force_permissive=False,
                             force_properties=None,
                             setting_properties=undefined,
-                            self_properties=undefined):
+                            self_properties=undefined,
+                            index=None):
         """
         validation upon the properties related to `opt_or_descr`
 
@@ -480,7 +481,7 @@ class Settings(object):
         else:
             if 'mandatory' in properties and \
                     not self._getcontext().cfgimpl_get_values()._isempty(
-                        opt_or_descr, value):
+                        opt_or_descr, value, index=index):
                 properties.remove('mandatory')
             elif opt_or_descr.impl_is_multi() and \
                     not is_write and 'empty' in forced_properties and \
index ec24567..b3c135d 100644 (file)
@@ -47,7 +47,7 @@ class StorageBase(object):
                  '_master_slaves',
                  '_choice_values',
                  '_choice_values_params',
-                 #autre
+                 #other
                  '_state_master_slaves',
                  '_state_callback',
                  '_state_callback_params',
index 15b91fd..7b663fd 100644 (file)
@@ -15,8 +15,8 @@
 # You should have received a copy of the GNU Lesser General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 # ____________________________________________________________
-from copy import copy
 from ..util import Cache
+from tiramisu.setting import undefined
 
 
 class Values(Cache):
@@ -25,52 +25,167 @@ class Values(Cache):
     def __init__(self, storage):
         """init plugin means create values storage
         """
-        self._values = {}
+        self._values = (tuple(), tuple(), tuple(), tuple())
         self._informations = {}
         # should init cache too
         super(Values, self).__init__(storage)
 
     # value
-    def setvalue(self, path, value, owner):
+    def setvalue(self, path, value, owner, index):
         """set value for a path
         a specified value must be associated to an owner
         """
-        self._values[path] = (owner, value)
+        values = []
+        vidx = None
 
-    def getvalue(self, path):
+        def _setvalue_info(nb, idx, value, vidx):
+            lst = list(self._values[nb])
+            if idx is None:
+                if index is None or nb == 0:
+                    lst.append(value)
+                else:
+                    lst.append([value])
+            else:
+                if index is None or nb == 0:
+                    lst[idx] = value
+                else:
+                    if nb == 1:
+                        try:
+                            vidx = lst[idx].index(index)
+                        except ValueError:
+                            vidx = None
+                    if vidx is None:
+                        lst[idx].append(value)
+                    elif nb != 1:
+                        lst[idx][vidx] = value
+            values.append(tuple(lst))
+            return vidx
+        try:
+            idx = self._values[0].index(path)
+        except ValueError:
+            idx = None
+        vidx = _setvalue_info(0, idx, path, vidx)
+        vidx = _setvalue_info(1, idx, index, vidx)
+        vidx = _setvalue_info(2, idx, value, vidx)
+        _setvalue_info(3, idx, owner, vidx)
+        self._values = tuple(values)
+
+    def getvalue(self, path, index=None):
         """get value for a path
         return: only value, not the owner
         """
-        return self._values[path][1]
+        return self._getvalue(path, 2, index)
 
-    def hasvalue(self, path):
+    def hasvalue(self, path, index=None):
         """if path has a value
         return: boolean
         """
-        return path in self._values
+        return path in self._values[0]
 
     def resetvalue(self, path):
         """remove value means delete value in storage
         """
-        del(self._values[path])
+        def _resetvalue(nb):
+            lst = list(self._values[nb])
+            lst.pop(idx)
+            values.append(tuple(lst))
+        values = []
+        idx = self._values[0].index(path)
+        _resetvalue(0)
+        _resetvalue(1)
+        _resetvalue(2)
+        _resetvalue(3)
+        self._values = tuple(values)
 
     def get_modified_values(self):
         """return all values in a dictionary
         example: {'path1': (owner, 'value1'), 'path2': (owner, 'value2')}
         """
-        return copy(self._values)
+        values = {}
+        for idx, path in enumerate(self._values[0]):
+            values[path] = (self._values[3][idx], self._values[2][idx])
+        return values
 
     # owner
-    def setowner(self, path, owner):
+    def setowner(self, path, owner, index=None):
         """change owner for a path
         """
-        self._values[path] = (owner, self._values[path][1])
+        idx = self._values[0].index(path)
+        if isinstance(self._values[3][idx], list):
+            if index is None:
+                raise ValueError('list but no index')
+            owner = list(self._values[3][idx])[index] = owner
+        elif index is not None:
+            raise ValueError('index set but not a list')
+        lst = list(self._values[3])
+        lst[idx] = owner
+        values = list(self._values)
+        values[3] = tuple(lst)
+        self._values = tuple(values)
+
+    def get_max_length(self, path):
+        try:
+            idx = self._values[0].index(path)
+        except ValueError:
+            return 0
+        return max(self._values[1][idx]) + 1
 
-    def getowner(self, path, default):
+    def getowner(self, path, default, index=None, only_default=False):
         """get owner for a path
         return: owner object
         """
-        return self._values.get(path, (default, None))[0]
+        if index is None:
+            if only_default:
+                if path in self._values[0]:
+                    return None
+                else:
+                    return default
+            val = self._getvalue(path, 3, index)
+            if val is None:
+                return default
+            return val
+        else:
+            value = self._getvalue(path, 3, index)
+            if value is None:
+                return default
+            else:
+                return value
+
+    def _getvalue(self, path, nb, index):
+        """
+        _values == ((path1, path2), ((value1_1, value1_2), value2), ((owner1_1, owner1_2), owner2), ((idx1_1, idx1_2), None))
+        """
+        try:
+            idx = self._values[0].index(path)
+        except ValueError:
+            value = None
+        else:
+            if isinstance(self._values[1][idx], list):
+                if index is None:
+                    raise ValueError('list but no index')
+            elif index is not None:
+                raise ValueError('index set but not a list')
+
+            if self._values[1][idx] is None:
+                if index is None:
+                    value = self._values[nb][idx]
+                else:
+                    value = self._values[nb][idx][index]
+            else:
+                if index is not None:
+                    try:
+                        subidx = self._values[1][idx].index(index)
+                        value = self._values[nb][idx][subidx]
+                    except ValueError:
+                        value = None
+                else:
+                    value = []
+                    for i in xrange(0, max(self._values[1][idx])):
+                        if i in self._values[1][idx]:
+                            value.append(self._values[nb][idx][self._values[1][idx].index(i)])
+                        else:
+                            value.append(undefined)
+        return value
 
     def set_information(self, key, value):
         """updates the information's attribute
index 1657f18..170232f 100644 (file)
@@ -47,11 +47,20 @@ class Cache(object):
                 value = getattr(self, slot)
                 #value has owners object, need 'str()' it
                 if slot == '_values':
-                    _value = {}
-                    for key, values in value.items():
-                        vals = list(values)
-                        vals[0] = str(vals[0])
-                        _value[key] = tuple(vals)
+                    _value = []
+                    _value.append(value[0])
+                    _value.append(value[1])
+                    str_owner = []
+                    _value.append(value[2])
+                    for owner in value[3]:
+                        if isinstance(owner, list):
+                            str_owners = []
+                            for subowner in owner:
+                                str_owners.append(str(subowner))
+                            str_owner.append(str_owners)
+                        else:
+                            str_owner.append(str(owner))
+                    _value.append(str_owner)
                     states[slot] = _value
                 else:
                     states[slot] = value
@@ -60,19 +69,32 @@ class Cache(object):
         return states
 
     def __setstate__(self, states):
+        def convert_owner(owner):
+            try:
+                owner = getattr(owners, owner)
+            except AttributeError:
+                owners.addowner(owner)
+                owner = getattr(owners, owner)
+            return owner
+
         for key, value in states.items():
             #value has owners object, need to reconstruct it
             if key == '_values':
-                _value = {}
-                for key_, values_ in value.items():
-                    vals = list(values_)
-                    try:
-                        vals[0] = getattr(owners, vals[0])
-                    except AttributeError:
-                        owners.addowner(vals[0])
-                        vals[0] = getattr(owners, vals[0])
-                    _value[key_] = tuple(vals)
-                value = _value
+                _value = []
+                _value.append(value[0])
+                _value.append(value[1])
+                _value.append(value[2])
+                obj_owner = []
+                for owner in value[3]:
+                    if isinstance(owner, list):
+                        obj_owners = []
+                        for subowner in owner:
+                            obj_owners.append(convert_owner(subowner))
+                        obj_owner.append(tuple(obj_owners))
+                    else:
+                        obj_owner.append(convert_owner(owner))
+                _value.append(tuple(obj_owner))
+                value = tuple(_value)
             setattr(self, key, value)
 
     def setcache(self, path, val, time):
index 18b3afa..aa8b5cc 100644 (file)
@@ -54,33 +54,10 @@ class Values(object):
             raise ConfigError(_('the context does not exist anymore'))
         return context
 
-    def _getvalue(self, opt, path, is_default, index=undefined,
-                  with_meta=True, self_properties=undefined):
-        """actually retrieves the value
-
-        :param opt: the `option.Option()` object
-        :returns: the option's value (or the default value if not set)
-        """
-        if opt.impl_is_optiondescription():  # pragma: optional cover
-            raise ValueError(_('optiondescription has no value'))
+    def _get_multi(self, opt, path):
+        return Multi([], self.context, opt, path)
 
-        if self_properties is undefined:
-            self_properties = self._getcontext().cfgimpl_get_settings()._getproperties(
-                opt, path, read_write=False)
-        force_default = 'frozen' in self_properties and \
-            'force_default_on_freeze' in self_properties
-        if not is_default and not force_default:
-            value = self._p_.getvalue(path)
-            if index is not undefined:
-                try:
-                    return value[index]
-                except IndexError:
-                    #value is smaller than expected
-                    #so return default value
-                    pass
-            else:
-                return value
-        #so default value
+    def _getdefaultvalue(self, opt, path, with_meta, index):
         # if value has callback and is not set
         if opt.impl_has_callback():
             callback, callback_params = opt.impl_get_callback()
@@ -126,6 +103,56 @@ class Values(object):
                     value = opt.impl_getdefault_multi()
         return value
 
+    def _getvalue(self, opt, path, is_default, index=undefined,
+                  with_meta=True, self_properties=undefined,
+                  masterlen=undefined):
+        """actually retrieves the value
+
+        :param opt: the `option.Option()` object
+        :returns: the option's value (or the default value if not set)
+        """
+        if opt.impl_is_optiondescription():  # pragma: optional cover
+            raise ValueError(_('optiondescription has no value'))
+
+        if self_properties is undefined:
+            self_properties = self._getcontext().cfgimpl_get_settings()._getproperties(
+                opt, path, read_write=False)
+        force_default = 'frozen' in self_properties and \
+            'force_default_on_freeze' in self_properties
+        if not is_default and not force_default:
+            if opt.impl_is_master_slaves('slave'):
+            #if masterlen is not undefined:
+                if index is undefined:
+                    value = []
+                    vals = self._p_.getvalue(path)
+                    length = max(masterlen, len(vals))
+                    for idx in xrange(0, length):
+                        try:
+                            if vals[idx] is undefined:
+                                value.append(self._getdefaultvalue(opt, path, with_meta, idx))
+                            else:
+                                value.append(vals[idx])
+                        except IndexError:
+                            try:
+                                value.append(self._getdefaultvalue(opt, path, with_meta, idx))
+                            except IndexError:
+                                value.append(None)
+                else:
+                    value = self._p_.getvalue(path, index)
+                return value
+            else:
+                value = self._p_.getvalue(path)
+                if index is not undefined:
+                    try:
+                        return value[index]
+                    except IndexError:
+                        #value is smaller than expected
+                        #so return default value
+                        pass
+                else:
+                    return value
+        return self._getdefaultvalue(opt, path, with_meta, index)
+
     def get_modified_values(self):
         context = self._getcontext()
         if context._impl_descr is not None:
@@ -175,13 +202,13 @@ class Values(object):
         if hasvalue:
             self._p_.resetvalue(path)
 
-    def _isempty(self, opt, value, force_allow_empty_list=False):
+    def _isempty(self, opt, value, force_allow_empty_list=False, index=None):
         "convenience method to know if an option is empty"
         if value is undefined:
             return False
         else:
             empty = opt._empty
-            if opt.impl_is_multi():
+            if index in [None, undefined] and opt.impl_is_multi():
                 if force_allow_empty_list:
                     allow_empty_list = True
                 else:
@@ -273,7 +300,7 @@ class Values(object):
                              force_properties, validate_properties,
                              index=undefined, submulti_index=undefined,
                              with_meta=True, setting_properties=undefined,
-                             self_properties=undefined):
+                             self_properties=undefined, masterlen=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 []
         """
@@ -286,7 +313,8 @@ class Values(object):
         is_default = self._is_default_owner(opt, path,
                                             validate_properties=False,
                                             validate_meta=False,
-                                            self_properties=self_properties)
+                                            self_properties=self_properties,
+                                            index=index)
         try:
             if index is None:
                 gv_index = undefined
@@ -294,7 +322,8 @@ class Values(object):
                 gv_index = index
             value = self._getvalue(opt, path, is_default, index=gv_index,
                                    with_meta=with_meta,
-                                   self_properties=self_properties)
+                                   self_properties=self_properties,
+                                   masterlen=masterlen)
             config_error = None
         except ConfigError as err:
             # For calculating properties, we need value (ie for mandatory
@@ -309,8 +338,7 @@ class Values(object):
             # value is not set, for 'undefined' (cannot set None because of
             # mandatory property)
             value = undefined
-
-        if config_error is None:
+        else:
             if index is undefined:
                 force_index = None
             else:
@@ -358,7 +386,8 @@ class Values(object):
                                         force_permissive=force_permissive,
                                         force_properties=force_properties,
                                         setting_properties=setting_properties,
-                                        self_properties=self_properties)
+                                        self_properties=self_properties,
+                                        index=index)
         if config_error is not None:
             raise config_error
         return value
@@ -394,7 +423,7 @@ class Values(object):
 
     def _setvalue(self, opt, path, value, force_permissive=False,
                   is_write=True, validate_properties=True,
-                  setting_properties=undefined):
+                  setting_properties=undefined, index=None):
         context = self._getcontext()
         context.cfgimpl_reset_cache()
         if validate_properties:
@@ -410,7 +439,15 @@ class Values(object):
                     if isinstance(val, SubMulti):
                         value[idx] = list(val)
         owner = context.cfgimpl_get_settings().getowner()
-        self._p_.setvalue(path, value, owner)
+        if opt.impl_is_master_slaves('slave') and index is None:
+            try:
+                self._p_.resetvalue(path)
+            except ValueError:
+                pass
+            for idx, val in enumerate(value):
+                self._p_.setvalue(path, val, owner, idx)
+        else:
+            self._p_.setvalue(path, value, owner, index)
 
     def _is_meta(self, opt, path):
         context = self._getcontext()
@@ -418,13 +455,13 @@ class Values(object):
         self_properties = setting._getproperties(opt, path, read_write=False)
         if 'frozen' in self_properties and 'force_default_on_freeze' in self_properties:
             return False
-        if self._p_.getowner(path, owners.default) is not owners.default:
+        if self._p_.getowner(path, owners.default, only_default=True) is not owners.default:
             return False
         if context.cfgimpl_get_meta() is not None:
             return True
         return False
 
-    def getowner(self, opt, force_permissive=False):
+    def getowner(self, opt, index=None, force_permissive=False):
         """
         retrieves the option's owner
 
@@ -437,11 +474,12 @@ class Values(object):
                 not isinstance(opt, DynSymLinkOption):
             opt = opt._impl_getopt()
         path = opt.impl_getpath(self._getcontext())
-        return self._getowner(opt, path, force_permissive=force_permissive)
+        return self._getowner(opt, path, index=index, force_permissive=force_permissive)
 
     def _getowner(self, opt, path, validate_properties=True,
                   force_permissive=False, validate_meta=undefined,
-                  self_properties=undefined):
+                  self_properties=undefined, only_default=False,
+                  index=None):
         """get owner of an option
         """
         if not isinstance(opt, Option) and not isinstance(opt,
@@ -456,7 +494,7 @@ class Values(object):
         if validate_properties:
             self._getitem(opt, path, True, force_permissive, None, True,
                           self_properties=self_properties)
-        owner = self._p_.getowner(path, owners.default)
+        owner = self._p_.getowner(path, owners.default, only_default=only_default, index=index)
         if validate_meta is undefined:
             if opt.impl_is_master_slaves('slave'):
                 master = opt.impl_get_master_slaves().getmaster(opt)
@@ -467,7 +505,11 @@ class Values(object):
         if validate_meta:
             meta = context.cfgimpl_get_meta()
             if owner is owners.default and meta is not None:
-                owner = meta.cfgimpl_get_values()._getowner(opt, path)
+                owner = meta.cfgimpl_get_values()._getowner(opt, path,
+                                                            validate_properties=validate_properties,
+                                                            force_permissive=force_permissive,
+                                                            self_properties=self_properties,
+                                                            only_default=only_default, index=index)
         return owner
 
     def setowner(self, opt, owner):
@@ -481,9 +523,6 @@ class Values(object):
             raise TypeError(_("invalid generic owner {0}").format(str(owner)))
 
         path = opt.impl_getpath(self._getcontext())
-        self._setowner(opt, path, owner)
-
-    def _setowner(self, opt, path, owner):
         if not self._p_.hasvalue(path):  # pragma: optional cover
             raise ConfigError(_('no value for {0} cannot change owner to {1}'
                                 '').format(path, owner))
@@ -495,7 +534,7 @@ class Values(object):
         self._p_.setowner(path, owner)
 
     def is_default_owner(self, opt, validate_properties=True,
-                         validate_meta=True):
+                         validate_meta=True, index=None):
         """
         :param config: *must* be only the **parent** config
                        (not the toplevel config)
@@ -504,14 +543,18 @@ class Values(object):
         path = opt.impl_getpath(self._getcontext())
         return self._is_default_owner(opt, path,
                                       validate_properties=validate_properties,
-                                      validate_meta=validate_meta)
+                                      validate_meta=validate_meta, index=index)
 
     def _is_default_owner(self, opt, path, validate_properties=True,
-                          validate_meta=True, self_properties=undefined):
-        return self._getowner(opt, path, validate_properties,
-                              validate_meta=validate_meta,
-                              self_properties=self_properties) == \
-            owners.default
+                          validate_meta=True, self_properties=undefined,
+                          index=None):
+        if not opt.impl_is_master_slaves('slave'):
+            index = None
+        d = self._getowner(opt, path, validate_properties,
+                           validate_meta=validate_meta,
+                           self_properties=self_properties, only_default=True,
+                           index=index)
+        return d == owners.default
 
     def reset_cache(self, only_expired):
         """
@@ -640,7 +683,6 @@ class Values(object):
 
 # ____________________________________________________________
 # multi types
-
 class Multi(list):
     """multi options values container
     that support item notation for the values of multi options"""
@@ -708,7 +750,7 @@ class Multi(list):
             self._validate(value, fake_context, index, True)
         #assume not checking mandatory property
         super(Multi, self).__setitem__(index, value)
-        context.cfgimpl_get_values()._setvalue(self.opt, self.path, self, setting_properties=setting_properties)
+        self._store()
 
     #def __repr__(self, *args, **kwargs):
     #    return super(Multi, self).__repr__(*args, **kwargs)
@@ -750,7 +792,7 @@ class Multi(list):
             value.submulti = weakref.ref(self)
         super(Multi, self).append(value)
         if setitem:
-            self._store(force)
+            self._store(force=force)
 
     def sort(self, cmp=None, key=None, reverse=False):
         if self.opt.impl_is_master_slaves():
@@ -773,7 +815,6 @@ class Multi(list):
         self._store()
 
     def insert(self, index, value, validate=True):
-        #FIXME value 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()))
@@ -833,7 +874,7 @@ class Multi(list):
                                                       context.cfgimpl_get_values(), index)
         #set value without valid properties
         ret = super(Multi, self).pop(index)
-        self._store(force)
+        self._store(force=force)
         return ret
 
     def _store(self, force=False):