From 06baff2f3b1006c4d025a6b1292eb6ca0b7dc992 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 24 Sep 2013 23:19:20 +0200 Subject: [PATCH] add warning ability --- test/test_option_validator.py | 51 +++++++++++++++---- tiramisu/option.py | 94 +++++++++++++++++++++-------------- tiramisu/value.py | 39 ++++++++++++--- 3 files changed, 128 insertions(+), 56 deletions(-) diff --git a/test/test_option_validator.py b/test/test_option_validator.py index 8e00916..030a7c0 100644 --- a/test/test_option_validator.py +++ b/test/test_option_validator.py @@ -3,7 +3,6 @@ from py.test import raises from tiramisu.config import Config from tiramisu.option import StrOption, OptionDescription -from tiramisu.error import ConfigError def return_true(value, param=None): @@ -13,37 +12,36 @@ def return_true(value, param=None): def return_false(value, param=None): if value == 'val' and param in [None, 'yes']: - return False + raise ValueError('error') def return_val(value, param=None): return 'val' +def return_if_val(value): + if value != 'val': + raise ValueError('error') + + def test_validator(): opt1 = StrOption('opt1', '', validator=return_true, 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) - opt3 = StrOption('opt3', '', validator=return_val) - root = OptionDescription('root', '', [opt1, opt2, opt3]) + root = OptionDescription('root', '', [opt1, opt2]) cfg = Config(root) assert cfg.opt1 == 'val' raises(ValueError, "cfg.opt2 = 'val'") - raises(ConfigError, "cfg.opt3 = 'val'") def test_validator_params(): opt1 = StrOption('opt1', '', validator=return_true, 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',)}) - opt3 = StrOption('opt3', '', validator=return_val, validator_params={'': ('yes',)}) - root = OptionDescription('root', '', [opt1, opt2, opt3]) + root = OptionDescription('root', '', [opt1, opt2]) cfg = Config(root) assert cfg.opt1 == 'val' raises(ValueError, "cfg.opt2 = 'val'") - raises(ConfigError, "cfg.opt3 = 'val'") def test_validator_params_key(): @@ -57,3 +55,36 @@ def test_validator_params_key(): def test_validator_params_option(): opt0 = StrOption('opt0', '', 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") diff --git a/tiramisu/option.py b/tiramisu/option.py index fb76444..4112050 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -26,7 +26,7 @@ from copy import copy, deepcopy from types import FunctionType from IPy import IP -from tiramisu.error import ConflictError, ConfigError +from tiramisu.error import ConflictError from tiramisu.setting import groups, multitypes from tiramisu.i18n import _ from tiramisu.autolib import carry_out_calculation @@ -327,13 +327,13 @@ class Option(BaseOption): """ __slots__ = ('_multi', '_validator', '_default_multi', '_default', '_state_callback', '_callback', '_multitype', - '_master_slaves', '__weakref__') + '_only_warning', '_master_slaves', '__weakref__') _empty = '' def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_params=None, - properties=None): + properties=None, only_warning=False): """ :param name: the option's name :param doc: the option's description @@ -351,6 +351,8 @@ class Option(BaseOption): validation of the value :param validator_params: the validator's parameters :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) @@ -388,6 +390,7 @@ class Option(BaseOption): default = [] self._multitype = multitypes.default self._default_multi = default_multi + self._only_warning = only_warning self.impl_validate(default) self._default = default @@ -436,10 +439,17 @@ class Option(BaseOption): if None not in (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 context: Config's context + :type context: :class:`tiramisu.config.Config` :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: return @@ -456,46 +466,49 @@ class Option(BaseOption): validator_params[''] = (val,) else: validator_params = {'': (val,)} - ret = carry_out_calculation(self._name, config=context, - callback=self._validator[0], - callback_params=validator_params) - if ret not in [False, True]: - raise ConfigError(_('validator should return a boolean, ' - 'not {0}').format(ret)) - return ret - else: - return True + # Raise ValueError if not valid + carry_out_calculation(self._name, config=context, + callback=self._validator[0], + callback_params=validator_params) def do_validation(_value, _index=None): if _value is None: return True - if not val_validator(_value): - raise ValueError(_("invalid value {0} " - "for option {1} for object {2}" - ).format(_value, - self._name, - self.__class__.__name__)) + ret_validation = None 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: - raise ValueError(_("invalid value {0} for option {1}: {2}" - "").format(_value, self._name, err)) - if context is not None: - descr._valid_consistency(self, _value, context, _index) + msg = _("invalid value {0} for option {1}: {2}").format( + _value, self._name, err) + if self._only_warning: + ret_validation = msg + else: + raise ValueError(msg) + # option validation + self._validate(_value) + return ret_validation # generic calculation if context is not None: 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: if not isinstance(value, list): raise ValueError(_("invalid value {0} for option {1} " "which must be a list").format(value, self._name)) - for index in range(0, len(value)): - val = value[index] - do_validation(val, index) + for index, val in enumerate(value): + ret_ = do_validation(val, index) + if ret_ is not None: + ret = ret_ + return ret def impl_getdefault(self, default_multi=False): "accessing the default value" @@ -610,7 +623,7 @@ class ChoiceOption(Option): def __init__(self, name, doc, values, default=None, default_multi=None, requires=None, multi=False, callback=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 """ @@ -629,7 +642,8 @@ class ChoiceOption(Option): multi=multi, validator=validator, validator_params=validator_params, - properties=properties) + properties=properties, + only_warning=only_warning) def impl_get_values(self): return self._values @@ -747,7 +761,8 @@ class IPOption(Option): def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_params=None, - properties=None, only_private=False, allow_reserved=False): + properties=None, only_private=False, allow_reserved=False, + only_warning=False): self._only_private = only_private self._allow_reserved = allow_reserved super(IPOption, self).__init__(name, doc, default=default, @@ -758,7 +773,8 @@ class IPOption(Option): multi=multi, validator=validator, validator_params=validator_params, - properties=properties) + properties=properties, + only_warning=only_warning) def _validate(self, value): ip = IP('{0}/32'.format(value)) @@ -786,7 +802,7 @@ class PortOption(Option): callback_params=None, validator=None, validator_params=None, properties=None, allow_range=False, allow_zero=False, allow_wellknown=True, allow_registred=True, - allow_private=False): + allow_private=False, only_warning=False): self._allow_range = allow_range self._min_value = None self._max_value = None @@ -818,7 +834,8 @@ class PortOption(Option): multi=multi, validator=validator, validator_params=validator_params, - properties=properties) + properties=properties, + only_warning=only_warning) def _validate(self, value): if self._allow_range and ":" in str(value): @@ -864,7 +881,6 @@ class NetmaskOption(Option): #opts must be (netmask, ip) options 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): msg = None try: @@ -903,7 +919,8 @@ class DomainnameOption(Option): def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_params=None, - properties=None, allow_ip=False, type_='domainname'): + properties=None, allow_ip=False, type_='domainname', + only_warning=False): #netbios: for MS domain #hostname: to identify the device #domainname: @@ -922,7 +939,8 @@ class DomainnameOption(Option): multi=multi, validator=validator, validator_params=validator_params, - properties=properties) + properties=properties, + only_warning=only_warning) def _validate(self, value): if self._allow_ip is True: diff --git a/tiramisu/value.py b/tiramisu/value.py index d587de1..247d273 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -33,7 +33,7 @@ class Values(object): but the values are physicaly located here, in `Values`, wich is also responsible of a caching utility. """ - __slots__ = ('context', '_p_', '__weakref__') + __slots__ = ('context', '_warning', '_p_', '__weakref__') def __init__(self, context, storage): """ @@ -106,8 +106,9 @@ class Values(object): path = self._get_opt_path(opt) if self._p_.hasvalue(path): setting = self.context().cfgimpl_get_settings() - opt.impl_validate(opt.impl_getdefault(), self.context(), - 'validator' in setting) + self._warning = opt.impl_validate(opt.impl_getdefault(), + self.context(), + 'validator' in setting) self.context().cfgimpl_reset_cache() if (opt.impl_is_multi() and opt.impl_get_multitype() == multitypes.master): @@ -229,7 +230,8 @@ class Values(object): else: value = self._getvalue(opt, path, 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 \ 'force_store_value' in setting[opt]: 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" # user didn't change value, so not write # valid opt - opt.impl_validate(value, self.context(), - 'validator' in self.context().cfgimpl_get_settings()) + self._warning = opt.impl_validate(value, self.context(), + 'validator' in self.context( + ).cfgimpl_get_settings()) if opt.impl_is_multi() and not isinstance(value, Multi): value = Multi(value, self.context, opt, path, setitem=True) self._setvalue(opt, path, value, force_permissive=force_permissive, @@ -370,6 +373,22 @@ class Values(object): def __setstate__(self, states): 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 @@ -476,7 +495,9 @@ class Multi(list): value = None self._validate(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: for slave in self.opt.impl_get_master_slaves(): path = values._get_opt_path(slave) @@ -537,7 +558,9 @@ class Multi(list): def _validate(self, value): if value is not None: 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: raise ValueError(_("invalid value {0} " "for option {1}: {2}"