optimise mandatory_warnings
authorEmmanuel Garette <egarette@cadoles.com>
Thu, 29 Oct 2015 08:03:13 +0000 (09:03 +0100)
committerEmmanuel Garette <egarette@cadoles.com>
Thu, 29 Oct 2015 08:03:13 +0000 (09:03 +0100)
test/test_cache.py
test/test_dyn_optiondescription.py
test/test_mandatory.py
tiramisu/config.py
tiramisu/option/masterslave.py
tiramisu/setting.py
tiramisu/value.py

index 2eb2268..7a6f6dc 100644 (file)
@@ -6,7 +6,7 @@ from tiramisu import setting
 setting.expires_time = 1
 from tiramisu.option import IntOption, OptionDescription
 from tiramisu.config import Config
 setting.expires_time = 1
 from tiramisu.option import IntOption, OptionDescription
 from tiramisu.config import Config
-from tiramisu.error import ConfigError
+from tiramisu.error import ConfigError, PropertiesOptionError
 
 
 from time import sleep, time
 
 
 from time import sleep, time
index 89da108..9dab50b 100644 (file)
@@ -284,7 +284,7 @@ def test_mandatory_dyndescription():
     del(cfg.od.dodval1.stval1)
     cfg.read_only()
     raises(PropertiesOptionError, "cfg.od.dodval1.stval1")
     del(cfg.od.dodval1.stval1)
     cfg.read_only()
     raises(PropertiesOptionError, "cfg.od.dodval1.stval1")
-    assert cfg.cfgimpl_get_values().mandatory_warnings() == ['od.dodval1.stval1', 'od.dodval2.stval2']
+    assert list(cfg.cfgimpl_get_values().mandatory_warnings()) == ['od.dodval1.stval1', 'od.dodval2.stval2']
 
 
 def test_build_dyndescription_context():
 
 
 def test_build_dyndescription_context():
@@ -467,7 +467,7 @@ def test_mandatory_dyndescription_context():
     del(cfg.od.dodval1.stval1)
     cfg.read_only()
     raises(PropertiesOptionError, "cfg.od.dodval1.stval1")
     del(cfg.od.dodval1.stval1)
     cfg.read_only()
     raises(PropertiesOptionError, "cfg.od.dodval1.stval1")
-    assert cfg.cfgimpl_get_values().mandatory_warnings() == ['od.dodval1.stval1', 'od.dodval2.stval2']
+    assert list(cfg.cfgimpl_get_values().mandatory_warnings()) == ['od.dodval1.stval1', 'od.dodval2.stval2']
 
 
 def test_increase_dyndescription_context():
 
 
 def test_increase_dyndescription_context():
index 2c13c1e..6669d1e 100644 (file)
@@ -1,12 +1,11 @@
 # coding: utf-8
 from autopath import do_autopath
 do_autopath()
 # coding: utf-8
 from autopath import do_autopath
 do_autopath()
-from time import sleep
 
 from py.test import raises
 from tiramisu.config import Config
 
 from py.test import raises
 from tiramisu.config import Config
-from tiramisu.option import StrOption, UnicodeOption, OptionDescription
-from tiramisu.error import PropertiesOptionError
+from tiramisu.option import IntOption, StrOption, UnicodeOption, OptionDescription, SymLinkOption
+from tiramisu.error import PropertiesOptionError, ConfigError
 from tiramisu.setting import groups
 
 
 from tiramisu.setting import groups
 
 
@@ -25,6 +24,61 @@ def make_description():
     return descr
 
 
     return descr
 
 
+def return_value(value):
+    return value
+
+
+def make_description2():
+    stroption = StrOption('str', 'Test string option', default="abc",
+                          properties=('mandatory', ))
+    stroption1 = StrOption('str1', 'Test string option',
+                           properties=('mandatory', ))
+    stroption2 = SymLinkOption('unicode2', stroption1)
+    stroption3 = StrOption('str3', 'Test string option', multi=True,
+                           properties=('mandatory', ))
+    unicode1 = UnicodeOption('unicode1', 'Test string option', callback=return_value, callback_params={'': ((stroption, False),)},  properties=('mandatory', ))
+    descr = OptionDescription('tiram', '', [stroption, stroption1, stroption2, stroption3, unicode1])
+    return descr
+
+
+def make_description_sym():
+    stroption = StrOption('str', 'Test string option', default="abc",
+                          properties=('mandatory', ))
+    stroption1 = StrOption('str1', 'Test string option',
+                           properties=('mandatory', ))
+    stroption2 = SymLinkOption('unicode2', stroption1)
+    stroption3 = StrOption('str3', 'Test string option', multi=True,
+                           properties=('mandatory', ))
+    descr = OptionDescription('tiram', '', [stroption, stroption1, stroption2, stroption3])
+    return descr
+
+
+def make_description3():
+    stroption = StrOption('str', 'Test string option', default="abc",
+                          properties=('mandatory', ))
+    stroption1 = StrOption('str1', 'Test string option',
+                           properties=('mandatory', ))
+    stroption2 = SymLinkOption('unicode2', stroption1)
+    stroption3 = StrOption('str3', 'Test string option', multi=True,
+                           properties=('mandatory', ))
+    unicode1 = UnicodeOption('unicode1', 'Test string option', callback=return_value, callback_params={'': ((stroption, False),)},  properties=('mandatory', ))
+    int1 = IntOption('int1', '', callback=return_value, callback_params={'': ((stroption, False),)},  properties=('mandatory', ))
+    descr = OptionDescription('tiram', '', [stroption, stroption1, stroption2, stroption3, unicode1, int1])
+    return descr
+
+
+def make_description4():
+    stroption = StrOption('str', 'Test string option', default="abc",
+                          properties=('mandatory', ))
+    stroption1 = StrOption('str1', 'Test string option',
+                           properties=('mandatory', ))
+    stroption2 = UnicodeOption('unicode2', 'Test string option',
+                               properties=('mandatory', ))
+    stroption3 = StrOption('str3', 'Test string option', multi=True, requires=[{'option': stroption, 'expected': 'yes', 'action': 'mandatory', 'transitive': False}])
+    descr = OptionDescription('tiram', '', [stroption, stroption1, stroption2, stroption3])
+    return descr
+
+
 def test_mandatory_ro():
     descr = make_description()
     config = Config(descr)
 def test_mandatory_ro():
     descr = make_description()
     config = Config(descr)
