add warning ability

This commit is contained in:
Emmanuel Garette 2013-09-24 23:19:20 +02:00
parent 5ba97c5928
commit 06baff2f3b
3 changed files with 128 additions and 56 deletions

View file

@ -3,7 +3,6 @@ from py.test import raises
from tiramisu.config import Config from tiramisu.config import Config
from tiramisu.option import StrOption, OptionDescription from tiramisu.option import StrOption, OptionDescription
from tiramisu.error import ConfigError
def return_true(value, param=None): def return_true(value, param=None):
@ -13,37 +12,36 @@ def return_true(value, param=None):
def return_false(value, param=None): def return_false(value, param=None):
if value == 'val' and param in [None, 'yes']: if value == 'val' and param in [None, 'yes']:
return False raise ValueError('error')
def return_val(value, param=None): def return_val(value, param=None):
return 'val' return 'val'
def return_if_val(value):
if value != 'val':
raise ValueError('error')
def test_validator(): def test_validator():
opt1 = StrOption('opt1', '', validator=return_true, default='val') opt1 = StrOption('opt1', '', validator=return_true, default='val')
raises(ValueError, "StrOption('opt2', '', validator=return_false, default='val')") raises(ValueError, "StrOption('opt2', '', validator=return_false, default='val')")
raises(ConfigError, "StrOption('opt3', '', validator=return_val, default='val')")
opt2 = StrOption('opt2', '', validator=return_false) opt2 = StrOption('opt2', '', validator=return_false)
opt3 = StrOption('opt3', '', validator=return_val) root = OptionDescription('root', '', [opt1, opt2])
root = OptionDescription('root', '', [opt1, opt2, opt3])
cfg = Config(root) cfg = Config(root)
assert cfg.opt1 == 'val' assert cfg.opt1 == 'val'
raises(ValueError, "cfg.opt2 = 'val'") raises(ValueError, "cfg.opt2 = 'val'")
raises(ConfigError, "cfg.opt3 = 'val'")
def test_validator_params(): def test_validator_params():
opt1 = StrOption('opt1', '', validator=return_true, validator_params={'': ('yes',)}, default='val') opt1 = StrOption('opt1', '', validator=return_true, validator_params={'': ('yes',)}, default='val')
raises(ValueError, "StrOption('opt2', '', validator=return_false, validator_params={'': ('yes',)}, default='val')") raises(ValueError, "StrOption('opt2', '', validator=return_false, validator_params={'': ('yes',)}, default='val')")
raises(ConfigError, "StrOption('opt3', '', validator=return_val, validator_params={'': ('yes',)}, default='val')")
opt2 = StrOption('opt2', '', validator=return_false, validator_params={'': ('yes',)}) opt2 = StrOption('opt2', '', validator=return_false, validator_params={'': ('yes',)})
opt3 = StrOption('opt3', '', validator=return_val, validator_params={'': ('yes',)}) root = OptionDescription('root', '', [opt1, opt2])
root = OptionDescription('root', '', [opt1, opt2, opt3])
cfg = Config(root) cfg = Config(root)
assert cfg.opt1 == 'val' assert cfg.opt1 == 'val'
raises(ValueError, "cfg.opt2 = 'val'") raises(ValueError, "cfg.opt2 = 'val'")
raises(ConfigError, "cfg.opt3 = 'val'")
def test_validator_params_key(): def test_validator_params_key():
@ -57,3 +55,36 @@ def test_validator_params_key():
def test_validator_params_option(): def test_validator_params_option():
opt0 = StrOption('opt0', '', default='val') opt0 = StrOption('opt0', '', default='val')
raises(ValueError, "opt1 = StrOption('opt1', '', validator=return_true, validator_params={'': ((opt0, False),)}, default='val')") raises(ValueError, "opt1 = StrOption('opt1', '', validator=return_true, validator_params={'': ((opt0, False),)}, default='val')")
def test_validator_multi():
opt1 = StrOption('opt1', '', validator=return_if_val, multi=True)
root = OptionDescription('root', '', [opt1])
cfg = Config(root)
assert cfg.opt1 == []
cfg.opt1.append('val')
assert cfg.opt1 == ['val']
raises(ValueError, "cfg.opt1.append('val1')")
raises(ValueError, "cfg.opt1 = ['val', 'val1']")
def test_validator_warning():
opt1 = StrOption('opt1', '', validator=return_true, default='val', only_warning=True)
opt2 = StrOption('opt2', '', validator=return_false, only_warning=True)
opt3 = StrOption('opt3', '', validator=return_if_val, multi=True, only_warning=True)
root = OptionDescription('root', '', [opt1, opt2, opt3])
cfg = Config(root)
assert cfg.opt1 == 'val'
cfg.opt1 = 'val'
assert cfg.cfgimpl_get_values().has_warning() is False
cfg.opt2 = 'val'
assert cfg.cfgimpl_get_values().has_warning() is True
assert cfg.cfgimpl_get_values().get_last_warning() == 'invalid value val for option opt2: error'
assert cfg.cfgimpl_get_values().has_warning() is False
cfg.opt3.append('val')
assert cfg.cfgimpl_get_values().has_warning() is False
cfg.opt3.append('val1')
assert cfg.cfgimpl_get_values().has_warning() is True
assert cfg.cfgimpl_get_values().get_last_warning() == 'invalid value val1 for option opt3: error'
assert cfg.cfgimpl_get_values().has_warning() is False
raises(ValueError, "cfg.opt2 = 1")

