add warning ability
authorEmmanuel Garette <egarette@cadoles.com>
Tue, 24 Sep 2013 21:19:20 +0000 (23:19 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Tue, 24 Sep 2013 21:19:20 +0000 (23:19 +0200)
test/test_option_validator.py
tiramisu/option.py
tiramisu/value.py

index 8e00916..030a7c0 100644 (file)
@@ -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")
index fb76444..4112050 100644 (file)
@@ -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:
index d587de1..247d273 100644 (file)
@@ -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}"