@@ -255,13 +309,12 @@ def test_mandatory_warnings_ro():
     except PropertiesOptionError as err:
         proc = err.proptype
     assert proc == ['mandatory']
     except PropertiesOptionError as err:
         proc = err.proptype
     assert proc == ['mandatory']
-    assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3']
     config.read_write()
     config.str = 'a'
     config.read_only()
     config.read_write()
     config.str = 'a'
     config.read_only()
-    assert config.cfgimpl_get_values().mandatory_warnings() == ['str1', 'unicode2', 'str3']
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str1', 'unicode2', 'str3']
     assert list(config.cfgimpl_get_values().mandatory_warnings(force_permissive=True)) == ['str1', 'unicode2', 'str3']
     assert list(config.cfgimpl_get_values().mandatory_warnings(force_permissive=True)) == ['str1', 'unicode2', 'str3']
-    sleep(.1)
 
 
 def test_mandatory_warnings_rw():
 
 
 def test_mandatory_warnings_rw():
@@ -270,11 +323,10 @@ def test_mandatory_warnings_rw():
     config.str = ''
     config.read_write()
     config.str
     config.str = ''
     config.read_write()
     config.str
-    assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3']
     config.str = 'a'
     config.str = 'a'
-    assert config.cfgimpl_get_values().mandatory_warnings() == ['str1', 'unicode2', 'str3']
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str1', 'unicode2', 'str3']
     assert list(config.cfgimpl_get_values().mandatory_warnings(force_permissive=True)) == ['str1', 'unicode2', 'str3']
     assert list(config.cfgimpl_get_values().mandatory_warnings(force_permissive=True)) == ['str1', 'unicode2', 'str3']
-    sleep(.1)
 
 
 def test_mandatory_warnings_disabled():
 
 
 def test_mandatory_warnings_disabled():
@@ -284,11 +336,10 @@ def test_mandatory_warnings_disabled():
     setting = config.cfgimpl_get_settings()
     config.read_write()
     config.str
     setting = config.cfgimpl_get_settings()
     config.read_write()
     config.str
-    assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3']
     setting[descr.str].append('disabled')
     setting[descr.str].append('disabled')
-    assert config.cfgimpl_get_values().mandatory_warnings() == ['str1', 'unicode2', 'str3']
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str1', 'unicode2', 'str3']
     assert list(config.cfgimpl_get_values().mandatory_warnings(force_permissive=True)) == ['str1', 'unicode2', 'str3']
     assert list(config.cfgimpl_get_values().mandatory_warnings(force_permissive=True)) == ['str1', 'unicode2', 'str3']
-    sleep(.1)
 
 
 def test_mandatory_warnings_hidden():
 
 
 def test_mandatory_warnings_hidden():
@@ -312,11 +363,10 @@ def test_mandatory_warnings_frozen():
     setting = config.cfgimpl_get_settings()
     config.read_write()
     config.str
     setting = config.cfgimpl_get_settings()
     config.read_write()
     config.str
-    assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3']
     setting[descr.str].append('frozen')
     config.read_only()
     setting[descr.str].append('frozen')
     config.read_only()
-    assert config.cfgimpl_get_values().mandatory_warnings() == ['str', 'str1', 'unicode2', 'str3']
-    sleep(.1)
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3']
 
 
 def test_mandatory_master():
 
 
 def test_mandatory_master():
@@ -401,3 +451,60 @@ def test_mandatory_slave():
     config.read_only()
     assert config.ip_admin_eth0.ip_admin_eth0 == ['ip']
     assert 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']
+
+
+def test_mandatory_warnings_symlink():
+    descr = make_description_sym()
+    config = Config(descr)
+    config.str = ''
+    setting = config.cfgimpl_get_settings()
+    config.read_write()
+    config.str
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3']
+    setting[descr.str].append('frozen')
+    config.read_only()
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2', 'str3']
+
+
+def test_mandatory_warnings_validate():
+    descr = make_description3()
+    config = Config(descr)
+    config.str = ''
+    raises(ValueError, "list(config.cfgimpl_get_values().mandatory_warnings())")
+    assert list(config.cfgimpl_get_values().mandatory_warnings(validate=False)) == ['str', 'str1', 'unicode2', 'str3', 'unicode1', 'int1']
+    config.str = 'test'
+    raises(ValueError, "list(config.cfgimpl_get_values().mandatory_warnings())")
+    assert list(config.cfgimpl_get_values().mandatory_warnings(validate=False)) == ['str1', 'unicode2', 'str3']
+
+
+def test_mandatory_warnings_validate_empty():
+    descr = make_description2()
+    config = Config(descr)
+    config.str = ''
+    config.read_only()
+    raises(ConfigError, "list(config.cfgimpl_get_values().mandatory_warnings())")
+    assert list(config.cfgimpl_get_values().mandatory_warnings(validate=False)) == ['str', 'str1', 'unicode2', 'str3', 'unicode1']
+
+
+def test_mandatory_warnings_requires():
+    descr = make_description4()
+    config = Config(descr)
+    config.str = ''
+    config.read_write()
+    config.str
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2']
+    config.read_only()
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str', 'str1', 'unicode2']
+    config.read_write()
+    config.str = 'yes'
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['str1', 'unicode2', 'str3']
+
+
+def test_mandatory_od_disabled():
+    descr = make_description()
+    od = OptionDescription('od', '', [descr])
+    config = Config(od)
+    config.read_only()
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == ['tiram.str1', 'tiram.unicode2', 'tiram.str3']
+    config.cfgimpl_get_settings()[descr].append('disabled')
+    assert list(config.cfgimpl_get_values().mandatory_warnings()) == []
index c711f02..ead89db 100644 (file)
@@ -241,7 +241,8 @@ class SubConfig(object):
             subpath = self._impl_path + '.' + name
         return subpath
 
             subpath = self._impl_path + '.' + name
         return subpath
 
