add global 'empty' property, this property raise mandatory PropertiesOptionError...
authorEmmanuel Garette <egarette@cadoles.com>
Sun, 26 Jul 2015 16:55:21 +0000 (18:55 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Sun, 26 Jul 2015 17:13:25 +0000 (19:13 +0200)
ChangeLog
test/test_mandatory.py
test/test_multi.py
tiramisu/option/baseoption.py
tiramisu/setting.py
tiramisu/storage/dictionary/option.py
tiramisu/value.py

index e5980ff..68f2dbc 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+Sun Jul 26 19:09:29 2015 +0200 Emmanuel Garette <egarette@cadoles.com>
+       * add global 'empty' property, this property raise mandatory
+       PropertiesOptionError if multi or master have empty value 
+
 Fri Jul 24 18:03:59 2015 +0200 Emmanuel Garette <egarette@cadoles.com>
        * add duplicate option to Config, to generate new Config with same
        value, properties, Option. Option are not duplication.
index 69ea2ad..2c13c1e 100644 (file)
@@ -1,12 +1,13 @@
+# coding: utf-8
 from autopath import do_autopath
 do_autopath()
-
 from time import sleep
 
-#from py.test import raises
+from py.test import raises
 from tiramisu.config import Config
 from tiramisu.option import StrOption, UnicodeOption, OptionDescription
 from tiramisu.error import PropertiesOptionError
+from tiramisu.setting import groups
 
 
 def make_description():
@@ -316,3 +317,87 @@ def test_mandatory_warnings_frozen():
     config.read_only()
     assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
     sleep(.1)
+
+
+def test_mandatory_master():
+    ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True,
+                              properties=('mandatory', ))
+    netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau",
+                                   multi=True)
+    interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
+    interface1.impl_set_group_type(groups.master)
+    o = OptionDescription('o', '', [interface1])
+    config = Config(o)
+    config.read_only()
+    raises(PropertiesOptionError, 'config.ip_admin_eth0.ip_admin_eth0')
+    raises(PropertiesOptionError, 'config.ip_admin_eth0.netmask_admin_eth0')
+
+
+def test_mandatory_master_empty():
+    ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
+    netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau",
+                                   multi=True)
+    interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
+    interface1.impl_set_group_type(groups.master)
+    o = OptionDescription('o', '', [interface1])
+    config = Config(o)
+    config.read_write()
+    assert config.ip_admin_eth0.ip_admin_eth0 == []
+    assert config.ip_admin_eth0.netmask_admin_eth0 == []
+    #
+    config.ip_admin_eth0.ip_admin_eth0.append()
+    assert config.ip_admin_eth0.ip_admin_eth0 == [None]
+    assert config.ip_admin_eth0.netmask_admin_eth0 == [None]
+    config.read_only()
+    raises(PropertiesOptionError, "config.ip_admin_eth0.ip_admin_eth0")
+    raises(PropertiesOptionError, "config.ip_admin_eth0.netmask_admin_eth0")
+    config.read_write()
+    del(config.ip_admin_eth0.ip_admin_eth0)
+    del(config.ip_admin_eth0.netmask_admin_eth0)
+    assert config.ip_admin_eth0.ip_admin_eth0 == []
+    assert config.ip_admin_eth0.netmask_admin_eth0 == []
+    #
+    config.ip_admin_eth0.ip_admin_eth0.append('')
+    assert config.ip_admin_eth0.ip_admin_eth0 == ['']
+    assert config.ip_admin_eth0.netmask_admin_eth0 == [None]
+    config.read_only()
+    raises(PropertiesOptionError, "config.ip_admin_eth0.ip_admin_eth0")
+    raises(PropertiesOptionError, "config.ip_admin_eth0.netmask_admin_eth0")
+    config.read_write()
+    #
+    config.read_write()
+    config.ip_admin_eth0.ip_admin_eth0 = ['ip']
+    config.read_only()
+    assert config.ip_admin_eth0.ip_admin_eth0 == ['ip']
+    assert config.ip_admin_eth0.netmask_admin_eth0 == [None]
+
+
+def test_mandatory_slave():
+    ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
+    netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau",
+                                   multi=True, properties=('mandatory', ))
+    interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
+    interface1.impl_set_group_type(groups.master)
+    o = OptionDescription('o', '', [interface1])
+    config = Config(o)
+    config.read_only()
+    assert config.ip_admin_eth0.ip_admin_eth0 == []
+    assert config.ip_admin_eth0.netmask_admin_eth0 == []
+    #
+    config.read_write()
+    config.ip_admin_eth0.ip_admin_eth0.append('ip')
+    config.read_only()
+    assert config.ip_admin_eth0.ip_admin_eth0 == ['ip']
+    raises(PropertiesOptionError, 'config.ip_admin_eth0.netmask_admin_eth0')
+    #
+    config.read_write()
+    config.ip_admin_eth0.netmask_admin_eth0 = ['']
+    config.read_only()
+    assert config.ip_admin_eth0.ip_admin_eth0 == ['ip']
+    raises(PropertiesOptionError, 'config.ip_admin_eth0.netmask_admin_eth0')
+    #
+    config.read_write()
+    config.ip_admin_eth0.netmask_admin_eth0 = ['ip']
+    config.read_only()
+    assert config.ip_admin_eth0.ip_admin_eth0 == ['ip']
+    assert config.ip_admin_eth0.netmask_admin_eth0 == ['ip']
index 6d06011..29c9ef1 100644 (file)
@@ -3,9 +3,9 @@ from autopath import do_autopath
 do_autopath()
 
 from tiramisu.value import Multi
