tiramisu/option.py:

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
This commit is contained in:
Emmanuel Garette 2013-09-28 17:05:01 +02:00
parent 482dfec7f2
commit 70f684e70c
4 changed files with 277 additions and 174 deletions

View File

@ -5,6 +5,7 @@ from tiramisu.setting import owners, groups
from tiramisu.config import Config from tiramisu.config import Config
from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\ from tiramisu.option import IPOption, NetworkOption, NetmaskOption, IntOption,\
BroadcastOption, SymLinkOption, OptionDescription BroadcastOption, SymLinkOption, OptionDescription
from tiramisu.error import ConfigError
def test_consistency_not_equal(): def test_consistency_not_equal():
@ -22,6 +23,60 @@ def test_consistency_not_equal():
c.b = 2 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(): def test_consistency_not_equal_symlink():
a = IntOption('a', '') a = IntOption('a', '')
b = IntOption('b', '') b = IntOption('b', '')
@ -29,7 +84,7 @@ def test_consistency_not_equal_symlink():
od = OptionDescription('od', '', [a, b, c]) od = OptionDescription('od', '', [a, b, c])
a.impl_add_consistency('not_equal', b) a.impl_add_consistency('not_equal', b)
c = Config(od) 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(): def test_consistency_not_equal_multi():
@ -53,6 +108,14 @@ def test_consistency_default():
raises(ValueError, "a.impl_add_consistency('not_equal', b)") 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(): def test_consistency_default_diff():
a = IntOption('a', '', 3) a = IntOption('a', '', 3)
b = IntOption('b', '', 1) b = IntOption('b', '', 1)
@ -99,7 +162,7 @@ def test_consistency_ip_netmask_error_multi():
a = IPOption('a', '', multi=True) a = IPOption('a', '', multi=True)
b = NetmaskOption('b', '') b = NetmaskOption('b', '')
od = OptionDescription('od', '', [a, 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(): def test_consistency_ip_netmask_multi():
@ -170,11 +233,42 @@ def test_consistency_broadcast():
b.impl_add_consistency('network_netmask', a) b.impl_add_consistency('network_netmask', a)
c.impl_add_consistency('broadcast', a, b) c.impl_add_consistency('broadcast', a, b)
c = Config(od) 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.a = ['192.168.1.0']
c.b = ['255.255.255.0'] c.b = ['255.255.255.0']
c.c = ['192.168.1.255'] c.c = ['192.168.1.255']
raises(ValueError, "c.a = ['192.168.1.1']") raises(ValueError, "c.a = ['192.168.1.1']")
#
c.a = ['192.168.1.0', '192.168.2.128'] c.a = ['192.168.1.0', '192.168.2.128']
c.b = ['255.255.255.0', '255.255.255.128'] c.b = ['255.255.255.0', '255.255.255.128']
c.c = ['192.168.1.255', '192.168.2.255'] c.c = ['192.168.1.255', '192.168.2.255']
raises(ValueError, "c.c[1] = '192.168.2.128'") 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']

View File

@ -40,7 +40,7 @@ def _diff_opt(opt1, opt2):
if diff2 != set(): if diff2 != set():
raise Exception('more attribute in opt2 {0}'.format(list(diff2))) raise Exception('more attribute in opt2 {0}'.format(list(diff2)))
for attr in attr1: for attr in attr1:
if attr in ['_cache_paths']: if attr in ['_cache_paths', '_cache_consistencies']:
continue continue
err1 = False err1 = False
err2 = False err2 = False

View File

@ -61,9 +61,8 @@ class BaseOption(object):
__setattr__ method __setattr__ method
""" """
__slots__ = ('_name', '_requires', '_properties', '_readonly', __slots__ = ('_name', '_requires', '_properties', '_readonly',
'_consistencies', '_calc_properties', '_impl_informations', '_calc_properties', '_impl_informations',
'_state_consistencies', '_state_readonly', '_state_requires', '_state_readonly', '_state_requires', '_stated')
'_stated')
def __init__(self, name, doc, requires, properties): def __init__(self, name, doc, requires, properties):
if not valid_name(name): if not valid_name(name):
@ -73,7 +72,6 @@ class BaseOption(object):
self.impl_set_information('doc', doc) self.impl_set_information('doc', doc)
self._calc_properties, self._requires = validate_requires_arg( self._calc_properties, self._requires = validate_requires_arg(
requires, self._name) requires, self._name)
self._consistencies = None
if properties is None: if properties is None:
properties = tuple() properties = tuple()
if not isinstance(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" "frozen" (which has noting to do with the high level "freeze"
propertie or "read_only" property) propertie or "read_only" property)
""" """
if not name.startswith('_state') and name not in ('_cache_paths', if not name.startswith('_state') and not name.startswith('_cache'):
'_consistencies'):
is_readonly = False is_readonly = False
# never change _name # never change _name
if name == '_name': if name == '_name':
@ -109,15 +106,12 @@ class BaseOption(object):
is_readonly = True is_readonly = True
except: except:
pass pass
try: elif name != '_readonly':
if self._readonly is True: try:
if value is True: if self._readonly is True:
# already readonly and try to re set readonly is_readonly = True
# don't raise, just exit except AttributeError:
return self._readonly = False
is_readonly = True
except AttributeError:
pass
if is_readonly: if is_readonly:
raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is" raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
" read-only").format( " read-only").format(
@ -149,57 +143,6 @@ class BaseOption(object):
raise ValueError(_("information's item not found: {0}").format( raise ValueError(_("information's item not found: {0}").format(
key)) 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): def _impl_convert_requires(self, descr, load=False):
"""export of the requires during the serialization process """export of the requires during the serialization process
@ -245,10 +188,7 @@ class BaseOption(object):
for func in dir(self): for func in dir(self):
if func.startswith('_impl_convert_'): if func.startswith('_impl_convert_'):
getattr(self, func)(descr) getattr(self, func)(descr)
try: self._state_readonly = self._readonly
self._state_readonly = self._readonly
except AttributeError:
pass
def __getstate__(self, stated=True): def __getstate__(self, stated=True):
"""special method to enable the serialization with pickle """special method to enable the serialization with pickle
@ -268,7 +208,8 @@ class BaseOption(object):
for subclass in self.__class__.__mro__: for subclass in self.__class__.__mro__:
if subclass is not object: if subclass is not object:
slots.update(subclass.__slots__) slots.update(subclass.__slots__)
slots -= frozenset(['_cache_paths', '__weakref__']) slots -= frozenset(['_cache_paths', '_cache_consistencies',
'__weakref__'])
states = {} states = {}
for slot in slots: for slot in slots:
# remove variable if save variable converted # remove variable if save variable converted
@ -327,7 +268,8 @@ class Option(BaseOption):
""" """
__slots__ = ('_multi', '_validator', '_default_multi', '_default', __slots__ = ('_multi', '_validator', '_default_multi', '_default',
'_state_callback', '_callback', '_multitype', '_state_callback', '_callback', '_multitype',
'_warnings_only', '_master_slaves', '__weakref__') '_consistencies', '_warnings_only', '_master_slaves',
'_state_consistencies', '__weakref__')
_empty = '' _empty = ''
def __init__(self, name, doc, default=None, default_multi=None, def __init__(self, name, doc, default=None, default_multi=None,
@ -393,66 +335,58 @@ class Option(BaseOption):
self._warnings_only = warnings_only self._warnings_only = warnings_only
self.impl_validate(default) self.impl_validate(default)
self._default = default self._default = default
self._consistencies = None
def _launch_consistency(self, func, right_opt, right_val, context, index, def _launch_consistency(self, func, option, value, context, index,
left_opts): 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: if context is not None:
descr = context.cfgimpl_get_description() descr = context.cfgimpl_get_description()
#right_opt is also in left_opts #option is also in all_cons_opts
if right_opt not in left_opts: if option not in all_cons_opts:
raise ConfigError(_('right_opt not in left_opts')) raise ConfigError(_('option not in all_cons_opts'))
left_vals = [] all_cons_vals = []
for opt in left_opts: for opt in all_cons_opts:
if right_opt == opt: #get value
value = right_val if option == opt:
opt_value = value
else: else:
#if context, calculate value, otherwise get default value
if context is not None: if context is not None:
path = descr.impl_get_path_by_opt(opt) opt_value = context._getattr(
value = context._getattr(path, validate=False) descr.impl_get_path_by_opt(opt), validate=False)
else: else:
value = opt.impl_getdefault() opt_value = opt.impl_getdefault()
if index is None:
#could be multi or not #append value
left_vals.append(value) if not self.impl_is_multi() or option == opt:
all_cons_vals.append(opt_value)
else: else:
#value is not already set, could be higher #value is not already set, could be higher index
try: try:
if right_opt == opt: all_cons_vals.append(opt_value[index])
val = value
else:
val = value[index]
if val is None:
#no value so no consistencies
return
left_vals.append(val)
except IndexError: except IndexError:
#so return if no value #so return if no value
return return
getattr(self, func)(all_cons_opts, all_cons_vals)
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)
def impl_validate(self, value, context=None, validate=True, def impl_validate(self, value, context=None, validate=True,
force_no_multi=False): force_index=None):
""" """
:param value: the option's value :param value: the option's value
:param context: Config's context :param context: Config's context
@ -509,12 +443,11 @@ class Option(BaseOption):
if context is not None: if context is not None:
descr = context.cfgimpl_get_description() descr = context.cfgimpl_get_description()
if not self._multi or force_no_multi: if not self._multi or force_index is not None:
do_validation(value) do_validation(value, force_index)
else: else:
if not isinstance(value, list): if not isinstance(value, list):
raise ValueError(_("invalid value {0} for option {1} " raise ValueError(_("which must be a list").format(value,
"which must be a list").format(value,
self._name)) self._name))
for index, val in enumerate(value): for index, val in enumerate(value):
do_validation(val, index) do_validation(val, index)
@ -561,31 +494,45 @@ class Option(BaseOption):
def impl_is_multi(self): def impl_is_multi(self):
return self._multi 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: if self._consistencies is None:
self._consistencies = [] self._consistencies = []
for opt in left_opts: for opt in other_opts:
if not isinstance(opt, Option): 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: 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(): if self.impl_is_multi() != opt.impl_is_multi():
raise ValueError(_('options in consistency should be multi in ' raise ConfigError(_('every options in consistency should be '
'two sides')) 'multi or none'))
func = '_cons_{0}'.format(func) func = '_cons_{0}'.format(func)
opts = tuple([self] + list(left_opts)) all_cons_opts = tuple([self] + list(other_opts))
self._launch_consistency(func, self, self.impl_getdefault(), None, value = self.impl_getdefault()
None, opts) if value is not None:
self._consistencies.append((func, opts)) 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()) self.impl_validate(self.impl_getdefault())
def _cons_not_equal(self, opts, vals): def _cons_not_equal(self, opts, vals):
if len(opts) != 2: for idx_inf, val_inf in enumerate(vals):
raise ConfigError(_('invalid len for opts')) for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
if vals[0] == vals[1]: if val_inf == val_sup is not None:
raise ValueError(_("invalid value {0} for option {1} " raise ValueError(_("same value for {0} and {1}").format(
"must be different as {2} option" opts[idx_inf]._name, opts[idx_inf + idx_sup + 1]._name))
"").format(vals[0], self._name, opts[1]._name))
def _impl_convert_callbacks(self, descr, load=False): def _impl_convert_callbacks(self, descr, load=False):
if not load and self._callback is None: if not load and self._callback is None:
@ -621,6 +568,57 @@ class Option(BaseOption):
else: else:
self._state_callback = (callback, cllbck_prms) 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): def _second_level_validation(self, value):
pass pass
@ -734,7 +732,7 @@ class SymLinkOption(BaseOption):
__slots__ = ('_name', '_opt', '_state_opt') __slots__ = ('_name', '_opt', '_state_opt')
_opt_type = 'symlink' _opt_type = 'symlink'
#not return _opt consistencies #not return _opt consistencies
_consistencies = {} _consistencies = None
def __init__(self, name, opt): def __init__(self, name, opt):
self._name = name self._name = name
@ -760,12 +758,6 @@ class SymLinkOption(BaseOption):
del(self._state_opt) del(self._state_opt)
super(SymLinkOption, self)._impl_setstate(descr) 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): class IPOption(Option):
"represents the choice of an ip" "represents the choice of an ip"
@ -904,10 +896,14 @@ class NetmaskOption(Option):
def _cons_network_netmask(self, opts, vals): def _cons_network_netmask(self, opts, vals):
#opts must be (netmask, network) options #opts must be (netmask, network) options
if None in vals:
return
self.__cons_netmask(opts, vals[0], vals[1], False) self.__cons_netmask(opts, vals[0], vals[1], False)
def _cons_ip_netmask(self, opts, vals): def _cons_ip_netmask(self, opts, vals):
#opts must be (netmask, ip) options #opts must be (netmask, ip) options
if None in vals:
return
self.__cons_netmask(opts, vals[0], vals[1], True) self.__cons_netmask(opts, vals[0], vals[1], True)
def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net): def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
@ -955,6 +951,8 @@ class BroadcastOption(Option):
def _cons_broadcast(self, opts, vals): def _cons_broadcast(self, opts, vals):
if len(vals) != 3: if len(vals) != 3:
raise ConfigError(_('invalid len for vals')) raise ConfigError(_('invalid len for vals'))
if None in vals:
return
broadcast, network, netmask = vals broadcast, network, netmask = vals
if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast): if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
raise ValueError(_('invalid broadcast {0} ({1}) with network {2} ' raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
@ -964,7 +962,12 @@ class BroadcastOption(Option):
class DomainnameOption(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') __slots__ = ('_type', '_allow_ip')
_opt_type = 'domainname' _opt_type = 'domainname'
@ -973,10 +976,6 @@ class DomainnameOption(Option):
callback_params=None, validator=None, validator_params=None, callback_params=None, validator=None, validator_params=None,
properties=None, allow_ip=False, type_='domainname', properties=None, allow_ip=False, type_='domainname',
warnings_only=False): 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']: if type_ not in ['netbios', 'hostname', 'domainname']:
raise ValueError(_('unknown type_ {0} for hostname').format(type_)) raise ValueError(_('unknown type_ {0} for hostname').format(type_))
self._type = type_ self._type = type_
@ -1030,9 +1029,9 @@ class OptionDescription(BaseOption):
""" """
__slots__ = ('_name', '_requires', '_cache_paths', '_group_type', __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
'_state_group_type', '_properties', '_children', '_state_group_type', '_properties', '_children',
'_consistencies', '_calc_properties', '__weakref__', '_cache_consistencies', '_calc_properties', '__weakref__',
'_readonly', '_impl_informations', '_state_requires', '_readonly', '_impl_informations', '_state_requires',
'_state_consistencies', '_stated', '_state_readonly') '_stated', '_state_readonly')
_opt_type = 'optiondescription' _opt_type = 'optiondescription'
def __init__(self, name, doc, children, requires=None, properties=None): def __init__(self, name, doc, children, requires=None, properties=None):
@ -1053,6 +1052,7 @@ class OptionDescription(BaseOption):
old = child old = child
self._children = (tuple(child_names), tuple(children)) self._children = (tuple(child_names), tuple(children))
self._cache_paths = None self._cache_paths = None
self._cache_consistencies = None
# the group_type is useful for filtering OptionDescriptions in a config # the group_type is useful for filtering OptionDescriptions in a config
self._group_type = groups.default self._group_type = groups.default
@ -1126,11 +1126,11 @@ class OptionDescription(BaseOption):
if not force_no_consistencies and \ if not force_no_consistencies and \
option._consistencies is not None: option._consistencies is not None:
for consistency in option._consistencies: for consistency in option._consistencies:
func, left_opts = consistency func, all_cons_opts = consistency
for opt in left_opts: for opt in all_cons_opts:
_consistencies.setdefault(opt, _consistencies.setdefault(opt,
[]).append((func, []).append((func,
left_opts)) all_cons_opts))
else: else:
_currpath.append(attr) _currpath.append(attr)
option.impl_build_cache(cache_path, option.impl_build_cache(cache_path,
@ -1142,7 +1142,12 @@ class OptionDescription(BaseOption):
if save: if save:
self._cache_paths = (tuple(cache_option), tuple(cache_path)) self._cache_paths = (tuple(cache_option), tuple(cache_path))
if not force_no_consistencies: 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 self._readonly = True
def impl_get_opt_by_path(self, path): def impl_get_opt_by_path(self, path):
@ -1213,15 +1218,18 @@ class OptionDescription(BaseOption):
def impl_get_group_type(self): def impl_get_group_type(self):
return self._group_type return self._group_type
def _valid_consistency(self, right_opt, right_val, context=None, index=None): def _valid_consistency(self, option, value, context, index):
#[('_cons_not_equal', (opt1, opt2))] if self._cache_consistencies is None:
consistencies = self._consistencies.get(right_opt) return True
#consistencies is something like [('_cons_not_equal', (opt1, opt2))]
consistencies = self._cache_consistencies.get(option)
if consistencies is not None: if consistencies is not None:
for func, opts in consistencies: for func, all_cons_opts in consistencies:
#opts[0] is the option where func is set #all_cons_opts[0] is the option where func is set
#opts is left_opts ret = all_cons_opts[0]._launch_consistency(func, option,
ret = opts[0]._launch_consistency(func, right_opt, right_val, value,
context, index, opts) context, index,
all_cons_opts)
if ret is False: if ret is False:
return False return False
return True return True
@ -1261,6 +1269,7 @@ class OptionDescription(BaseOption):
""" """
if descr is None: if descr is None:
self._cache_paths = None self._cache_paths = None
self._cache_consistencies = None
self.impl_build_cache(force_no_consistencies=True) self.impl_build_cache(force_no_consistencies=True)
descr = self descr = self
self._group_type = getattr(groups, self._state_group_type) self._group_type = getattr(groups, self._state_group_type)

View File

@ -455,10 +455,10 @@ class Multi(list):
value_slave.append(slave.impl_getdefault_multi(), value_slave.append(slave.impl_getdefault_multi(),
force=True) force=True)
def __setitem__(self, key, value): def __setitem__(self, index, value):
self._validate(value) self._validate(value, index)
#assume not checking mandatory property #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) self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
def append(self, value, force=False): def append(self, value, force=False):
@ -476,7 +476,8 @@ class Multi(list):
#Force None il return a list #Force None il return a list
if isinstance(value, list): if isinstance(value, list):
value = None value = None
self._validate(value) index = self.__len__()
self._validate(value, index)
super(Multi, self).append(value) super(Multi, self).append(value)
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self.context().cfgimpl_get_values()._setvalue(self.opt, self.path,
self, self,
@ -486,7 +487,6 @@ class Multi(list):
path = values._get_opt_path(slave) path = values._get_opt_path(slave)
if not values._is_default_owner(path): if not values._is_default_owner(path):
if slave.impl_has_callback(): if slave.impl_has_callback():
index = self.__len__() - 1
dvalue = values._getcallback_value(slave, index=index) dvalue = values._getcallback_value(slave, index=index)
else: else:
dvalue = slave.impl_getdefault_multi() dvalue = slave.impl_getdefault_multi()
@ -538,11 +538,11 @@ class Multi(list):
super(Multi, self).extend(iterable) super(Multi, self).extend(iterable)
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self) 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: if value is not None:
try: try:
self.opt.impl_validate(value, context=self.context(), self.opt.impl_validate(value, context=self.context(),
force_no_multi=True) force_index=force_index)
except ValueError as err: except ValueError as err:
raise ValueError(_("invalid value {0} " raise ValueError(_("invalid value {0} "
"for option {1}: {2}" "for option {1}: {2}"