-    def getattr(self, name, force_permissive=False, validate=True):
+    def getattr(self, name, force_permissive=False, validate=True,
+                _setting_properties=undefined):
         """
         attribute notation mechanism for accessing the value of an option
         :param name: attribute name
         """
         attribute notation mechanism for accessing the value of an option
         :param name: attribute name
@@ -254,7 +255,8 @@ class SubConfig(object):
             homeconfig, name = self.cfgimpl_get_home_by_path(
                 name, force_permissive=force_permissive)
             return homeconfig.getattr(name, force_permissive=force_permissive,
             homeconfig, name = self.cfgimpl_get_home_by_path(
                 name, force_permissive=force_permissive)
             return homeconfig.getattr(name, force_permissive=force_permissive,
-                                      validate=validate)
+                                      validate=validate,
+                                      _setting_properties=_setting_properties)
         context = self._cfgimpl_get_context()
         option = self.cfgimpl_get_description().__getattr__(name,
                                                             context=context)
         context = self._cfgimpl_get_context()
         option = self.cfgimpl_get_description().__getattr__(name,
                                                             context=context)
@@ -263,22 +265,26 @@ class SubConfig(object):
             return self.cfgimpl_get_values()._get_cached_item(
                 option, path=subpath,
                 validate=validate,
             return self.cfgimpl_get_values()._get_cached_item(
                 option, path=subpath,
                 validate=validate,
-                force_permissive=force_permissive)
+                force_permissive=force_permissive,
+                setting_properties=_setting_properties)
         elif isinstance(option, SymLinkOption):  # pragma: no dynoptiondescription cover
             path = context.cfgimpl_get_description().impl_get_path_by_opt(
                 option._impl_getopt())
             return context.getattr(path, validate=validate,
         elif isinstance(option, SymLinkOption):  # pragma: no dynoptiondescription cover
             path = context.cfgimpl_get_description().impl_get_path_by_opt(
                 option._impl_getopt())
             return context.getattr(path, validate=validate,
-                                   force_permissive=force_permissive)
+                                   force_permissive=force_permissive,
+                                   _setting_properties=_setting_properties)
         elif option.impl_is_optiondescription():
             self.cfgimpl_get_settings().validate_properties(
                 option, True, False, path=subpath,
         elif option.impl_is_optiondescription():
             self.cfgimpl_get_settings().validate_properties(
                 option, True, False, path=subpath,
-                force_permissive=force_permissive)
+                force_permissive=force_permissive,
+                setting_properties=_setting_properties)
             return SubConfig(option, self._impl_context, subpath)
         else:
             return self.cfgimpl_get_values()._get_cached_item(
                 option, path=subpath,
                 validate=validate,
             return SubConfig(option, self._impl_context, subpath)
         else:
             return self.cfgimpl_get_values()._get_cached_item(
                 option, path=subpath,
                 validate=validate,
-                force_permissive=force_permissive)
+                force_permissive=force_permissive,
+                setting_properties=_setting_properties)
 
     def find(self, bytype=None, byname=None, byvalue=undefined, type_='option',
              check_properties=True, force_permissive=False):
 
     def find(self, bytype=None, byname=None, byvalue=undefined, type_='option',
              check_properties=True, force_permissive=False):
index 3462f70..19a756a 100644 (file)
@@ -115,25 +115,25 @@ class MasterSlaves(object):
 
     def getitem(self, values, opt, path, validate, force_permissive,
                 force_properties, validate_properties, slave_path=undefined,
 
     def getitem(self, values, opt, path, validate, force_permissive,
                 force_properties, validate_properties, slave_path=undefined,
-                slave_value=undefined, setting_properties=undefined, settings=undefined):
+                slave_value=undefined, setting_properties=undefined, self_properties=undefined):
         if self.is_master(opt):
             return self._getmaster(values, opt, path, validate,
                                    force_permissive, force_properties,
                                    validate_properties, slave_path,
         if self.is_master(opt):
             return self._getmaster(values, opt, path, validate,
                                    force_permissive, force_properties,
                                    validate_properties, slave_path,
-                                   slave_value, settings)
+                                   slave_value, self_properties)
         else:
             return self._getslave(values, opt, path, validate,
                                   force_permissive, force_properties,
         else:
             return self._getslave(values, opt, path, validate,
                                   force_permissive, force_properties,
-                                  validate_properties, setting_properties, settings)
+                                  validate_properties, setting_properties, self_properties)
 
     def _getmaster(self, values, opt, path, validate, force_permissive,
                    force_properties, validate_properties, c_slave_path,
 
     def _getmaster(self, values, opt, path, validate, force_permissive,
                    force_properties, validate_properties, c_slave_path,
-                   c_slave_value, settings):
+                   c_slave_value, self_properties):
         value = values._get_validated_value(opt, path, validate,
                                             force_permissive,
                                             force_properties,
                                             validate_properties,
         value = values._get_validated_value(opt, path, validate,
                                             force_permissive,
                                             force_properties,
                                             validate_properties,
-                                            settings=settings)
+                                            self_properties=self_properties)
         if validate is True:
             masterlen = len(value)
             for slave in self.getslaves(opt):
         if validate is True:
             masterlen = len(value)
             for slave in self.getslaves(opt):