-from tiramisu.option import IntOption, OptionDescription
+from tiramisu.option import IntOption, StrOption, OptionDescription
 from tiramisu.config import Config
-from tiramisu.error import ConfigError
+from tiramisu.error import ConfigError, PropertiesOptionError
 
 import weakref
 from py.test import raises
@@ -21,3 +21,21 @@ def test_multi():
     assert c is multi._getcontext()
     del(c)
     raises(ConfigError, "multi._getcontext()")
+
+
+def test_multi_none():
+    s = StrOption('str', '', multi=True)
+    o = OptionDescription('od', '', [s])
+    c = Config(o)
+    c.read_only()
+    assert c.str == []
+    c.read_write()
+    c.str.append(None)
+    assert c.str == [None]
+    c.read_only()
+    raises(PropertiesOptionError, "c.str")
+    c.read_write()
+    c.str = ['']
+    assert c.str == ['']
+    c.read_only()
+    raises(PropertiesOptionError, "c.str")
index 88c24f9..6f0564e 100644 (file)
@@ -99,7 +99,7 @@ class Base(StorageBase):
     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, warnings_only=False, extra=None, allow_empty_list=False):
+                 properties=None, warnings_only=False, extra=None, allow_empty_list=undefined):
         if not valid_name(name):  # pragma: optional cover
             raise ValueError(_("invalid name: {0} for option").format(name))
         if requires is not None:
@@ -900,7 +900,7 @@ class SymLinkOption(OnlyOption):
                                'for symlink {0}').format(name))
         super(Base, self).__init__(name, undefined, undefined, undefined,
                                    undefined, undefined, undefined, undefined,
-                                   False, opt)
+                                   undefined, opt)
         self.commit()
 
     def __getattr__(self, name, context=undefined):
index ba63853..f371e5b 100644 (file)
@@ -75,6 +75,9 @@ everything_frozen
     whole option in config are frozen (even if option have not frozen
     property)
 
+empty
+    raise mandatory PropertiesOptionError if multi or master have empty value
+
 validator
     launch validator set by user in option (this property has no effect
     for internal validator)
@@ -97,10 +100,10 @@ read_write
     you can set all variables not frozen
 """
 ro_append = set(['frozen', 'disabled', 'validator', 'everything_frozen',
-                'mandatory'])
+                'mandatory', 'empty'])
 ro_remove = set(['permissive', 'hidden'])
 rw_append = set(['frozen', 'disabled', 'validator', 'hidden'])
-rw_remove = set(['permissive', 'everything_frozen', 'mandatory'])
+rw_remove = set(['permissive', 'everything_frozen', 'mandatory', 'empty'])
 
 
 forbidden_set_properties = set(['force_store_value'])
@@ -468,8 +471,13 @@ class Settings(object):
         else:
             if 'mandatory' in properties and \
                     not self._getcontext().cfgimpl_get_values()._isempty(
-                        opt_or_descr, value, opt_or_descr.impl_allow_empty_list()):
+                        opt_or_descr, value):
                 properties.remove('mandatory')
+            elif not is_write and 'empty' in forced_properties and \
+                    not opt_or_descr.impl_is_master_slaves('slave') and \
+                    self._getcontext().cfgimpl_get_values()._isempty(
+                        opt_or_descr, value, force_allow_empty_list=True):
+                properties.add('mandatory')
             if is_write and 'everything_frozen' in forced_properties:
                 properties.add('frozen')
             elif 'frozen' in properties and not is_write:
index 219e606..bab035f 100644 (file)
@@ -79,7 +79,7 @@ class StorageBase(object):
             self._properties = properties
         if opt is not undefined:
             self._opt = opt
-        if allow_empty_list is not False:
+        if allow_empty_list is not undefined:
             self._allow_empty_list = allow_empty_list
 
     def _set_default_values(self, default, default_multi):
@@ -302,8 +302,7 @@ class StorageBase(object):
         try:
             return self._allow_empty_list
         except AttributeError:
-            return False
-
+            return undefined
 
     def _get_extra(self, key):
         extra = self._extra
index 2ff1fe5..c5f45e5 100644 (file)
@@ -175,18 +175,27 @@ class Values(object):
         if hasvalue:
             self._p_.resetvalue(path)
 
-    def _isempty(self, opt, value, allow_empty_list):
+    def _isempty(self, opt, value, force_allow_empty_list=False):
         "convenience method to know if an option is empty"
-        empty = opt._empty
-        if value is not undefined:
-            empty_not_multi = not opt.impl_is_multi() and (value is None or
-                                                           value == empty)
-            empty_multi = opt.impl_is_multi() and ((not allow_empty_list and value == []) or
-                                                   None in value or
-                                                   empty in value)
+        if value is undefined:
+            return False
         else:
-            empty_multi = empty_not_multi = False
-        return empty_not_multi or empty_multi
+            empty = opt._empty
+            if opt.impl_is_multi():
+                if force_allow_empty_list:
+                    allow_empty_list = True
+                else:
+                    allow_empty_list = opt.impl_allow_empty_list()
+                    if allow_empty_list is undefined:
+                        if opt.impl_is_master_slaves('slave'):
+                            allow_empty_list = True
+                        else:
+                            allow_empty_list = False
+                isempty = (not allow_empty_list and value == []) or \
+                    None in value or empty in value
+            else:
+                isempty = value is None or value == empty
+        return isempty
 
     def __getitem__(self, opt):
         "enables us to use the pythonic dictionary-like access to values"