can set valid value for an option with invalid consistency

This commit is contained in:
Emmanuel Garette 2018-06-02 08:35:05 +02:00
parent 165b28cb00
commit 50a2ab4186
8 changed files with 287 additions and 222 deletions

View File

@ -316,11 +316,33 @@ def test_consistency_not_equal_multi():
raises(ValueError, "api.option('b').value.set([2, 3])") raises(ValueError, "api.option('b').value.set([2, 3])")
def test_consistency_not_equal_multi_default(): def test_consistency_not_equal_multi_default1():
a = IntOption('a', '', multi=True, default=[1]) a = IntOption('a', '', multi=True, default=[1])
b = IntOption('b', '', multi=True, default=[1, 2]) b = IntOption('b', '', multi=True, default=[3, 1])
od = OptionDescription('a', '', [a, b]) od = OptionDescription('a', '', [a, b])
raises(ValueError, "a.impl_add_consistency('not_equal', b)") raises(ValueError, "b.impl_add_consistency('not_equal', a)")
def test_consistency_not_equal_multi_default2():
a = IntOption('a', '', multi=True, default=[1])
b = IntOption('b', '', multi=True, default_multi=1)
od = OptionDescription('a', '', [a, b])
#default_multi not tested now
a.impl_add_consistency('not_equal', b)
def test_consistency_not_equal_master_default():
a = IntOption('a', '', multi=True, default=[2, 1])
b = IntOption('b', '', multi=True, default_multi=1)
od = MasterSlaves('a', '', [a, b])
a.impl_add_consistency('not_equal', b)
od2 = OptionDescription('a', '', [od])
api = getapi(Config(od2))
# default_multi not tested
raises(ValueError, "api.option('a.b', 0).value.get()")
api.option('a.b', 0).value.set(3)
api.option('a.b', 1).value.set(3)
assert api.option('a.b', 1).value.get() == 3
def test_consistency_not_equal_multi_default_modif(): def test_consistency_not_equal_multi_default_modif():
@ -417,24 +439,26 @@ def test_consistency_ip_in_network():
assert len(w) == 1 assert len(w) == 1
def test_consistency_ip_in_network_len_error(): # needs 3 arguments, not 2
a = NetworkOption('a', '') #def test_consistency_ip_in_network_len_error():
b = NetmaskOption('b', '') # a = NetworkOption('a', '')
c = IPOption('c', '') # b = NetmaskOption('b', '')
od = OptionDescription('od', '', [a, b, c]) # c = IPOption('c', '')
raises(ConfigError, "c.impl_add_consistency('in_network', a)") # od = OptionDescription('od', '', [a, b, c])
# raises(ConfigError, "c.impl_add_consistency('in_network', a)")
def test_consistency_ip_netmask_network_error(): # needs 2 arguments, not 3
a = IPOption('a', '') #def test_consistency_ip_netmask_network_error():
b = NetworkOption('b', '') # a = IPOption('a', '')
c = NetmaskOption('c', '') # b = NetworkOption('b', '')
od = OptionDescription('od', '', [a, b, c]) # c = NetmaskOption('c', '')
c.impl_add_consistency('ip_netmask', a, b) # od = OptionDescription('od', '', [a, b, c])
api = getapi(Config(od)) # c.impl_add_consistency('ip_netmask', a, b)
api.option('a').value.set('192.168.1.1') # api = getapi(Config(od))
api.option('b').value.set('192.168.1.0') # api.option('a').value.set('192.168.1.1')
raises(ConfigError, "api.option('c').value.set('255.255.255.0')") # api.option('b').value.set('192.168.1.0')
# raises(ConfigError, "api.option('c').value.set('255.255.255.0')")
def test_consistency_ip_netmask_error_multi(): def test_consistency_ip_netmask_error_multi():

View File

@ -30,10 +30,10 @@ from .option import ChoiceOption
TIRAMISU_VERSION = 3 TIRAMISU_VERSION = 3
try: #try:
from .value import Multi # from .value import Multi
except: #except:
Multi = list # Multi = list
COUNT_TIME = False COUNT_TIME = False
@ -532,8 +532,8 @@ class TiramisuOptionValue(CommonTiramisuOption):
value = self.subconfig.getattr(self._name, value = self.subconfig.getattr(self._name,
self.index, self.index,
self.config_bag) self.config_bag)
if isinstance(value, Multi): #if isinstance(value, Multi):
value = list(value) # value = list(value)
return value return value
@count @count