@@ -148,7 +148,7 @@ class MasterSlaves(object):
                                                                   False,
                                                                   None, False,
                                                                   None,
                                                                   False,
                                                                   None, False,
                                                                   None,
-                                                                  settings=settings)
+                                                                  self_properties=self_properties)
                     slavelen = len(slave_value)
                     self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
                 except ConfigError:  # pragma: optional cover
                     slavelen = len(slave_value)
                     self.validate_slave_length(masterlen, slavelen, slave.impl_getname(), opt)
                 except ConfigError:  # pragma: optional cover
@@ -157,7 +157,7 @@ class MasterSlaves(object):
 
     def _getslave(self, values, opt, path, validate, force_permissive,
                   force_properties, validate_properties, setting_properties,
 
     def _getslave(self, values, opt, path, validate, force_permissive,
                   force_properties, validate_properties, setting_properties,
-                  settings):
+                  self_properties):
         """
         if master has length 0:
             return []
         """
         if master has length 0:
             return []
@@ -192,7 +192,7 @@ class MasterSlaves(object):
                                             validate_properties,
                                             None,  # not undefined
                                             with_meta=master_is_meta,
                                             validate_properties,
                                             None,  # not undefined
                                             with_meta=master_is_meta,
-                                            settings=settings)
+                                            self_properties=self_properties)
         #if slave, had values until master's one
         path = opt.impl_getpath(context)
         valuelen = len(value)
         #if slave, had values until master's one
         path = opt.impl_getpath(context)
         valuelen = len(value)
@@ -207,7 +207,7 @@ class MasterSlaves(object):
                                                          validate_properties=False,
                                                          with_meta=master_is_meta,
                                                          index=index,
                                                          validate_properties=False,
                                                          with_meta=master_is_meta,
                                                          index=index,
-                                                         settings=settings),
+                                                         self_properties=self_properties),
                              setitem=False,
                              force=True,
                              validate=validate)
                              setitem=False,
                              force=True,
                              validate=validate)
@@ -218,7 +218,7 @@ class MasterSlaves(object):
                                                                    path=path,
                                                                    force_permissive=force_permissive,
                                                                    force_properties=force_properties,
                                                                    path=path,
                                                                    force_permissive=force_permissive,
                                                                    force_properties=force_properties,
-                                                                   self_properties=setting_properties)
+                                                                   setting_properties=setting_properties)
         return value
 
     def setitem(self, values, opt, value, path):
         return value
 
     def setitem(self, values, opt, value, path):
index ff1288e..f6e331c 100644 (file)
@@ -330,19 +330,19 @@ class Settings(object):
     # properties methods
     def __contains__(self, propname):
         "enables the pythonic 'in' syntaxic sugar"
     # properties methods
     def __contains__(self, propname):
         "enables the pythonic 'in' syntaxic sugar"
-        return propname in self._getproperties()
+        return propname in self._getproperties(read_write=False)
 
     def __repr__(self):
 
     def __repr__(self):
-        return str(list(self._getproperties()))
+        return str(list(self._getproperties(read_write=False)))
 
     def __getitem__(self, opt):
         path = opt.impl_getpath(self._getcontext())
         return self._getitem(opt, path)
 
 
     def __getitem__(self, opt):
         path = opt.impl_getpath(self._getcontext())
         return self._getitem(opt, path)
 
-    def _getitem(self, opt, path, self_properties=undefined):
+    def _getitem(self, opt, path, setting_properties=undefined):
         return Property(self,
                         self._getproperties(opt, path,
         return Property(self,
                         self._getproperties(opt, path,
-                                            self_properties=self_properties),
+                                            setting_properties=setting_properties),
                         opt, path)
 
     def __setitem__(self, opt, value):  # pragma: optional cover
                         opt, path)
 
     def __setitem__(self, opt, value):  # pragma: optional cover
@@ -361,41 +361,45 @@ class Settings(object):
         self._getcontext().cfgimpl_reset_cache()
 
     def _getproperties(self, opt=None, path=None,
         self._getcontext().cfgimpl_reset_cache()
 
     def _getproperties(self, opt=None, path=None,
-                       self_properties=undefined, read_write=True):
+                       setting_properties=undefined, read_write=True,
+                       apply_requires=True):
         """
         """
         if opt is None:
             props = self._p_.getproperties(path, default_properties)
         else:
         """
         """
         if opt is None:
             props = self._p_.getproperties(path, default_properties)
         else:
-            if self_properties is undefined:
-                self_properties = self._getproperties()
+            if setting_properties is undefined:
+                setting_properties = self._getproperties(read_write=False)
             if path is None:  # pragma: optional cover
                 raise ValueError(_('if opt is not None, path should not be'
                                    ' None in _getproperties'))
             is_cached = False
             if path is None:  # pragma: optional cover
                 raise ValueError(_('if opt is not None, path should not be'
                                    ' None in _getproperties'))
             is_cached = False
-            if 'cache' in self_properties and 'expire' in self_properties:
-                ntime = int(time())
-            else:
-                ntime = None
-            if 'cache' in self_properties and self._p_.hascache(path):
-                is_cached, props = self._p_.getcache(path, ntime)
+            if apply_requires:
+                if 'cache' in setting_properties and 'expire' in setting_properties:
+                    ntime = int(time())
+                else:
+                    ntime = None
+                if 'cache' in setting_properties and self._p_.hascache(path):
+                    is_cached, props = self._p_.getcache(path, ntime)
             if not is_cached:
             if not is_cached:
-                props = copy(self._p_.getproperties(path, opt.impl_getproperties()))
-                props |= self.apply_requires(opt, path)
-                if 'cache' in self_properties:
-                    if 'expire' in self_properties:
-                        ntime = ntime + expires_time
-                    self._p_.setcache(path, props, ntime)
+                props = self._p_.getproperties(path, opt.impl_getproperties())
+                if apply_requires:
+                    props = copy(props)
+                    props |= self.apply_requires(opt, path, setting_properties)
+                    if 'cache' in setting_properties:
+                        if 'expire' in setting_properties:
+                            ntime = ntime + expires_time
+                        self._p_.setcache(path, props, ntime)
         if read_write:
         if read_write:
-            return copy(props)
-        else:
-            return props
+            props = copy(props)
+        return props
 
     def append(self, propname):
         "puts property propname in the Config's properties attribute"
         props = self._p_.getproperties(None, default_properties)
 
     def append(self, propname):
         "puts property propname in the Config's properties attribute"
         props = self._p_.getproperties(None, default_properties)
-        props.add(propname)
-        self._setproperties(props, None)
+        if propname not in props:
+            props.add(propname)
+            self._setproperties(props, None)
 
     def remove(self, propname):
         "deletes property propname in the Config's properties attribute"
 
     def remove(self, propname):
         "deletes property propname in the Config's properties attribute"
@@ -422,7 +426,8 @@ class Settings(object):
     #____________________________________________________________
     def validate_properties(self, opt_or_descr, is_descr, is_write, path,
                             value=None, force_permissive=False,
     #____________________________________________________________
     def validate_properties(self, opt_or_descr, is_descr, is_write, path,
                             value=None, force_permissive=False,
-                            force_properties=None, force_permissives=None,
+                            force_properties=None,
+                            setting_properties=undefined,
                             self_properties=undefined):
         """
         validation upon the properties related to `opt_or_descr`
                             self_properties=undefined):
         """
         validation upon the properties related to `opt_or_descr`
@@ -432,8 +437,6 @@ class Settings(object):
                                  was present
         :param force_properties: set() with properties that is force to add
                                  in global properties
                                  was present
         :param force_properties: set() with properties that is force to add
                                  in global properties
-        :param force_permissives: set() with permissives that is force to add
-                                 in global permissives
         :param is_descr: we have to know if we are in an option description,
                          just because the mandatory property
                          doesn't exist here
         :param is_descr: we have to know if we are in an option description,
                          just because the mandatory property
                          doesn't exist here
@@ -443,25 +446,26 @@ class Settings(object):
                          (typically with the `frozen` property)
         """
         # opt properties
                          (typically with the `frozen` property)
         """
         # opt properties
-        if self_properties is undefined:
-            self_properties = self._getproperties(read_write=False)
-        properties = self._getproperties(opt_or_descr, path,
-                                         self_properties=self_properties)
+        if setting_properties is undefined:
+            setting_properties = self._getproperties(read_write=False)
+        if self_properties is not undefined:
+            properties = copy(self_properties)
+        else:
+            properties = self._getproperties(opt_or_descr, path,
+                                             setting_properties=setting_properties)
         # remove opt permissive
         # permissive affect option's permission with or without permissive
         # global property
         properties -= self._p_.getpermissive(path)
         # remove global permissive if need
         # remove opt permissive
         # permissive affect option's permission with or without permissive
         # global property
         properties -= self._p_.getpermissive(path)
         # remove global permissive if need
-        if force_permissive is True or 'permissive' in self_properties:
+        if force_permissive is True or 'permissive' in setting_properties:
             properties -= self._p_.getpermissive()
             properties -= self._p_.getpermissive()
-        if force_permissives is not None:
-            properties -= force_permissives
 
         if force_properties is not None:
 
         if force_properties is not None:
-            forced_properties = copy(self_properties)
+            forced_properties = copy(setting_properties)
             forced_properties.update(force_properties)
         else:
             forced_properties.update(force_properties)
         else:
-            forced_properties = self_properties
+            forced_properties = setting_properties
 
         # calc properties
         properties &= forced_properties
 
         # calc properties
         properties &= forced_properties
@@ -531,10 +535,16 @@ class Settings(object):
 
     #____________________________________________________________
     def _read(self, remove, append):
 
     #____________________________________________________________
     def _read(self, remove, append):
-        for prop in remove:
-            self.remove(prop)
-        for prop in append:
-            self.append(prop)
+        props = self._p_.getproperties(None, default_properties)
+        modified = False
+        if remove & props != set([]):
+            props = props - remove
+            modified = True
+        if append & props != append:
+            props = props | append
+            modified = True
+        if modified:
+            self._setproperties(props, None)
 
     def read_only(self):
         "convenience method to freeze, hide and disable"
 
     def read_only(self):
         "convenience method to freeze, hide and disable"
@@ -555,7 +565,7 @@ class Settings(object):
         else:
             self._p_.reset_all_cache()
 
         else:
             self._p_.reset_all_cache()
 
-    def apply_requires(self, opt, path):
+    def apply_requires(self, opt, path, setting_properties):
         """carries out the jit (just in time) requirements between options
 
         a requirement is a tuple of this form that comes from the option's
         """carries out the jit (just in time) requirements between options
 
         a requirement is a tuple of this form that comes from the option's
@@ -616,7 +626,8 @@ class Settings(object):
                                              " '{0}' with requirement on: "
                                              "'{1}'").format(path, reqpath))
                 try:
                                              " '{0}' with requirement on: "
                                              "'{1}'").format(path, reqpath))
                 try:
-                    value = context.getattr(reqpath, force_permissive=True)
+                    value = context.getattr(reqpath, force_permissive=True,
+                                            _setting_properties=setting_properties)
                 except PropertiesOptionError as err:
                     if not transitive:
                         continue
                 except PropertiesOptionError as err:
                     if not transitive:
                         continue
index c5f45e5..18b3afa 100644 (file)
@@ -55,7 +55,7 @@ class Values(object):
         return context
 
     def _getvalue(self, opt, path, is_default, index=undefined,
         return context
 
     def _getvalue(self, opt, path, is_default, index=undefined,
-                  with_meta=True, settings=undefined):
+                  with_meta=True, self_properties=undefined):
         """actually retrieves the value
 
         :param opt: the `option.Option()` object
         """actually retrieves the value
 
         :param opt: the `option.Option()` object