View file

@ -26,7 +26,7 @@ from copy import copy, deepcopy
from types import FunctionType from types import FunctionType
from IPy import IP from IPy import IP
from tiramisu.error import ConflictError, ConfigError from tiramisu.error import ConflictError
from tiramisu.setting import groups, multitypes from tiramisu.setting import groups, multitypes
from tiramisu.i18n import _ from tiramisu.i18n import _
from tiramisu.autolib import carry_out_calculation from tiramisu.autolib import carry_out_calculation
@ -327,13 +327,13 @@ class Option(BaseOption):
""" """
__slots__ = ('_multi', '_validator', '_default_multi', '_default', __slots__ = ('_multi', '_validator', '_default_multi', '_default',
'_state_callback', '_callback', '_multitype', '_state_callback', '_callback', '_multitype',
'_master_slaves', '__weakref__') '_only_warning', '_master_slaves', '__weakref__')
_empty = '' _empty = ''
def __init__(self, name, doc, default=None, default_multi=None, def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None, requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None, callback_params=None, validator=None, validator_params=None,
properties=None): properties=None, only_warning=False):
""" """
:param name: the option's name :param name: the option's name
:param doc: the option's description :param doc: the option's description
@ -351,6 +351,8 @@ class Option(BaseOption):
validation of the value validation of the value
:param validator_params: the validator's parameters :param validator_params: the validator's parameters
:param properties: tuple of default properties :param properties: tuple of default properties
:param only_warning: _validator and _consistencies don't raise if True
Values()._warning contain message
""" """
super(Option, self).__init__(name, doc, requires, properties) super(Option, self).__init__(name, doc, requires, properties)
@ -388,6 +390,7 @@ class Option(BaseOption):
default = [] default = []
self._multitype = multitypes.default self._multitype = multitypes.default
self._default_multi = default_multi self._default_multi = default_multi
self._only_warning = only_warning
self.impl_validate(default) self.impl_validate(default)
self._default = default self._default = default
@ -436,10 +439,17 @@ class Option(BaseOption):
if None not in (values, values_): if None not in (values, values_):
getattr(self, func)(opt_._name, values, values_) getattr(self, func)(opt_._name, values, values_)
def impl_validate(self, value, context=None, validate=True): def impl_validate(self, value, context=None, validate=True,
force_no_multi=False):
""" """
:param value: the option's value :param value: the option's value
:param context: Config's context
:type context: :class:`tiramisu.config.Config`
:param validate: if true enables ``self._validator`` validation :param validate: if true enables ``self._validator`` validation
:type validate: boolean
:param force_no_multi: if multi, value has to be a list
not if force_no_multi is True
:type force_no_multi: boolean
""" """
if not validate: if not validate:
return return
@ -456,46 +466,49 @@ class Option(BaseOption):
validator_params[''] = (val,) validator_params[''] = (val,)
else: else:
validator_params = {'': (val,)} validator_params = {'': (val,)}
ret = carry_out_calculation(self._name, config=context, # Raise ValueError if not valid
callback=self._validator[0], carry_out_calculation(self._name, config=context,
callback_params=validator_params) callback=self._validator[0],
if ret not in [False, True]: callback_params=validator_params)
raise ConfigError(_('validator should return a boolean, '
'not {0}').format(ret))
return ret
else:
return True
def do_validation(_value, _index=None): def do_validation(_value, _index=None):
if _value is None: if _value is None:
return True return True
if not val_validator(_value): ret_validation = None
raise ValueError(_("invalid value {0} "
"for option {1} for object {2}"
).format(_value,
self._name,
self.__class__.__name__))
try: try:
self._validate(_value) # valid with self._validator
val_validator(_value)
# if not context launch consistency validation
if context is not None:
descr._valid_consistency(self, _value, context, _index)
except ValueError as err: except ValueError as err:
raise ValueError(_("invalid value {0} for option {1}: {2}" msg = _("invalid value {0} for option {1}: {2}").format(
"").format(_value, self._name, err)) _value, self._name, err)
if context is not None: if self._only_warning:
descr._valid_consistency(self, _value, context, _index) ret_validation = msg
else:
raise ValueError(msg)
# option validation
self._validate(_value)
return ret_validation
# generic calculation # generic calculation
if context is not None: if context is not None:
descr = context.cfgimpl_get_description() descr = context.cfgimpl_get_description()
if not self._multi:
do_validation(value) ret = None
if not self._multi or force_no_multi:
ret = do_validation(value)
else: else:
if not isinstance(value, list): if not isinstance(value, list):
raise ValueError(_("invalid value {0} for option {1} " raise ValueError(_("invalid value {0} for option {1} "
"which must be a list").format(value, "which must be a list").format(value,
self._name)) self._name))
for index in range(0, len(value)): for index, val in enumerate(value):
val = value[index] ret_ = do_validation(val, index)
do_validation(val, index) if ret_ is not None:
ret = ret_
return ret
def impl_getdefault(self, default_multi=False): def impl_getdefault(self, default_multi=False):
"accessing the default value" "accessing the default value"
@ -610,7 +623,7 @@ class ChoiceOption(Option):
def __init__(self, name, doc, values, default=None, default_multi=None, def __init__(self, name, doc, values, default=None, default_multi=None,
requires=None, multi=False, callback=None, requires=None, multi=False, callback=None,
callback_params=None, open_values=False, validator=None, callback_params=None, open_values=False, validator=None,
validator_params=None, properties=()): validator_params=None, properties=None, only_warning=False):
""" """
:param values: is a list of values the option can possibly take :param values: is a list of values the option can possibly take
""" """
@ -629,7 +642,8 @@ class ChoiceOption(Option):
multi=multi, multi=multi,
validator=validator, validator=validator,
validator_params=validator_params, validator_params=validator_params,
properties=properties) properties=properties,
only_warning=only_warning)
def impl_get_values(self): def impl_get_values(self):
return self._values return self._values
@ -747,7 +761,8 @@ class IPOption(Option):
def __init__(self, name, doc, default=None, default_multi=None, def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None, requires=None, multi=False, callback=None,
callback_params=None, validator=None, validator_params=None, callback_params=None, validator=None, validator_params=None,
properties=None, only_private=False, allow_reserved=False): properties=None, only_private=False, allow_reserved=False,
only_warning=False):
self._only_private = only_private self._only_private = only_private
self._allow_reserved = allow_reserved self._allow_reserved = allow_reserved
super(IPOption, self).__init__(name, doc, default=default, super(IPOption, self).__init__(name, doc, default=default,
@ -758,7 +773,8 @@ class IPOption(Option):
multi=multi, multi=multi,
validator=validator, validator=validator,
validator_params=validator_params, validator_params=validator_params,
properties=properties) properties=properties,
only_warning=only_warning)
def _validate(self, value): def _validate(self, value):
ip = IP('{0}/32'.format(value)) ip = IP('{0}/32'.format(value))
@ -786,7 +802,7 @@ class PortOption(Option):
callback_params=None, validator=None, validator_params=None, callback_params=None, validator=None, validator_params=None,
properties=None, allow_range=False, allow_zero=False, properties=None, allow_range=False, allow_zero=False,
allow_wellknown=True, allow_registred=True, allow_wellknown=True, allow_registred=True,
allow_private=False): allow_private=False, only_warning=False):
self._allow_range = allow_range self._allow_range = allow_range
self._min_value = None self._min_value = None
self._max_value = None self._max_value = None
@ -818,7 +834,8 @@ class PortOption(Option):
multi=multi, multi=multi,
validator=validator, validator=validator,
validator_params=validator_params, validator_params=validator_params,
properties=properties) properties=properties,
only_warning=only_warning)
def _validate(self, value): def _validate(self, value):
if self._allow_range and ":" in str(value): if self._allow_range and ":" in str(value):
@ -864,7 +881,6 @@ class NetmaskOption(Option):
#opts must be (netmask, ip) options #opts must be (netmask, ip) options
self.__cons_netmask(optname, value, value_, True) self.__cons_netmask(optname, value, value_, True)
#def __cons_netmask(self, opt, value, context, index, opts, make_net):
def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net): def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net):
msg = None msg = None
try: try:
@ -903,7 +919,8 @@ class DomainnameOption(Option):
def __init__(self, name, doc, default=None, default_multi=None, def __init__(self, name, doc, default=None, default_multi=None,
requires=None, multi=False, callback=None, requires=None, multi=False, callback=None,
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',
only_warning=False):
#netbios: for MS domain #netbios: for MS domain
#hostname: to identify the device #hostname: to identify the device
#domainname: #domainname:
@ -922,7 +939,8 @@ class DomainnameOption(Option):
multi=multi, multi=multi,
validator=validator, validator=validator,
validator_params=validator_params, validator_params=validator_params,
properties=properties) properties=properties,
only_warning=only_warning)
def _validate(self, value): def _validate(self, value):
if self._allow_ip is True: if self._allow_ip is True:

View file

@ -33,7 +33,7 @@ class Values(object):
but the values are physicaly located here, in `Values`, wich is also but the values are physicaly located here, in `Values`, wich is also
responsible of a caching utility. responsible of a caching utility.
""" """
__slots__ = ('context', '_p_', '__weakref__') __slots__ = ('context', '_warning', '_p_', '__weakref__')
def __init__(self, context, storage): def __init__(self, context, storage):
""" """
@ -106,8 +106,9 @@ class Values(object):
path = self._get_opt_path(opt) path = self._get_opt_path(opt)
if self._p_.hasvalue(path): if self._p_.hasvalue(path):
setting = self.context().cfgimpl_get_settings() setting = self.context().cfgimpl_get_settings()
opt.impl_validate(opt.impl_getdefault(), self.context(), self._warning = opt.impl_validate(opt.impl_getdefault(),
'validator' in setting) self.context(),
'validator' in setting)
self.context().cfgimpl_reset_cache() self.context().cfgimpl_reset_cache()
if (opt.impl_is_multi() and if (opt.impl_is_multi() and
opt.impl_get_multitype() == multitypes.master): opt.impl_get_multitype() == multitypes.master):
@ -229,7 +230,8 @@ class Values(object):
else: else:
value = self._getvalue(opt, path, validate) value = self._getvalue(opt, path, validate)
if config_error is None and validate: if config_error is None and validate:
opt.impl_validate(value, self.context(), 'validator' in setting) self._warning = opt.impl_validate(value, self.context(),
'validator' in setting)
if config_error is None and self._is_default_owner(path) and \ if config_error is None and self._is_default_owner(path) and \
'force_store_value' in setting[opt]: 'force_store_value' in setting[opt]:
self.setitem(opt, value, path, is_write=False) self.setitem(opt, value, path, is_write=False)
@ -250,8 +252,9 @@ class Values(object):
# is_write is, for example, used with "force_store_value" # is_write is, for example, used with "force_store_value"
# user didn't change value, so not write # user didn't change value, so not write
# valid opt # valid opt
opt.impl_validate(value, self.context(), self._warning = opt.impl_validate(value, self.context(),
'validator' in self.context().cfgimpl_get_settings()) 'validator' in self.context(
).cfgimpl_get_settings())
if opt.impl_is_multi() and not isinstance(value, Multi): if opt.impl_is_multi() and not isinstance(value, Multi):
value = Multi(value, self.context, opt, path, setitem=True) value = Multi(value, self.context, opt, path, setitem=True)
self._setvalue(opt, path, value, force_permissive=force_permissive, self._setvalue(opt, path, value, force_permissive=force_permissive,
@ -370,6 +373,22 @@ class Values(object):
def __setstate__(self, states): def __setstate__(self, states):
self._p_ = states['_p_'] self._p_ = states['_p_']
def has_warning(self):
"""If option is "only_warning", validation error is store in
self._warning.
has_warning just indicate that a warning message is store
"""
return self._warning is not None
def get_last_warning(self):
"""Get last warning message in self._warning.
We can get only one time this message.
"""
ret = self._warning
self._warning = None
return ret
# ____________________________________________________________ # ____________________________________________________________
# multi types # multi types
@ -476,7 +495,9 @@ class Multi(list):
value = None value = None
self._validate(value) self._validate(value)
super(Multi, self).append(value) super(Multi, self).append(value)
self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force) self.context().cfgimpl_get_values()._setvalue(self.opt, self.path,
self,
validate_properties=not force)
if not force and self.opt.impl_get_multitype() == multitypes.master: if not force and self.opt.impl_get_multitype() == multitypes.master:
for slave in self.opt.impl_get_master_slaves(): for slave in self.opt.impl_get_master_slaves():
path = values._get_opt_path(slave) path = values._get_opt_path(slave)
@ -537,7 +558,9 @@ class Multi(list):
def _validate(self, value): def _validate(self, value):
if value is not None: if value is not None:
try: try:
self.opt._validate(value) self.context().cfgimpl_get_values()._warning = \
self.opt.impl_validate(value, context=self.context(),
force_no_multi=True)
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}"