tiramisu/option.py:
authorEmmanuel Garette <egarette@cadoles.com>
Sat, 28 Sep 2013 15:05:01 +0000 (17:05 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Sat, 28 Sep 2013 15:05:01 +0000 (17:05 +0200)
  separate _consistencies (for Option) and _cache_consistencies (for OptionDescription)
  _launch_consistency need index for multi's option
  _cons_not_equal support multi options

tiramisu/value.py:
  Multi._validate support consistency

test/test_option_consistency.py
test/test_state.py
tiramisu/option.py
tiramisu/value.py

index 1c23dac..d5226db 100644 (file)
@@ -5,6 +5,7 @@ from tiramisu.setting import owners, groups
 from tiramisu.config import Config
 from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\
     BroadcastOption, SymLinkOption, OptionDescription
+from tiramisu.error import ConfigError
 
 
 def test_consistency_not_equal():
@@ -22,6 +23,60 @@ def test_consistency_not_equal():
     c.b = 2
 
 
+def test_consistency_not_equal_many_opts():
+    a = IntOption('a', '')
+    b = IntOption('b', '')
+    c = IntOption('c', '')
+    d = IntOption('d', '')
+    e = IntOption('e', '')
+    f = IntOption('f', '')
+    od = OptionDescription('od', '', [a, b, c, d, e, f])
+    a.impl_add_consistency('not_equal', b, c, d, e, f)
+    c = Config(od)
+    assert c.a is None
+    assert c.b is None
+    #
+    c.a = 1
+    del(c.a)
+    #
+    c.a = 1
+    raises(ValueError, "c.b = 1")
+    #
+    c.b = 2
+    raises(ValueError, "c.f = 2")
+    raises(ValueError, "c.f = 1")
+    #
+    c.d = 3
+    raises(ValueError, "c.f = 3")
+    raises(ValueError, "c.a = 3")
+    raises(ValueError, "c.c = 3")
+    raises(ValueError, "c.e = 3")
+
+
+def test_consistency_not_in_config():
+    a = IntOption('a', '')
+    b = IntOption('b', '')
+    a.impl_add_consistency('not_equal', b)
+    od1 = OptionDescription('od1', '', [a])
+    od2 = OptionDescription('od2', '', [b])
+    od = OptionDescription('root', '', [od1])
+    raises(ConfigError, "Config(od)")
+    od = OptionDescription('root', '', [od1, od2])
+    Config(od)
+    #with subconfig
+    raises(ConfigError, "Config(od.od1)")
+
+
+def test_consistency_afer_config():
+    a = IntOption('a', '')
+    b = IntOption('b', '')
+    od1 = OptionDescription('od1', '', [a])
+    od2 = OptionDescription('od2', '', [b])
+    od = OptionDescription('root', '', [od1, od2])
+    Config(od)
+    raises(AttributeError, "a.impl_add_consistency('not_equal', b)")
+
+
 def test_consistency_not_equal_symlink():
     a = IntOption('a', '')
     b = IntOption('b', '')
@@ -29,7 +84,7 @@ def test_consistency_not_equal_symlink():
     od = OptionDescription('od', '', [a, b, c])
     a.impl_add_consistency('not_equal', b)
     c = Config(od)
-    assert set(od._consistencies.keys()) == set([a, b])
+    assert set(od._cache_consistencies.keys()) == set([a, b])
 
 
 def test_consistency_not_equal_multi():
@@ -53,6 +108,14 @@ def test_consistency_default():
     raises(ValueError, "a.impl_add_consistency('not_equal', b)")
 
 
+def test_consistency_default_multi():
+    a = IntOption('a', '', [2, 1], multi=True)
+    b = IntOption('b', '', [1, 1], multi=True)
+    c = IntOption('c', '', [1, 2], multi=True)
+    raises(ValueError, "a.impl_add_consistency('not_equal', b)")
+    a.impl_add_consistency('not_equal', c)
+
+
 def test_consistency_default_diff():
     a = IntOption('a', '', 3)
     b = IntOption('b', '', 1)
@@ -99,7 +162,7 @@ def test_consistency_ip_netmask_error_multi():
     a = IPOption('a', '', multi=True)
     b = NetmaskOption('b', '')
     od = OptionDescription('od', '', [a, b])
-    raises(ValueError, "b.impl_add_consistency('ip_netmask', a)")
+    raises(ConfigError, "b.impl_add_consistency('ip_netmask', a)")
 
 
 def test_consistency_ip_netmask_multi():
@@ -170,11 +233,42 @@ def test_consistency_broadcast():
     b.impl_add_consistency('network_netmask', a)
     c.impl_add_consistency('broadcast', a, b)
     c = Config(od)
+    #first, test network_netmask
+    c.a = ['192.168.1.128']
+    raises(ValueError, "c.b = ['255.255.255.0']")
+    #
     c.a = ['192.168.1.0']
     c.b = ['255.255.255.0']
     c.c = ['192.168.1.255']
     raises(ValueError, "c.a = ['192.168.1.1']")
+    #
     c.a = ['192.168.1.0', '192.168.2.128']
     c.b = ['255.255.255.0', '255.255.255.128']
     c.c = ['192.168.1.255', '192.168.2.255']
     raises(ValueError, "c.c[1] = '192.168.2.128'")
+    c.c[1] = '192.168.2.255'
+
+
+def test_consistency_broadcast_default():
+    a = NetworkOption('a', '', '192.168.1.0')
+    b = NetmaskOption('b', '', '255.255.255.128')
+    c = BroadcastOption('c', '', '192.168.2.127')
+    d = BroadcastOption('d', '', '192.168.1.127')
+    od = OptionDescription('a', '', [a, b, c])
+    raises(ValueError, "c.impl_add_consistency('broadcast', a, b)")
+    od2 = OptionDescription('a', '', [a, b, d])
+    d.impl_add_consistency('broadcast', a, b)
+
+
+def test_consistency_not_all():
+    #_cache_consistencies is not None by not options has consistencies
+    a = NetworkOption('a', '', multi=True)
+    b = NetmaskOption('b', '', multi=True)
+    c = BroadcastOption('c', '', multi=True)
+    od = OptionDescription('a', '', [a, b, c])
+    od.impl_set_group_type(groups.master)
+    b.impl_add_consistency('network_netmask', a)
+    c = Config(od)
+    c.a = ['192.168.1.0']
+    c.b = ['255.255.255.0']
+    c.c = ['192.168.1.255']
index 4430bd9..ef46ce2 100644 (file)
@@ -40,7 +40,7 @@ def _diff_opt(opt1, opt2):
     if diff2 != set():
         raise Exception('more attribute in opt2 {0}'.format(list(diff2)))
     for attr in attr1:
-        if attr in ['_cache_paths']:
+        if attr in ['_cache_paths', '_cache_consistencies']:
             continue
         err1 = False
         err2 = False
index d3c4bf9..c7a28c2 100644 (file)
@@ -61,9 +61,8 @@ class BaseOption(object):
     __setattr__ method
     """
     __slots__ = ('_name', '_requires', '_properties', '_readonly',
-                 '_consistencies', '_calc_properties', '_impl_informations',
-                 '_state_consistencies', '_state_readonly', '_state_requires',
-                 '_stated')
+                 '_calc_properties', '_impl_informations',
+                 '_state_readonly', '_state_requires', '_stated')
 
     def __init__(self, name, doc, requires, properties):
         if not valid_name(name):
@@ -73,7 +72,6 @@ class BaseOption(object):
         self.impl_set_information('doc', doc)
         self._calc_properties, self._requires = validate_requires_arg(
             requires, self._name)
-        self._consistencies = None
         if properties is None:
             properties = tuple()
         if not isinstance(properties, tuple):
@@ -98,8 +96,7 @@ class BaseOption(object):
         "frozen" (which has noting to do with the high level "freeze"
         propertie or "read_only" property)
         """
-        if not name.startswith('_state') and name not in ('_cache_paths',
-                                                          '_consistencies'):
+        if not name.startswith('_state') and not name.startswith('_cache'):
             is_readonly = False
             # never change _name
             if name == '_name':
@@ -109,15 +106,12 @@ class BaseOption(object):
                     is_readonly = True
                 except:
                     pass
-            try:
-                if self._readonly is True:
-                    if value is True:
-                        # already readonly and try to re set readonly
-                        # don't raise, just exit
-                        return
-                    is_readonly = True
-            except AttributeError:
-                pass
+            elif name != '_readonly':
+                try:
+                    if self._readonly is True:
+                        is_readonly = True
+                except AttributeError:
+                    self._readonly = False
             if is_readonly:
                 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
                                        " read-only").format(
@@ -149,57 +143,6 @@ class BaseOption(object):
             raise ValueError(_("information's item not found: {0}").format(
                 key))
 
-    # serialize/unserialize
-    def _impl_convert_consistencies(self, descr, load=False):
-        """during serialization process, many things have to be done.
-        one of them is the localisation of the options.
-        The paths are set once for all.
-
-        :type descr: :class:`tiramisu.option.OptionDescription`
-        :param load: `True` if we are at the init of the option description
-        :type load: bool
-        """
-        if not load and self._consistencies is None:
-            self._state_consistencies = None
-        elif load and self._state_consistencies is None:
-            self._consistencies = None
-            del(self._state_consistencies)
-        else:
-            if load:
-                consistencies = self._state_consistencies
-            else:
-                consistencies = self._consistencies
-            if isinstance(consistencies, list):
-                new_value = []
-                for consistency in consistencies:
-                    values = []
-                    for obj in consistency[1]:
-                        if load:
-                            values.append(descr.impl_get_opt_by_path(obj))
-                        else:
-                            values.append(descr.impl_get_path_by_opt(obj))
-                    new_value.append((consistency[0], tuple(values)))
-
-            else:
-                new_value = {}
-                for key, _consistencies in consistencies.items():
-                    new_value[key] = []
-                    for key_cons, _cons in _consistencies:
-                        _list_cons = []
-                        for _con in _cons:
-                            if load:
-                                _list_cons.append(
-                                    descr.impl_get_opt_by_path(_con))
-                            else:
-                                _list_cons.append(
-                                    descr.impl_get_path_by_opt(_con))
-                        new_value[key].append((key_cons, tuple(_list_cons)))
-            if load:
-                del(self._state_consistencies)
-                self._consistencies = new_value
-            else:
-                self._state_consistencies = new_value
-
     def _impl_convert_requires(self, descr, load=False):
         """export of the requires during the serialization process
 
@@ -245,10 +188,7 @@ class BaseOption(object):
         for func in dir(self):
             if func.startswith('_impl_convert_'):
                 getattr(self, func)(descr)
-        try:
-            self._state_readonly = self._readonly
-        except AttributeError:
-            pass
+        self._state_readonly = self._readonly
 
     def __getstate__(self, stated=True):
         """special method to enable the serialization with pickle
@@ -268,7 +208,8 @@ class BaseOption(object):
         for subclass in self.__class__.__mro__:
             if subclass is not object:
                 slots.update(subclass.__slots__)
-        slots -= frozenset(['_cache_paths', '__weakref__'])
+        slots -= frozenset(['_cache_paths', '_cache_consistencies',
+                            '__weakref__'])
         states = {}
         for slot in slots:
             # remove variable if save variable converted
@@ -327,7 +268,8 @@ class Option(BaseOption):
     """
     __slots__ = ('_multi', '_validator', '_default_multi', '_default',
                  '_state_callback', '_callback', '_multitype',
-                 '_warnings_only', '_master_slaves', '__weakref__')
+                 '_consistencies', '_warnings_only', '_master_slaves',
+                 '_state_consistencies', '__weakref__')
     _empty = ''
 
     def __init__(self, name, doc, default=None, default_multi=None,
@@ -393,66 +335,58 @@ class Option(BaseOption):
         self._warnings_only = warnings_only
         self.impl_validate(default)
         self._default = default
+        self._consistencies = None
 
-    def _launch_consistency(self, func, right_opt, right_val, context, index,
-                            left_opts):
+    def _launch_consistency(self, func, option, value, context, index,
+                            all_cons_opts):
+        """Launch consistency now
+
+        :param func: function name, this name should start with _cons_
+        :type func: `str`
+        :param option: option that value is changing
+        :type option: `tiramisu.option.Option`
+        :param value: new value of this option
+        :param context: Config's context, if None, check default value instead
+        :type context: `tiramisu.config.Config`
+        :param index: only for multi option, consistency should be launch for
+                      specified index
+        :type index: `int`
+        :param all_cons_opts: all options concerne by this consistency
+        :type all_cons_opts: `list` of `tiramisu.option.Option`
+        """
         if context is not None:
             descr = context.cfgimpl_get_description()
-        #right_opt is also in left_opts
-        if right_opt not in left_opts:
-            raise ConfigError(_('right_opt not in left_opts'))
-
-        left_vals = []
-        for opt in left_opts:
-            if right_opt == opt:
-                value = right_val
+        #option is also in all_cons_opts
+        if option not in all_cons_opts:
+            raise ConfigError(_('option not in all_cons_opts'))
+
+        all_cons_vals = []
+        for opt in all_cons_opts:
+            #get value
+            if option == opt:
+                opt_value = value
             else:
+                #if context, calculate value, otherwise get default value
                 if context is not None:
-                    path = descr.impl_get_path_by_opt(opt)
-                    value = context._getattr(path, validate=False)
+                    opt_value = context._getattr(
+                        descr.impl_get_path_by_opt(opt), validate=False)
                 else:
-                    value = opt.impl_getdefault()
-            if index is None:
-                #could be multi or not
-                left_vals.append(value)
+                    opt_value = opt.impl_getdefault()
+
+            #append value
+            if not self.impl_is_multi() or option == opt:
+                all_cons_vals.append(opt_value)
             else:
-                #value is not already set, could be higher
+                #value is not already set, could be higher index
                 try:
-                    if right_opt == opt:
-                        val = value
-                    else:
-                        val = value[index]
-                        if val is None:
-                            #no value so no consistencies
-                            return
-                    left_vals.append(val)
+                    all_cons_vals.append(opt_value[index])
                 except IndexError:
                     #so return if no value
                     return
-
-        if self.impl_is_multi():
-            if index is None:
-                for idx, right_v in enumerate(right_val):
-                    try:
-                        left_v = []
-                        for left_val in left_vals:
-                            left_v.append(left_val[idx])
-                        if None in left_v:
-                            continue
-                    except IndexError:
-                        continue
-                    getattr(self, func)(left_opts, left_v)
-            else:
-                if None in left_vals:
-                    return
-                getattr(self, func)(left_opts, left_vals)
-        else:
-            if None in left_vals:
-                return
-            getattr(self, func)(left_opts, left_vals)
+        getattr(self, func)(all_cons_opts, all_cons_vals)
 
     def impl_validate(self, value, context=None, validate=True,
-                      force_no_multi=False):
+                      force_index=None):
         """
         :param value: the option's value
         :param context: Config's context
@@ -509,12 +443,11 @@ class Option(BaseOption):
         if context is not None:
             descr = context.cfgimpl_get_description()
 
-        if not self._multi or force_no_multi:
-             do_validation(value)
+        if not self._multi or force_index is not None:
+            do_validation(value, force_index)
         else:
             if not isinstance(value, list):
-                raise ValueError(_("invalid value {0} for option {1} "
-                                   "which must be a list").format(value,
+                raise ValueError(_("which must be a list").format(value,
                                                                   self._name))
             for index, val in enumerate(value):
                 do_validation(val, index)
@@ -561,31 +494,45 @@ class Option(BaseOption):
     def impl_is_multi(self):
         return self._multi
 
-    def impl_add_consistency(self, func, *left_opts):
+    def impl_add_consistency(self, func, *other_opts):
+        """Add consistency means that value will be validate with other_opts
+        option's values.
+
+        :param func: function's name
+        :type func: `str`
+        :param other_opts: options used to validate value
+        :type other_opts: `list` of `tiramisu.option.Option`
+        """
         if self._consistencies is None:
             self._consistencies = []
-        for opt in left_opts:
+        for opt in other_opts:
             if not isinstance(opt, Option):
-                raise ValueError(_('consistency should be set with an option'))
+                raise ConfigError(_('consistency should be set with an option'))
             if self is opt:
-                raise ValueError(_('cannot add consistency with itself'))
+                raise ConfigError(_('cannot add consistency with itself'))
             if self.impl_is_multi() != opt.impl_is_multi():
-                raise ValueError(_('options in consistency should be multi in '
-                                 'two sides'))
+                raise ConfigError(_('every options in consistency should be '
+                                    'multi or none'))
         func = '_cons_{0}'.format(func)
-        opts = tuple([self] + list(left_opts))
-        self._launch_consistency(func, self, self.impl_getdefault(), None,
-                                 None, opts)
-        self._consistencies.append((func, opts))
+        all_cons_opts = tuple([self] + list(other_opts))
+        value = self.impl_getdefault()
+        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)
+            else:
+                self._launch_consistency(func, self, value, None,
+                                         None, all_cons_opts)
+        self._consistencies.append((func, all_cons_opts))
         self.impl_validate(self.impl_getdefault())
 
     def _cons_not_equal(self, opts, vals):
-        if len(opts) != 2:
-            raise ConfigError(_('invalid len for opts'))
-        if vals[0] == vals[1]:
-            raise ValueError(_("invalid value {0} for option {1} "
-                               "must be different as {2} option"
-                               "").format(vals[0], self._name, opts[1]._name))
+        for idx_inf, val_inf in enumerate(vals):
+            for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
+                if val_inf == val_sup is not None:
+                    raise ValueError(_("same value for {0} and {1}").format(
+                        opts[idx_inf]._name, opts[idx_inf + idx_sup + 1]._name))
 
     def _impl_convert_callbacks(self, descr, load=False):
         if not load and self._callback is None:
@@ -621,6 +568,57 @@ class Option(BaseOption):
             else:
                 self._state_callback = (callback, cllbck_prms)
 
+    # serialize/unserialize
+    def _impl_convert_consistencies(self, descr, load=False):
+        """during serialization process, many things have to be done.
+        one of them is the localisation of the options.
+        The paths are set once for all.
+
+        :type descr: :class:`tiramisu.option.OptionDescription`
+        :param load: `True` if we are at the init of the option description
+        :type load: bool
+        """
+        if not load and self._consistencies is None:
+            self._state_consistencies = None
+        elif load and self._state_consistencies is None:
+            self._consistencies = None
+            del(self._state_consistencies)
+        else:
+            if load:
+                consistencies = self._state_consistencies
+            else:
+                consistencies = self._consistencies
+            if isinstance(consistencies, list):
+                new_value = []
+                for consistency in consistencies:
+                    values = []
+                    for obj in consistency[1]:
+                        if load:
+                            values.append(descr.impl_get_opt_by_path(obj))
+                        else:
+                            values.append(descr.impl_get_path_by_opt(obj))
+                    new_value.append((consistency[0], tuple(values)))
+
+            else:
+                new_value = {}
+                for key, _consistencies in consistencies.items():
+                    new_value[key] = []
+                    for key_cons, _cons in _consistencies:
+                        _list_cons = []
+                        for _con in _cons:
+                            if load:
+                                _list_cons.append(
+                                    descr.impl_get_opt_by_path(_con))
+                            else:
+                                _list_cons.append(
+                                    descr.impl_get_path_by_opt(_con))
+                        new_value[key].append((key_cons, tuple(_list_cons)))
+            if load:
+                del(self._state_consistencies)
+                self._consistencies = new_value
+            else:
+                self._state_consistencies = new_value
+
     def _second_level_validation(self, value):
         pass
 
@@ -734,7 +732,7 @@ class SymLinkOption(BaseOption):
     __slots__ = ('_name', '_opt', '_state_opt')
     _opt_type = 'symlink'
     #not return _opt consistencies
-    _consistencies = {}
+    _consistencies = None
 
     def __init__(self, name, opt):
         self._name = name
@@ -760,12 +758,6 @@ class SymLinkOption(BaseOption):
         del(self._state_opt)
         super(SymLinkOption, self)._impl_setstate(descr)
 
-    def _impl_convert_consistencies(self, descr, load=False):
-        if load:
-            del(self._state_consistencies)
-        else:
-            self._state_consistencies = None
-
 
 class IPOption(Option):
     "represents the choice of an ip"
@@ -904,10 +896,14 @@ class NetmaskOption(Option):
 
     def _cons_network_netmask(self, opts, vals):
         #opts must be (netmask, network) options
+        if None in vals:
+            return
         self.__cons_netmask(opts, vals[0], vals[1], False)
 
     def _cons_ip_netmask(self, opts, vals):
         #opts must be (netmask, ip) options
+        if None in vals:
+            return
         self.__cons_netmask(opts, vals[0], vals[1], True)
 
     def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
@@ -955,6 +951,8 @@ class BroadcastOption(Option):
     def _cons_broadcast(self, opts, vals):
         if len(vals) != 3:
             raise ConfigError(_('invalid len for vals'))
+        if None in vals:
+            return
         broadcast, network, netmask = vals
         if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
             raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
@@ -964,7 +962,12 @@ class BroadcastOption(Option):
 
 
 class DomainnameOption(Option):
-    "represents the choice of a domain name"
+    """represents the choice of a domain name
+    netbios: for MS domain
+    hostname: to identify the device
+    domainname:
+    fqdn: with tld, not supported yet
+    """
     __slots__ = ('_type', '_allow_ip')
     _opt_type = 'domainname'
 
@@ -973,10 +976,6 @@ class DomainnameOption(Option):
                  callback_params=None, validator=None, validator_params=None,
                  properties=None, allow_ip=False, type_='domainname',
                  warnings_only=False):
-        #netbios: for MS domain
-        #hostname: to identify the device
-        #domainname:
-        #fqdn: with tld, not supported yet
         if type_ not in ['netbios', 'hostname', 'domainname']:
             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
         self._type = type_
@@ -1030,9 +1029,9 @@ class OptionDescription(BaseOption):
     """
     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
                  '_state_group_type', '_properties', '_children',
-                 '_consistencies', '_calc_properties', '__weakref__',
+                 '_cache_consistencies', '_calc_properties', '__weakref__',
                  '_readonly', '_impl_informations', '_state_requires',
-                 '_state_consistencies', '_stated', '_state_readonly')
+                 '_stated', '_state_readonly')
     _opt_type = 'optiondescription'
 
     def __init__(self, name, doc, children, requires=None, properties=None):
@@ -1053,6 +1052,7 @@ class OptionDescription(BaseOption):
             old = child
         self._children = (tuple(child_names), tuple(children))
         self._cache_paths = None
+        self._cache_consistencies = None
         # the group_type is useful for filtering OptionDescriptions in a config
         self._group_type = groups.default
 
@@ -1126,11 +1126,11 @@ class OptionDescription(BaseOption):
                 if not force_no_consistencies and \
                         option._consistencies is not None:
                     for consistency in option._consistencies:
-                        func, left_opts = consistency
-                        for opt in left_opts:
+                        func, all_cons_opts = consistency
+                        for opt in all_cons_opts:
                             _consistencies.setdefault(opt,
                                                       []).append((func,
-                                                                  left_opts))
+                                                                  all_cons_opts))
             else:
                 _currpath.append(attr)
                 option.impl_build_cache(cache_path,
@@ -1142,7 +1142,12 @@ class OptionDescription(BaseOption):
         if save:
             self._cache_paths = (tuple(cache_option), tuple(cache_path))
             if not force_no_consistencies:
-                self._consistencies = _consistencies
+                if _consistencies != {}:
+                    self._cache_consistencies = {}
+                    for opt, cons in _consistencies.items():
+                        if opt not in cache_option:
+                            raise ConfigError(_('consistency with option {0} which is not in Config').format(opt._name))
+                        self._cache_consistencies[opt] = tuple(cons)
                 self._readonly = True
 
     def impl_get_opt_by_path(self, path):
@@ -1213,15 +1218,18 @@ class OptionDescription(BaseOption):
     def impl_get_group_type(self):
         return self._group_type
 
-    def _valid_consistency(self, right_opt, right_val, context=None, index=None):
-        #[('_cons_not_equal', (opt1, opt2))]
-        consistencies = self._consistencies.get(right_opt)
+    def _valid_consistency(self, option, value, context, index):
+        if self._cache_consistencies is None:
+            return True
+        #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
+        consistencies = self._cache_consistencies.get(option)
         if consistencies is not None:
-            for func, opts in consistencies:
-                #opts[0] is the option where func is set
-                #opts is left_opts
-                ret = opts[0]._launch_consistency(func, right_opt, right_val,
-                                                  context, index, opts)
+            for func, all_cons_opts in consistencies:
+                #all_cons_opts[0] is the option where func is set
+                ret = all_cons_opts[0]._launch_consistency(func, option,
+                                                           value,
+                                                           context, index,
+                                                           all_cons_opts)
                 if ret is False:
                     return False
         return True
@@ -1261,6 +1269,7 @@ class OptionDescription(BaseOption):
         """
         if descr is None:
             self._cache_paths = None
+            self._cache_consistencies = None
             self.impl_build_cache(force_no_consistencies=True)
             descr = self
         self._group_type = getattr(groups, self._state_group_type)
index 6942453..4426742 100644 (file)
@@ -455,10 +455,10 @@ class Multi(list):
                             value_slave.append(slave.impl_getdefault_multi(),
                                                force=True)
 
-    def __setitem__(self, key, value):
-        self._validate(value)
+    def __setitem__(self, index, value):
+        self._validate(value, index)
         #assume not checking mandatory property
-        super(Multi, self).__setitem__(key, value)
+        super(Multi, self).__setitem__(index, value)
         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
 
     def append(self, value, force=False):
@@ -476,7 +476,8 @@ class Multi(list):
                     #Force None il return a list
                     if isinstance(value, list):
                         value = None
-        self._validate(value)
+        index = self.__len__()
+        self._validate(value, index)
         super(Multi, self).append(value)
         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path,
                                                       self,
@@ -486,7 +487,6 @@ class Multi(list):
                 path = values._get_opt_path(slave)
                 if not values._is_default_owner(path):
                     if slave.impl_has_callback():
-                        index = self.__len__() - 1
                         dvalue = values._getcallback_value(slave, index=index)
                     else:
                         dvalue = slave.impl_getdefault_multi()
@@ -538,11 +538,11 @@ class Multi(list):
         super(Multi, self).extend(iterable)
         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
 
-    def _validate(self, value):
+    def _validate(self, value, force_index):
         if value is not None:
             try:
                 self.opt.impl_validate(value, context=self.context(),
-                                       force_no_multi=True)
+                                       force_index=force_index)
             except ValueError as err:
                 raise ValueError(_("invalid value {0} "
                                    "for option {1}: {2}"