@@ -64,11 +64,11 @@ class Values(object):
         if opt.impl_is_optiondescription():  # pragma: optional cover
             raise ValueError(_('optiondescription has no value'))
 
         if opt.impl_is_optiondescription():  # pragma: optional cover
             raise ValueError(_('optiondescription has no value'))
 
-        if settings is undefined:
-            settings = self._getcontext().cfgimpl_get_settings()._getproperties(
+        if self_properties is undefined:
+            self_properties = self._getcontext().cfgimpl_get_settings()._getproperties(
                 opt, path, read_write=False)
                 opt, path, read_write=False)
-        force_default = 'frozen' in settings and \
-            'force_default_on_freeze' in settings
+        force_default = 'frozen' in self_properties and \
+            'force_default_on_freeze' in self_properties
         if not is_default and not force_default:
             value = self._p_.getvalue(path)
             if index is not undefined:
         if not is_default and not force_default:
             value = self._p_.getvalue(path)
             if index is not undefined:
@@ -210,27 +210,37 @@ class Values(object):
     def _get_cached_item(self, opt, path=None, validate=True,
                          force_permissive=False, force_properties=None,
                          validate_properties=True,
     def _get_cached_item(self, opt, path=None, validate=True,
                          force_permissive=False, force_properties=None,
                          validate_properties=True,
-                         setting_properties=undefined):
+                         setting_properties=undefined, self_properties=undefined):
+        untrusted_cached_properties = force_properties is None
+        context = self._getcontext()
         if path is None:
         if path is None:
-            path = opt.impl_getpath(self._getcontext())
+            path = opt.impl_getpath(context)
         ntime = None
         if setting_properties is undefined:
         ntime = None
         if setting_properties is undefined:
-            setting_properties = self._getcontext().cfgimpl_get_settings(
+            setting_properties = context.cfgimpl_get_settings(
             )._getproperties(read_write=False)
             )._getproperties(read_write=False)
-        settings = self._getcontext().cfgimpl_get_settings()._getproperties(
-            opt, path, read_write=False, self_properties=setting_properties)
+        if self_properties is undefined:
+            self_properties = context.cfgimpl_get_settings()._getproperties(
+                opt, path, read_write=False, setting_properties=setting_properties)
         if 'cache' in setting_properties and self._p_.hascache(path):
             if 'expire' in setting_properties:
                 ntime = int(time())
             is_cached, value = self._p_.getcache(path, ntime)
             if is_cached:
                 if opt.impl_is_multi() and not isinstance(value, Multi):
         if 'cache' in setting_properties and self._p_.hascache(path):
             if 'expire' in setting_properties:
                 ntime = int(time())
             is_cached, value = self._p_.getcache(path, ntime)
             if is_cached:
                 if opt.impl_is_multi() and not isinstance(value, Multi):
-                    #load value so don't need to validate if is not a Multi
                     value = Multi(value, self.context, opt, path)
                     value = Multi(value, self.context, opt, path)
+                if not untrusted_cached_properties:
+                    # revalidate properties (because not default properties)
+                    context.cfgimpl_get_settings().validate_properties(opt, False, False, value=value,
+                                                                       path=path,
+                                                                       force_permissive=force_permissive,
+                                                                       force_properties=force_properties,
+                                                                       setting_properties=setting_properties,
+                                                                       self_properties=self_properties)
                 return value
         val = self._getitem(opt, path, validate, force_permissive,
                             force_properties, validate_properties,
                 return value
         val = self._getitem(opt, path, validate, force_permissive,
                             force_properties, validate_properties,
-                            setting_properties, settings=settings)
+                            setting_properties, self_properties=self_properties)
         if 'cache' in setting_properties and validate and validate_properties \
                 and force_permissive is False and force_properties is None:
             if 'expire' in setting_properties:
         if 'cache' in setting_properties and validate and validate_properties \
                 and force_permissive is False and force_properties is None:
             if 'expire' in setting_properties:
@@ -242,7 +252,7 @@ class Values(object):
 
     def _getitem(self, opt, path, validate, force_permissive, force_properties,
                  validate_properties, setting_properties=undefined,
 
     def _getitem(self, opt, path, validate, force_permissive, force_properties,
                  validate_properties, setting_properties=undefined,
-                 settings=undefined):
+                 self_properties=undefined):
         if opt.impl_is_master_slaves():
             return opt.impl_get_master_slaves().getitem(self, opt, path,
                                                         validate,
         if opt.impl_is_master_slaves():
             return opt.impl_get_master_slaves().getitem(self, opt, path,
                                                         validate,
@@ -250,20 +260,20 @@ class Values(object):
                                                         force_properties,
                                                         validate_properties,
                                                         setting_properties=setting_properties,
                                                         force_properties,
                                                         validate_properties,
                                                         setting_properties=setting_properties,
-                                                        settings=settings)
+                                                        self_properties=self_properties)
         else:
             return self._get_validated_value(opt, path, validate,
                                              force_permissive,
                                              force_properties,
                                              validate_properties,
                                              setting_properties=setting_properties,
         else:
             return self._get_validated_value(opt, path, validate,
                                              force_permissive,
                                              force_properties,
                                              validate_properties,
                                              setting_properties=setting_properties,
-                                             settings=settings)
+                                             self_properties=self_properties)
 
     def _get_validated_value(self, opt, path, validate, force_permissive,
                              force_properties, validate_properties,
                              index=undefined, submulti_index=undefined,
                              with_meta=True, setting_properties=undefined,
 
     def _get_validated_value(self, opt, path, validate, force_permissive,
                              force_properties, validate_properties,
                              index=undefined, submulti_index=undefined,
                              with_meta=True, setting_properties=undefined,
-                             settings=undefined):
+                             self_properties=undefined):
         """same has getitem but don't touch the cache
         index is None for slave value, if value returned is not a list, just return []
         """
         """same has getitem but don't touch the cache
         index is None for slave value, if value returned is not a list, just return []
         """