View File

@ -75,6 +75,7 @@ class SubConfig(object):
masterpath = master.impl_getname() masterpath = master.impl_getname()
mconfig_bag = config_bag.copy('nooption') mconfig_bag = config_bag.copy('nooption')
mconfig_bag.option = master mconfig_bag.option = master
mconfig_bag.validate = False
value = self.getattr(masterpath, value = self.getattr(masterpath,
None, None,
mconfig_bag) mconfig_bag)

View File

@ -102,9 +102,7 @@ class IPOption(Option):
opts, opts,
vals, vals,
warnings_only): warnings_only):
if len(vals) != 3: if len(vals) != 3 or None in vals:
raise ConfigError(_('invalid len for vals'))
if None in vals:
return return
ip, network, netmask = vals ip, network, netmask = vals
if IP(ip) not in IP('{0}/{1}'.format(network, if IP(ip) not in IP('{0}/{1}'.format(network,

View File

@ -52,13 +52,15 @@ class NetmaskOption(Option):
vals, vals,
warnings_only): warnings_only):
#opts must be (netmask, network) options #opts must be (netmask, network) options
if None in vals: if len(vals) != 2 or None in vals:
return return
return self.__cons_netmask(opts, return self.__cons_netmask(current_opt,
opts,
vals[0], vals[0],
vals[1], vals[1],
False, False,
warnings_only) warnings_only,
'network')
def _cons_ip_netmask(self, def _cons_ip_netmask(self,
current_opt, current_opt,
@ -66,20 +68,24 @@ class NetmaskOption(Option):
vals, vals,
warnings_only): warnings_only):
#opts must be (netmask, ip) options #opts must be (netmask, ip) options
if None in vals: if len(vals) != 2 or None in vals:
return return
self.__cons_netmask(opts, self.__cons_netmask(current_opt,
opts,
vals[0], vals[0],
vals[1], vals[1],
True, True,
warnings_only) warnings_only,
'ip')
def __cons_netmask(self, def __cons_netmask(self,
current_opt,
opts, opts,
val_netmask, val_netmask,
val_ipnetwork, val_ipnetwork,
make_net, make_net,
warnings_only): warnings_only,
typ):
if len(opts) != 2: if len(opts) != 2:
raise ConfigError(_('invalid len for opts')) raise ConfigError(_('invalid len for opts'))
msg = None msg = None
@ -95,7 +101,10 @@ class NetmaskOption(Option):
except ValueError: except ValueError:
if not make_net: if not make_net:
msg = _('with netmask "{0}" ("{1}")') if current_opt == opts[1]:
raise ValueError(_('with netmask "{0}" ("{1}")').format(val_netmask, opts[0].impl_get_display_name()))
else:
raise ValueError(_('with {2} "{0}" ("{1}")').format(val_ipnetwork, opts[1].impl_get_display_name(), typ))
if msg is not None: if msg is not None:
raise ValueError(msg.format(val_netmask, raise ValueError(msg.format(val_netmask,
opts[1].impl_get_display_name())) opts[1].impl_get_display_name()))

View File

@ -198,14 +198,20 @@ class Option(OnlyOption):
""" """
if current_opt is undefined: if current_opt is undefined:
current_opt = self current_opt = self
if config_bag is not undefined and \ if check_error and config_bag is not undefined and \
((check_error is True and not 'validator' in config_bag.setting_properties) or \ not config_bag.validate:
(check_error is False and not 'warnings' in config_bag.setting_properties)): # just to check propertieserror
self.valid_consistency(current_opt,
value,
context,
force_index,
check_error,
config_bag)
return return
def _is_not_unique(value): def _is_not_unique(value):
#FIXME pourquoi la longueur doit etre egal ??? #FIXME pourquoi la longueur doit etre egal ?
if check_error and self.impl_is_unique() and len(set(value)) != len(value): if check_error and self.impl_is_unique() and len(set(value)) != len(value):
for idx, val in enumerate(value): for idx, val in enumerate(value):
if val in value[idx+1:]: if val in value[idx+1:]:
@ -303,12 +309,12 @@ class Option(OnlyOption):
do_validation(val, do_validation(val,
idx) idx)
self._valid_consistency(current_opt, self.valid_consistency(current_opt,
value, value,
context, context,
force_index, force_index,
check_error, check_error,
config_bag) config_bag)
except ValueError as err: except ValueError as err:
if debug: # pragma: no cover if debug: # pragma: no cover
log.debug('do_validation: value: {0}, index: {1}:' log.debug('do_validation: value: {0}, index: {1}:'
@ -458,13 +464,13 @@ class Option(OnlyOption):
self._add_dependency(opt) self._add_dependency(opt)
opt._add_dependency(self) opt._add_dependency(self)
def _valid_consistency(self, def valid_consistency(self,
option, option,
value, value,
context, context,
index, index,
check_error, check_error,
config_bag): config_bag):
if context is not undefined: if context is not undefined:
descr = context.cfgimpl_get_description() descr = context.cfgimpl_get_description()
# no consistency found at all # no consistency found at all
@ -494,30 +500,70 @@ class Option(OnlyOption):
else: else:
opts = all_cons_opts opts = all_cons_opts
wopt = opts[0]() wopt = opts[0]()
wopt._launch_consistency(self, wopt.launch_consistency(self,
func, func,
cons_id, cons_id,
option, option,
value, value,
context, context,
index, index,
opts, opts,
warnings_only, warnings_only,
transitive, transitive,
config_bag) config_bag)
def _launch_consistency(self, def _get_consistency_value(self,
current_opt, orig_option,
func, current_option,
cons_id, index,
option, cons_id,
value, context,
context, config_bag,
index, value,
opts, transitive,
warnings_only, func):
transitive, if func in ALLOWED_CONST_LIST:
config_bag): index = None
index_ = None
elif not current_option.impl_is_master_slaves('slave'):
index_ = None
else:
index_ = index
if orig_option == current_option:
# orig_option is current option
# we have already value, so use it
return value
if context is undefined:
#if no context get default value
return current_option.impl_getdefault()
#otherwise calculate value
sconfig_bag = config_bag.copy('nooption')
sconfig_bag.option = current_option
sconfig_bag.fromconsistency.append(cons_id)
sconfig_bag.force_permissive = True
path = current_option.impl_getpath(context)
opt_value = context.getattr(path,
index_,
sconfig_bag)
if index_ is None and index is not None:
if len(opt_value) <= index:
opt_value = current_option.impl_getdefault_multi()
else:
opt_value = opt_value[index]
return opt_value
def launch_consistency(self,
current_opt,
func,
cons_id,
option,
value,
context,
index,
opts,
warnings_only,
transitive,
config_bag):
"""Launch consistency now """Launch consistency now
:param func: function name, this name should start with _cons_ :param func: function name, this name should start with _cons_
@ -537,111 +583,93 @@ class Option(OnlyOption):
:param transitive: propertyerror is transitive :param transitive: propertyerror is transitive
:type transitive: `boolean` :type transitive: `boolean`
""" """
if context is not undefined: #if context is not undefined:
descr = context.cfgimpl_get_description() # descr = context.cfgimpl_get_description()
if config_bag is not undefined and cons_id in config_bag.fromconsistency: if config_bag is not undefined and cons_id in config_bag.fromconsistency:
return return
all_cons_vals = [] all_cons_vals = []
all_cons_opts = [] all_cons_opts = []
length = None length = None
for opt in opts: for opt in opts:
if isinstance(opt, weakref.ReferenceType): if isinstance(opt, weakref.ReferenceType):
opt = opt() opt = opt()
if option == opt: is_multi = False
# option is current option
# we have already value, so use it
opt_value = value
elif context is undefined:
opt_value = opt.impl_getdefault()
else:
#if context, calculate value, otherwise get default value
sconfig_bag = config_bag.copy('nooption')
sconfig_bag.option = opt
sconfig_bag.fromconsistency.append(cons_id)
sconfig_bag.force_permissive = True
path = opt.impl_getpath(context)
if opt.impl_is_master_slaves('slave'):
index_ = index
else:
index_ = None
try:
opt_value = context.getattr(path,
index_,
sconfig_bag)
except PropertiesOptionError as err:
if debug: # pragma: no cover
log.debug('propertyerror in _launch_consistency: {0}'.format(err))
if transitive:
err.set_orig_opt(option)
raise err
opt_value = None
if not option == opt and opt_value is not None and index is not None and \
(context is undefined or \
not opt.impl_is_master_slaves('slave')):
if len(opt_value) <= index:
opt_value = opt.impl_getdefault_multi()
else:
opt_value = opt_value[index]
if opt_value is not None and opt.impl_is_multi() and index is None and func not in ALLOWED_CONST_LIST:
if length is not None and length != len(opt_value):
if context is undefined:
return
raise ValueError(_('unexpected length of "{}" in constency "{}", should be "{}"'
'').format(len(opt_value),
opt.impl_get_display_name(),
length))
else:
length = len(opt_value)
is_multi = True
else:
is_multi = False
if isinstance(opt_value, list) and func in ALLOWED_CONST_LIST:
for value_ in opt_value:
if isinstance(value_, list):
for val in value_:
all_cons_vals.append((False, val))
all_cons_opts.append(opt)
else:
all_cons_vals.append((False, value_))
all_cons_opts.append(opt)
else:
all_cons_vals.append((is_multi, opt_value))
all_cons_opts.append(opt)
else:
try: try:
all_values = [] opt_value = self._get_consistency_value(option,
if length is None: opt,
all_value = [] index,
for is_multi, values in all_cons_vals: cons_id,
all_value.append(values) context,
all_values = [all_value] config_bag,
else: value,
for idx in range(length): transitive,
all_value = [] func)
for is_multi, values in all_cons_vals: except PropertiesOptionError as err:
if not is_multi: if debug: # pragma: no cover
all_value.append(values) log.debug('propertyerror in launch_consistency: {0}'.format(err))
else: if transitive:
all_value.append(values[idx]) err.set_orig_opt(option)
all_values.append(all_value)
for values in all_values:
getattr(self, func)(current_opt,
all_cons_opts,
values,
warnings_only)
except ValueError as err:
if warnings_only:
msg = _('attention, "{0}" could be an invalid {1} for "{2}", {3}'
'').format(value,
self._display_name,
current_opt.impl_get_display_name(),
err)
warnings.warn_explicit(ValueWarning(msg, weakref.ref(self)),
ValueWarning,
self.__class__.__name__, 0)
else:
raise err raise err
else:
if opt.impl_is_multi() and index is None and func not in ALLOWED_CONST_LIST:
len_value = len(opt_value)
if length is not None and length != len_value:
if context is undefined:
return
raise ValueError(_('unexpected length of "{}" in constency "{}", should be "{}"'
'').format(len(opt_value),
opt.impl_get_display_name(),
length))
else:
length = len_value
is_multi = True
if isinstance(opt_value, list) and func in ALLOWED_CONST_LIST:
for value_ in opt_value:
if opt.impl_is_submulti():
for val in value_:
all_cons_vals.append((False, val))
all_cons_opts.append(opt)
else:
all_cons_vals.append((False, value_))
all_cons_opts.append(opt)
else:
all_cons_vals.append((is_multi, opt_value))
all_cons_opts.append(opt)
if config_bag is not undefined and not config_bag.validate:
return
all_values = []
if length is None:
all_value = []
for is_multi, values in all_cons_vals:
all_value.append(values)
all_values = [all_value]
else:
for idx in range(length):
all_value = []
for is_multi, values in all_cons_vals:
if not is_multi:
all_value.append(values)
else:
all_value.append(values[idx])
all_values.append(all_value)
try:
for values in all_values:
getattr(self, func)(current_opt,
all_cons_opts,
values,
warnings_only)
except ValueError as err:
if warnings_only:
msg = _('attention, "{0}" could be an invalid {1} for "{2}", {3}'
'').format(value,
self._display_name,
current_opt.impl_get_display_name(),
err)
warnings.warn_explicit(ValueWarning(msg, weakref.ref(self)),
ValueWarning,
self.__class__.__name__, 0)
else:
raise err
def _cons_not_equal(self, def _cons_not_equal(self,
current_opt, current_opt,

View File

@ -124,36 +124,53 @@ class ConfigBag(object):
'option', 'option',
'ori_option', 'ori_option',
'properties', 'properties',
'validate',
'setting_properties', 'setting_properties',
'force_permissive', 'force_permissive',
'force_unrestraint', 'force_unrestraint',
'display_warnings',
'trusted_cached_properties', 'trusted_cached_properties',
'fromconsistency', 'fromconsistency',
'_validator'
) )
def __init__(self, config, **kwargs): def __init__(self, config, **kwargs):
self.default = {'force_permissive': False, self.default = {'force_permissive': False,
'force_unrestraint': False, 'force_unrestraint': False,
'display_warnings': True,
'trusted_cached_properties': True, 'trusted_cached_properties': True,
} }
self.config = config self.config = config
self._validator = True
self.fromconsistency = [] self.fromconsistency = []
for key, value in kwargs.items(): for key, value in kwargs.items():
if value != self.default.get(key): if value != self.default.get(key):
setattr(self, key, value) setattr(self, key, value)
def __getattr__(self, key): def __getattr__(self, key):
if key in ['validate', 'validate_properties']: if key == 'validate_properties':
return not self.force_unrestraint return not self.force_unrestraint
if key == 'validate':
if self.setting_properties is not None:
return 'validator' in self.setting_properties
return self._validator
if key == 'setting_properties': if key == 'setting_properties':
if self.force_unrestraint: if self.force_unrestraint:
return None return None
self.setting_properties = self.config.cfgimpl_get_settings().get_context_properties() self.setting_properties = self.config.cfgimpl_get_settings().get_context_properties()
return self.setting_properties return self.setting_properties
if key not in self.__slots__:
raise KeyError('unknown key {}'.format(key))
return self.default.get(key) return self.default.get(key)
def __setattr__(self, key, value):
if key == 'validate':
if self.setting_properties is not None:
if value is False:
self.setting_properties = frozenset(set(self.setting_properties) - {'validator'})
else:
self.setting_properties = frozenset(set(self.setting_properties) | {'validator'})
else:
self._validator = value
else:
super().__setattr__(key, value)
def delete(self, key): def delete(self, key):
try: try:
return self.__delattr__(key) return self.__delattr__(key)

View File

@ -100,22 +100,20 @@ class Values(object):
value = self.getvalue(path, value = self.getvalue(path,
index, index,
config_bag) config_bag)
#FIXME suboptimal ...
# validate value # validate value
if config_bag.validate: context = self._getcontext()
context = self._getcontext() opt = config_bag.option
opt = config_bag.option opt.impl_validate(value,
context=context,
force_index=index,
check_error=True,
config_bag=config_bag)
if setting_properties and 'warnings' in setting_properties:
opt.impl_validate(value, opt.impl_validate(value,
context=context, context=context,
force_index=index, force_index=index,
check_error=True, check_error=False,
config_bag=config_bag) config_bag=config_bag)
if config_bag.display_warnings:
opt.impl_validate(value,
context=context,
force_index=index,
check_error=False,
config_bag=config_bag)
# store value in cache # store value in cache
if not is_cached and \ if not is_cached and \
setting_properties and 'cache' in setting_properties: setting_properties and 'cache' in setting_properties:
@ -178,9 +176,9 @@ class Values(object):
#so return default value #so return default value
else: else:
return value return value
return self._getdefaultvalue(path, return self.getdefaultvalue(path,
index, index,
config_bag) config_bag)
def getdefaultvalue(self, def getdefaultvalue(self,
path, path,
@ -197,14 +195,6 @@ class Values(object):
:type index: int :type index: int
:returns: default value :returns: default value
""" """
return self._getdefaultvalue(path,
index,
config_bag)
def _getdefaultvalue(self,
path,
index,
config_bag):
context = self._getcontext() context = self._getcontext()
opt = config_bag.option opt = config_bag.option
def _reset_cache(_value): def _reset_cache(_value):
@ -325,9 +315,7 @@ class Values(object):
context = self._getcontext() context = self._getcontext()
owner = context.cfgimpl_get_settings().getowner() owner = context.cfgimpl_get_settings().getowner()
if config_bag.setting_properties is not None and \ if config_bag.validate:
'validator' in config_bag.setting_properties and \
config_bag.validate:
if index is not None or config_bag.option._has_consistencies(context): if index is not None or config_bag.option._has_consistencies(context):
# set value to a fake config when option has dependency # set value to a fake config when option has dependency
# validation will be complet in this case (consistency, ...) # validation will be complet in this case (consistency, ...)
@ -365,7 +353,7 @@ class Values(object):
context = self._getcontext() context = self._getcontext()
settings = context.cfgimpl_get_settings() settings = context.cfgimpl_get_settings()
# First validate properties with this value # First validate properties with this value
self_properties = config_bag.self_properties self_properties = config_bag.properties
if self_properties is None: if self_properties is None:
self_properties = settings.getproperties(path, self_properties = settings.getproperties(path,
index, index,
@ -385,12 +373,13 @@ class Values(object):
context, context,
check_error=True, check_error=True,
force_index=index) force_index=index)
# No error found so emit warnings if config_bag.setting_properties and 'warnings' in config_bag.setting_properties:
opt.impl_validate(value, # No error found so emit warnings
config_bag, opt.impl_validate(value,
context, config_bag,
check_error=False, context,
force_index=index) check_error=False,
force_index=index)
def _setvalue(self, def _setvalue(self,
path, path,
@ -555,16 +544,16 @@ class Values(object):
setting = context.cfgimpl_get_settings() setting = context.cfgimpl_get_settings()
hasvalue = self._p_.hasvalue(path) hasvalue = self._p_.hasvalue(path)
if config_bag.validate and hasvalue and 'validator' in config_bag.setting_properties: if hasvalue and config_bag.validate:
fake_context = context._gen_fake_values() fake_context = context._gen_fake_values()
fake_value = fake_context.cfgimpl_get_values() fake_value = fake_context.cfgimpl_get_values()
sconfig_bag = config_bag.copy() sconfig_bag = config_bag.copy()
sconfig_bag.validate = False sconfig_bag.validate = False
fake_value.reset(path, fake_value.reset(path,
sconfig_bag) sconfig_bag)
value = fake_value._getdefaultvalue(path, value = fake_value.getdefaultvalue(path,
None, None,
config_bag) config_bag)
fake_value.setvalue_validation(path, fake_value.setvalue_validation(path,
None, None,
value, value,
@ -578,9 +567,9 @@ class Values(object):
if 'force_store_value' in setting.getproperties(path, if 'force_store_value' in setting.getproperties(path,
None, None,
config_bag): config_bag):
value = self._getdefaultvalue(path, value = self.getdefaultvalue(path,
None, None,
config_bag) config_bag)
self._setvalue(path, self._setvalue(path,
None, None,
value, value,
@ -601,7 +590,7 @@ class Values(object):
if self._p_.hasvalue(path, index=index): if self._p_.hasvalue(path, index=index):
context = self._getcontext() context = self._getcontext()
if config_bag.validate and 'validator' in config_bag.setting_properties: if config_bag.validate:
fake_context = context._gen_fake_values() fake_context = context._gen_fake_values()
fake_value = fake_context.cfgimpl_get_values() fake_value = fake_context.cfgimpl_get_values()
sconfig_bag = config_bag.copy() sconfig_bag = config_bag.copy()
@ -609,9 +598,9 @@ class Values(object):
fake_value.reset_slave(path, fake_value.reset_slave(path,
index, index,
sconfig_bag) sconfig_bag)
value = fake_value._getdefaultvalue(path, value = fake_value.getdefaultvalue(path,
index, index,
config_bag) config_bag)
fake_value.setvalue_validation(path, fake_value.setvalue_validation(path,
index, index,
value, value,
@ -756,11 +745,10 @@ class Values(object):
context = self._getcontext() context = self._getcontext()
# copy # copy
od_setting_properties = config_bag.setting_properties - {'mandatory', 'empty'} od_setting_properties = config_bag.setting_properties - {'mandatory', 'empty'}
setting_properties = set(config_bag.setting_properties) setting_properties = set(config_bag.setting_properties) - {'warnings'}
setting_properties.update(['mandatory', 'empty']) setting_properties.update(['mandatory', 'empty'])
config_bag.setting_properties = frozenset(setting_properties) config_bag.setting_properties = frozenset(setting_properties)
config_bag.force_permissive = True config_bag.force_permissive = True
config_bag.display_warnings = False
descr = context.cfgimpl_get_description() descr = context.cfgimpl_get_description()
return self._mandatory_warnings(context, return self._mandatory_warnings(context,