From a0de1109f7f51ffc0ff37e4e7a50cf856c05a153 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Thu, 29 Oct 2015 09:03:13 +0100 Subject: [PATCH] optimise mandatory_warnings --- test/test_cache.py | 2 +- test/test_dyn_optiondescription.py | 4 +- test/test_mandatory.py | 137 +++++++++++++++++++++--- tiramisu/config.py | 18 ++-- tiramisu/option/masterslave.py | 20 ++-- tiramisu/setting.py | 95 +++++++++-------- tiramisu/value.py | 166 +++++++++++++++++++---------- 7 files changed, 307 insertions(+), 135 deletions(-) diff --git a/test/test_cache.py b/test/test_cache.py index 2eb2268..7a6f6dc 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -6,7 +6,7 @@ from tiramisu import setting 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 diff --git a/test/test_dyn_optiondescription.py b/test/test_dyn_optiondescription.py index 89da108..9dab50b 100644 --- a/test/test_dyn_optiondescription.py +++ b/test/test_dyn_optiondescription.py @@ -284,7 +284,7 @@ def test_mandatory_dyndescription(): 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(): @@ -467,7 +467,7 @@ def test_mandatory_dyndescription_context(): 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(): diff --git a/test/test_mandatory.py b/test/test_mandatory.py index 2c13c1e..6669d1e 100644 --- a/test/test_mandatory.py +++ b/test/test_mandatory.py @@ -1,12 +1,11 @@ # 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 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 @@ -25,6 +24,61 @@ def make_description(): 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) @@ -255,13 +309,12 @@ def test_mandatory_warnings_ro(): 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() - 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'] - sleep(.1) def test_mandatory_warnings_rw(): @@ -270,11 +323,10 @@ def test_mandatory_warnings_rw(): 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' - 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'] - sleep(.1) def test_mandatory_warnings_disabled(): @@ -284,11 +336,10 @@ def test_mandatory_warnings_disabled(): 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') - 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'] - sleep(.1) def test_mandatory_warnings_hidden(): @@ -312,11 +363,10 @@ def test_mandatory_warnings_frozen(): 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() - 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(): @@ -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'] + + +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()) == [] diff --git a/tiramisu/config.py b/tiramisu/config.py index c711f02..ead89db 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -241,7 +241,8 @@ class SubConfig(object): 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 @@ -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, - validate=validate) + validate=validate, + _setting_properties=_setting_properties) 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, - 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, - 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, - 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, - 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): diff --git a/tiramisu/option/masterslave.py b/tiramisu/option/masterslave.py index 3462f70..19a756a 100644 --- a/tiramisu/option/masterslave.py +++ b/tiramisu/option/masterslave.py @@ -115,25 +115,25 @@ class MasterSlaves(object): 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, - slave_value, settings) + slave_value, self_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, - c_slave_value, settings): + c_slave_value, self_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): @@ -148,7 +148,7 @@ class MasterSlaves(object): 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 @@ -157,7 +157,7 @@ class MasterSlaves(object): def _getslave(self, values, opt, path, validate, force_permissive, force_properties, validate_properties, setting_properties, - settings): + self_properties): """ if master has length 0: return [] @@ -192,7 +192,7 @@ class MasterSlaves(object): 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) @@ -207,7 +207,7 @@ class MasterSlaves(object): validate_properties=False, with_meta=master_is_meta, index=index, - settings=settings), + self_properties=self_properties), setitem=False, force=True, validate=validate) @@ -218,7 +218,7 @@ class MasterSlaves(object): 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): diff --git a/tiramisu/setting.py b/tiramisu/setting.py index ff1288e..f6e331c 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -330,19 +330,19 @@ class Settings(object): # 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): - 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, self_properties=undefined): + def _getitem(self, opt, path, setting_properties=undefined): 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 @@ -361,41 +361,45 @@ class Settings(object): 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 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 '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: - 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: - 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) - 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" @@ -422,7 +426,8 @@ class Settings(object): #____________________________________________________________ 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` @@ -432,8 +437,6 @@ class Settings(object): 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 @@ -443,25 +446,26 @@ class Settings(object): (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 - if force_permissive is True or 'permissive' in self_properties: + if force_permissive is True or 'permissive' in setting_properties: properties -= self._p_.getpermissive() - if force_permissives is not None: - properties -= force_permissives if force_properties is not None: - forced_properties = copy(self_properties) + forced_properties = copy(setting_properties) forced_properties.update(force_properties) else: - forced_properties = self_properties + forced_properties = setting_properties # calc properties properties &= forced_properties @@ -531,10 +535,16 @@ class Settings(object): #____________________________________________________________ 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" @@ -555,7 +565,7 @@ class Settings(object): 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 @@ -616,7 +626,8 @@ class Settings(object): " '{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 diff --git a/tiramisu/value.py b/tiramisu/value.py index c5f45e5..18b3afa 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -55,7 +55,7 @@ class Values(object): 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 @@ -64,11 +64,11 @@ class Values(object): 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) - 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: @@ -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, - setting_properties=undefined): + setting_properties=undefined, self_properties=undefined): + untrusted_cached_properties = force_properties is None + context = self._getcontext() if path is None: - path = opt.impl_getpath(self._getcontext()) + path = opt.impl_getpath(context) ntime = None if setting_properties is undefined: - setting_properties = self._getcontext().cfgimpl_get_settings( + setting_properties = context.cfgimpl_get_settings( )._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): - #load value so don't need to validate if is not a Multi 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, - 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: @@ -242,7 +252,7 @@ class Values(object): 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, @@ -250,20 +260,20 @@ class Values(object): 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, - 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, - 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 [] """ @@ -271,12 +281,12 @@ class Values(object): 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, - settings=settings) + self_properties=self_properties) 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, - settings=settings) + self_properties=self_properties) 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 - 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: @@ -337,11 +347,18 @@ class Values(object): 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, - self_properties=setting_properties) + setting_properties=setting_properties, + self_properties=self_properties) 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, - self_properties=setting_properties) + setting_properties=setting_properties) 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() - 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 @@ -424,21 +441,21 @@ class Values(object): 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() - 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) - 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, - 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'): @@ -490,10 +507,10 @@ class Values(object): 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, - settings=settings) == \ + self_properties=self_properties) == \ 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)) - 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 - :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): + name = opt.impl_getname() + path = '.'.join(currpath + [name]) + 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: - 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: - 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() - 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 @@ -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) - 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)