@@ -271,12 +281,12 @@ class Values(object):
         setting = context.cfgimpl_get_settings()
         if setting_properties is undefined:
             setting_properties = setting._getproperties(read_write=False)
         setting = context.cfgimpl_get_settings()
         if setting_properties is undefined:
             setting_properties = setting._getproperties(read_write=False)
-        if settings is undefined:
-            settings = setting._getproperties(opt, path, read_write=False)
+        if self_properties is undefined:
+            self_properties = setting._getproperties(opt, path, read_write=False)
         is_default = self._is_default_owner(opt, path,
                                             validate_properties=False,
                                             validate_meta=False,
         is_default = self._is_default_owner(opt, path,
                                             validate_properties=False,
                                             validate_meta=False,
-                                            settings=settings)
+                                            self_properties=self_properties)
         try:
             if index is None:
                 gv_index = undefined
         try:
             if index is None:
                 gv_index = undefined
@@ -284,7 +294,7 @@ class Values(object):
                 gv_index = index
             value = self._getvalue(opt, path, is_default, index=gv_index,
                                    with_meta=with_meta,
                 gv_index = index
             value = self._getvalue(opt, path, is_default, index=gv_index,
                                    with_meta=with_meta,
-                                   settings=settings)
+                                   self_properties=self_properties)
             config_error = None
         except ConfigError as err:
             # For calculating properties, we need value (ie for mandatory
             config_error = None
         except ConfigError as err:
             # For calculating properties, we need value (ie for mandatory
@@ -329,7 +339,7 @@ class Values(object):
                     config_error = err
                     value = None
 
                     config_error = err
                     value = None
 
-            if is_default and 'force_store_value' in settings:
+            if is_default and 'force_store_value' in self_properties:
                 if isinstance(value, Multi):
                     item = list(value)
                 else:
                 if isinstance(value, Multi):
                     item = list(value)
                 else:
@@ -337,11 +347,18 @@ class Values(object):
                 self.setitem(opt, item, path, is_write=False,
                              force_permissive=force_permissive)
         if validate_properties:
                 self.setitem(opt, item, path, is_write=False,
                              force_permissive=force_permissive)
         if validate_properties:
-            setting.validate_properties(opt, False, False, value=value,
+            if config_error is not None:
+                # should not raise PropertiesOptionError if option is
+                # mandatory
+                val_props = undefined
+            else:
+                val_props = value
+            setting.validate_properties(opt, False, False, value=val_props,
                                         path=path,
                                         force_permissive=force_permissive,
                                         force_properties=force_properties,
                                         path=path,
                                         force_permissive=force_permissive,
                                         force_properties=force_properties,
-                                        self_properties=setting_properties)
+                                        setting_properties=setting_properties,
+                                        self_properties=self_properties)
         if config_error is not None:
             raise config_error
         return value
         if config_error is not None:
             raise config_error
         return value
@@ -385,7 +402,7 @@ class Values(object):
             setting.validate_properties(opt, False, is_write,
                                         value=value, path=path,
                                         force_permissive=force_permissive,
             setting.validate_properties(opt, False, is_write,
                                         value=value, path=path,
                                         force_permissive=force_permissive,
-                                        self_properties=setting_properties)
+                                        setting_properties=setting_properties)
         if isinstance(value, Multi):
             value = list(value)
             if opt.impl_is_submulti():
         if isinstance(value, Multi):
             value = list(value)
             if opt.impl_is_submulti():
@@ -398,8 +415,8 @@ class Values(object):
     def _is_meta(self, opt, path):
         context = self._getcontext()
         setting = context.cfgimpl_get_settings()
     def _is_meta(self, opt, path):
         context = self._getcontext()
         setting = context.cfgimpl_get_settings()
-        settings = setting._getproperties(opt, path, read_write=False)
-        if 'frozen' in settings and 'force_default_on_freeze' in settings:
+        self_properties = setting._getproperties(opt, path, read_write=False)
+        if 'frozen' in self_properties and 'force_default_on_freeze' in self_properties:
             return False
         if self._p_.getowner(path, owners.default) is not owners.default:
             return False
             return False
         if self._p_.getowner(path, owners.default) is not owners.default:
             return False
@@ -424,21 +441,21 @@ class Values(object):
 
     def _getowner(self, opt, path, validate_properties=True,
                   force_permissive=False, validate_meta=undefined,
 
     def _getowner(self, opt, path, validate_properties=True,
                   force_permissive=False, validate_meta=undefined,
-                  settings=undefined):
+                  self_properties=undefined):
         """get owner of an option
         """
         if not isinstance(opt, Option) and not isinstance(opt,
                                                           DynSymLinkOption):
             raise ConfigError(_('owner only avalaible for an option'))
         context = self._getcontext()
         """get owner of an option
         """
         if not isinstance(opt, Option) and not isinstance(opt,
                                                           DynSymLinkOption):
             raise ConfigError(_('owner only avalaible for an option'))
         context = self._getcontext()
-        if settings is undefined:
-            settings = context.cfgimpl_get_settings()._getproperties(
+        if self_properties is undefined:
+            self_properties = context.cfgimpl_get_settings()._getproperties(
                 opt, path, read_write=False)
                 opt, path, read_write=False)
-        if 'frozen' in settings and 'force_default_on_freeze' in settings:
+        if 'frozen' in self_properties and 'force_default_on_freeze' in self_properties:
             return owners.default
         if validate_properties:
             self._getitem(opt, path, True, force_permissive, None, True,
             return owners.default
         if validate_properties:
             self._getitem(opt, path, True, force_permissive, None, True,
-                          settings=settings)
+                          self_properties=self_properties)
         owner = self._p_.getowner(path, owners.default)
         if validate_meta is undefined:
             if opt.impl_is_master_slaves('slave'):
         owner = self._p_.getowner(path, owners.default)
         if validate_meta is undefined:
             if opt.impl_is_master_slaves('slave'):
@@ -490,10 +507,10 @@ class Values(object):
                                       validate_meta=validate_meta)
 
     def _is_default_owner(self, opt, path, validate_properties=True,
                                       validate_meta=validate_meta)
 
     def _is_default_owner(self, opt, path, validate_properties=True,
-                          validate_meta=True, settings=undefined):
+                          validate_meta=True, self_properties=undefined):
         return self._getowner(opt, path, validate_properties,
                               validate_meta=validate_meta,
         return self._getowner(opt, path, validate_properties,
                               validate_meta=validate_meta,
-                              settings=settings) == \
+                              self_properties=self_properties) == \
             owners.default
 
     def reset_cache(self, only_expired):
             owners.default
 
     def reset_cache(self, only_expired):
@@ -528,41 +545,72 @@ class Values(object):
                 raise ValueError(_("information's item"
                                    " not found: {0}").format(key))
 
                 raise ValueError(_("information's item"
                                    " not found: {0}").format(key))
 
-    def mandatory_warnings(self, force_permissive=False):
+    def mandatory_warnings(self, force_permissive=False, validate=True):
         """convenience function to trace Options that are mandatory and
         where no value has been set
 
         """convenience function to trace Options that are mandatory and
         where no value has been set
 
-        :returns: generator of mandatory Option's path
+        :param force_permissive: do raise with permissives properties
+        :type force_permissive: `bool`
+        :param validate: validate value when calculating properties
+        :type validate: `bool`
 
 
+        :returns: generator of mandatory Option's path
         """
         """
-        def _mandatory_warnings(description):
-            #if value in cache, properties are not calculated
-            _ret = []
-            context = self._getcontext()
-            setting_properties = context.cfgimpl_get_settings()._getproperties(
-                read_write=False)
+        context = self._getcontext()
+        settings = context.cfgimpl_get_settings()
+        setting_properties = context.cfgimpl_get_settings()._getproperties(
+            read_write=False)
+
+        def _mandatory_warnings(description, currpath=None):
+            if currpath is None:
+                currpath = []
             for opt in description._impl_getchildren(context=context):
             for opt in description._impl_getchildren(context=context):
+                name = opt.impl_getname()
+                path = '.'.join(currpath + [name])
+
                 if opt.impl_is_optiondescription():
                 if opt.impl_is_optiondescription():
-                    _ret.extend(_mandatory_warnings(opt))
-                elif isinstance(opt, SymLinkOption) and \
-                        not isinstance(opt, DynSymLinkOption):
-                    pass
-                else:
-                    path = opt.impl_getpath(self._getcontext())
                     try:
                     try:
-                        self._get_cached_item(opt, path=path,
-                                              force_properties=frozenset(('mandatory',)),
-                                              force_permissive=force_permissive,
-                                              setting_properties=setting_properties)
+                        settings.validate_properties(opt, True, False, path=path,
+                                                     force_permissive=force_permissive,
+                                                     setting_properties=setting_properties)
                     except PropertiesOptionError as err:
                     except PropertiesOptionError as err:
-                        if err.proptype == ['mandatory']:
-                            _ret.append(path)
-            return _ret
-        self.reset_cache(False)
+                        pass
+                    else:
+                        for path in _mandatory_warnings(opt, currpath + [name]):
+                            yield path
+                else:
+                    if isinstance(opt, SymLinkOption) and \
+                            not isinstance(opt, DynSymLinkOption):
+                        true_opt = opt._impl_getopt()
+                        true_path = descr.impl_get_path_by_opt(true_opt)
+                    else:
+                        true_opt = opt
+                        true_path = path
+                    #FIXME attention c'est réutilisé donc jamais complet ??
+                    self_properties = settings._getproperties(true_opt, true_path,
+                                                              read_write=False,
+                                                              setting_properties=setting_properties)
+                    if 'mandatory' in self_properties:
+                        try:
+                            self._get_cached_item(true_opt, path=true_path,
+                                                  force_properties=frozenset(('mandatory',)),
+                                                  force_permissive=force_permissive,
+                                                  setting_properties=setting_properties,
+                                                  self_properties=self_properties,
+                                                  validate=validate)
+                        except PropertiesOptionError as err:
+                            if err.proptype == ['mandatory']:
+                                yield path
+                        except ConfigError as err:
+                            if validate:
+                                raise err
+                            else:
+                                #assume that uncalculated value is an empty value
+                                yield path
+
         descr = self._getcontext().cfgimpl_get_description()
         descr = self._getcontext().cfgimpl_get_description()
-        ret = _mandatory_warnings(descr)
-        self.reset_cache(False)
-        return ret
+        for path in _mandatory_warnings(descr):
+            yield path
 
     def force_cache(self):
         """parse all option to force data in cache
 
     def force_cache(self):
         """parse all option to force data in cache
@@ -660,7 +708,7 @@ class Multi(list):
             self._validate(value, fake_context, index, True)
         #assume not checking mandatory property
         super(Multi, self).__setitem__(index, value)
             self._validate(value, fake_context, index, True)
         #assume not checking mandatory property
         super(Multi, self).__setitem__(index, value)
-        context.cfgimpl_get_values()._setvalue(self.opt, self.path, self)
+        context.cfgimpl_get_values()._setvalue(self.opt, self.path, self, setting_properties=setting_properties)
 
     #def __repr__(self, *args, **kwargs):
     #    return super(Multi, self).__repr__(*args, **kwargs)
 
     #def __repr__(self, *args, **kwargs):
     #    return super(Multi, self).__repr__(*args, **kwargs)