diff --git a/test/test_freeze.py b/test/test_freeze.py index d4f6d65..81127e4 100644 --- a/test/test_freeze.py +++ b/test/test_freeze.py @@ -20,7 +20,7 @@ def make_description_freeze(): floatoption = FloatOption('float', 'Test float option', default=2.3) stroption = StrOption('str', 'Test string option', default="abc") boolop = BoolOption('boolop', 'Test boolean option op', default=[True], multi=True) - wantref_option = BoolOption('wantref', 'Test requires', default=False, + wantref_option = BoolOption('wantref', 'Test requires', default=False, properties=('force_store_value',), requires=(('boolop', True, 'hidden'),)) wantframework_option = BoolOption('wantframework', 'Test requires', default=False, @@ -136,3 +136,12 @@ def test_freeze_get_multi(): except PropertiesOptionError, err: prop = err.proptype assert 'frozen' in prop + + +def test_force_store_value(): + descr = make_description_freeze() + conf = Config(descr) + opt = conf.unwrap_from_path('wantref') + assert conf.cfgimpl_get_values().getowner(opt) == 'default' + conf.wantref + assert conf.cfgimpl_get_values().getowner(opt) == 'user' diff --git a/test/test_option_setting.py b/test/test_option_setting.py index aa5e674..8bae976 100644 --- a/test/test_option_setting.py +++ b/test/test_option_setting.py @@ -57,7 +57,7 @@ def test_reset(): config.string = "foo" assert config.string == "foo" assert config.cfgimpl_get_values().getowner(s) == owners.user - config.unwrap_from_path("string").reset(config) + del(config.string) assert config.string == 'string' assert config.cfgimpl_get_values().getowner(s) == owners.default @@ -67,13 +67,13 @@ def test_reset_with_multi(): descr = OptionDescription("options", "", [s]) config = Config(descr) # config.string = [] - config.unwrap_from_path("string").reset(config) + del(config.string) assert config.string == ["string"] assert config.cfgimpl_get_values().getowner(s) == 'default' config.string = ["eggs", "spam", "foo"] assert config.cfgimpl_get_values().getowner(s) == 'user' config.string = [] - config.unwrap_from_path("string").reset(config) + del(config.string) # assert config.string == ["string"] assert config.cfgimpl_get_values().getowner(s) == 'default' raises(ValueError, "config.string = None") diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index f3ea34f..5ddd2f3 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -1,23 +1,25 @@ # coding: utf-8 import autopath -from tiramisu.config import * -from tiramisu.option import * from tiramisu.setting import groups, owners +from tiramisu.config import Config +from tiramisu.option import ChoiceOption, BoolOption, IntOption, \ + StrOption, OptionDescription from py.test import raises + def make_description(): numero_etab = StrOption('numero_etab', "identifiant de l'établissement") nom_machine = StrOption('nom_machine', "nom de la machine", default="eoleng") nombre_interfaces = IntOption('nombre_interfaces', "nombre d'interfaces à activer", - default=1) + default=1) activer_proxy_client = BoolOption('activer_proxy_client', "utiliser un proxy", default=False) mode_conteneur_actif = BoolOption('mode_conteneur_actif', "le serveur est en mode conteneur", default=False) adresse_serveur_ntp = StrOption('serveur_ntp', "adresse serveur ntp", multi=True) time_zone = ChoiceOption('time_zone', 'fuseau horaire du serveur', - ('Paris', 'Londres'), 'Paris') + ('Paris', 'Londres'), 'Paris') ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé") netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau") @@ -27,32 +29,34 @@ def make_description(): interface1.set_group_type(groups.family) general = OptionDescription('general', '', [numero_etab, nom_machine, - nombre_interfaces, activer_proxy_client, - mode_conteneur_actif, adresse_serveur_ntp, - time_zone]) + nombre_interfaces, activer_proxy_client, + mode_conteneur_actif, adresse_serveur_ntp, + time_zone]) general.set_group_type(groups.family) creole = OptionDescription('creole', 'first tiramisu configuration', [general, interface1]) - descr = OptionDescription('baseconfig', 'baseconifgdescr', [creole] ) + descr = OptionDescription('baseconfig', 'baseconifgdescr', [creole]) return descr + def test_base_config(): descr = make_description() config = Config(descr) - assert config.creole.general.activer_proxy_client == False + assert config.creole.general.activer_proxy_client is False assert config.creole.general.nom_machine == "eoleng" assert config.find_first(byname='nom_machine', type_='value') == "eoleng" result = {'general.numero_etab': None, 'general.nombre_interfaces': 1, - 'general.serveur_ntp': [], 'interface1.ip_admin_eth0.ip_admin_eth0': None, - 'general.mode_conteneur_actif': False, 'general.time_zone': 'Paris', - 'interface1.ip_admin_eth0.netmask_admin_eth0': None, 'general.nom_machine': - 'eoleng', 'general.activer_proxy_client': False} + 'general.serveur_ntp': [], 'interface1.ip_admin_eth0.ip_admin_eth0': None, + 'general.mode_conteneur_actif': False, 'general.time_zone': 'Paris', + 'interface1.ip_admin_eth0.netmask_admin_eth0': None, 'general.nom_machine': + 'eoleng', 'general.activer_proxy_client': False} assert config.creole.make_dict() == result result = {'serveur_ntp': [], 'mode_conteneur_actif': False, - 'ip_admin_eth0': None, 'time_zone': 'Paris', 'numero_etab': None, - 'netmask_admin_eth0': None, 'nom_machine': 'eoleng', 'activer_proxy_client': - False, 'nombre_interfaces': 1} + 'ip_admin_eth0': None, 'time_zone': 'Paris', 'numero_etab': None, + 'netmask_admin_eth0': None, 'nom_machine': 'eoleng', 'activer_proxy_client': + False, 'nombre_interfaces': 1} assert config.creole.make_dict(flatten=True) == result + def test_get_group_type(): descr = make_description() config = Config(descr) @@ -62,6 +66,7 @@ def test_get_group_type(): assert isinstance(grp.get_group_type(), groups.GroupType) raises(TypeError, 'grp.set_group_type(groups.default)') + def test_iter_on_groups(): descr = make_description() config = Config(descr) @@ -69,8 +74,9 @@ def test_iter_on_groups(): group_names = [res[0] for res in result] assert group_names == ['general', 'interface1'] + def test_iter_on_empty_group(): - config = Config(OptionDescription("name", "descr", [] )) + config = Config(OptionDescription("name", "descr", [])) result = list(config.iter_groups()) assert result == [] for i in config.iter_groups(): @@ -79,6 +85,7 @@ def test_iter_on_empty_group(): pass assert [] == list(config) + def test_groups_with_master(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) @@ -86,26 +93,30 @@ def test_groups_with_master(): interface1.set_group_type(groups.master) assert interface1.get_group_type() == groups.master + def test_groups_with_master_in_config(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) interface1.set_group_type(groups.master) - cfg = Config(interface1) + Config(interface1) assert interface1.get_group_type() == groups.master + def test_allowed_groups(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) raises(ValueError, "interface1.set_group_type('toto')") + def test_master_not_valid_name(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) invalid_group = OptionDescription('interface1', '', [ip_admin_eth0, netmask_admin_eth0]) raises(ValueError, "invalid_group.set_group_type(groups.master)") + def test_sub_group_in_master_group(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) @@ -113,14 +124,15 @@ def test_sub_group_in_master_group(): invalid_group = OptionDescription('ip_admin_eth0', '', [subgroup, ip_admin_eth0, netmask_admin_eth0]) raises(ValueError, "invalid_group.set_group_type(groups.master)") + def test_group_always_has_multis(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau") group = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0]) raises(ValueError, "group.set_group_type(groups.master)") -#____________________________________________________________ +#____________________________________________________________ def test_values_with_master_and_slaves(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) @@ -136,6 +148,7 @@ def test_values_with_master_and_slaves(): assert cfg.ip_admin_eth0.ip_admin_eth0 == ["192.168.230.145"] assert cfg.cfgimpl_get_values().getowner(opt) == owner + def test_reset_values_with_master_and_slaves(): ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True) netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True) @@ -149,6 +162,6 @@ def test_reset_values_with_master_and_slaves(): assert cfg.cfgimpl_get_values().getowner(opt) == owners.default cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145") assert cfg.cfgimpl_get_values().getowner(opt) == owner - cfg.cfgimpl_get_values().reset(opt) + del(cfg.ip_admin_eth0.ip_admin_eth0) assert cfg.cfgimpl_get_values().getowner(opt) == owners.default assert cfg.ip_admin_eth0.ip_admin_eth0 == [] diff --git a/tiramisu/config.py b/tiramisu/config.py index add91b2..9543f1d 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -72,17 +72,24 @@ class SubConfig(object): return self._setattr(name, value) + def cfgimpl_clean_cache(self): + self.cfgimpl_get_context().cfgimpl_clean_cache() + def _setattr(self, name, value, force_permissive=False): if '.' in name: homeconfig, name = self.cfgimpl_get_home_by_path(name) return homeconfig.__setattr__(name, value) child = getattr(self._cfgimpl_descr, name) if type(child) != SymLinkOption: - self.cfgimpl_get_values()._setitem(child, value, - force_permissive=force_permissive) + self.cfgimpl_get_values().setitem(child, value, + force_permissive=force_permissive) else: child.setoption(self.cfgimpl_get_context(), value) + def __delattr__(self, name): + child = getattr(self._cfgimpl_descr, name) + del(self.cfgimpl_get_values()[child]) + def __getattr__(self, name): return self._getattr(name) @@ -104,7 +111,7 @@ class SubConfig(object): force_properties=force_properties, validate=validate) # special attributes - if name.startswith('_cfgimpl_'): + if name.startswith('_cfgimpl_') or name.startswith('cfgimpl_'): # if it were in __dict__ it would have been found already object.__getattr__(self, name) opt_or_descr = getattr(self._cfgimpl_descr, name) @@ -128,10 +135,10 @@ class SubConfig(object): name)) return SubConfig(opt_or_descr, self._cfgimpl_context) else: - return self.cfgimpl_get_values()._getitem(opt_or_descr, - validate=validate, - force_properties=force_properties, - force_permissive=force_permissive) + return self.cfgimpl_get_values().getitem(opt_or_descr, + validate=validate, + force_properties=force_properties, + force_permissive=force_permissive) def cfgimpl_get_home_by_path(self, path, force_permissive=False, force_properties=None): """:returns: tuple (config, name)""" @@ -354,6 +361,10 @@ class Config(SubConfig): def _cfgimpl_build_all_paths(self): self._cfgimpl_descr.build_cache() + def cfgimpl_clean_cache(self): + self.cfgimpl_get_values().reset_cache() + self.cfgimpl_get_settings().reset_cache() + def unwrap_from_path(self, path): """convenience method to extract and Option() object from the Config() and it is **fast**: finds the option directly in the appropriate @@ -491,9 +502,11 @@ def mandatory_warnings(config): :returns: generator of mandatory Option's path """ + config.cfgimpl_clean_cache() for path in config.cfgimpl_get_description().getpaths(include_groups=True): try: config._getattr(path, force_properties=('mandatory',)) except PropertiesOptionError, err: if err.proptype == ['mandatory']: yield path + config.cfgimpl_clean_cache() diff --git a/tiramisu/option.py b/tiramisu/option.py index cad39f9..d905de5 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -236,11 +236,6 @@ class Option(BaseInformation): else: return True - def reset(self, config): - """resets the default value and owner - """ - config._cfgimpl_context._cfgimpl_values.reset(self) - def getkey(self, value): return value diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 5af26f9..05071ca 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -20,11 +20,14 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ - +from time import time from tiramisu.error import RequirementRecursionError, PropertiesOptionError from tiramisu.i18n import _ +expires_time = 5 + + class _const: """convenient class that emulates a module and builds constants (that is, unique names)""" @@ -139,17 +142,18 @@ populate_multitypes() #____________________________________________________________ class Setting(object): "``Config()``'s configuration options" - __slots__ = ('properties', 'permissives', 'owner', 'context') + __slots__ = ('properties', 'permissives', 'owner', 'context', '_cache') def __init__(self, context): # properties attribute: the name of a property enables this property # key is None for global properties - self.properties = {None: []} # ['hidden', 'disabled', 'mandatory', 'frozen', 'validator']} + self.properties = {None: ['expire']} # permissive properties self.permissives = {} # generic owner self.owner = owners.user self.context = context + self._cache = {} #____________________________________________________________ # properties methods @@ -177,6 +181,7 @@ class Setting(object): if propname not in props: props.append(propname) self.set_properties(props) + self.context.cfgimpl_clean_cache() def disable_property(self, propname): "deletes property propname in the Config's properties attribute" @@ -184,6 +189,7 @@ class Setting(object): if propname in props: props.remove(propname) self.set_properties(props) + self.context.cfgimpl_clean_cache() def set_properties(self, properties, opt=None): """save properties for specified opt @@ -203,44 +209,55 @@ class Setting(object): if not propname in properties: properties.append(propname) self.set_properties(properties, opt) + self.context.cfgimpl_clean_cache() def del_property(self, propname, opt, is_apply_req=True): properties = self.get_properties(opt, is_apply_req) if propname in properties: properties.remove(propname) self.set_properties(properties, opt) + self.context.cfgimpl_clean_cache() #____________________________________________________________ def validate_properties(self, opt_or_descr, is_descr, is_write, value=None, force_permissive=False, force_properties=None): - properties = set(self.get_properties(opt_or_descr)) - #remove this properties, those properties are validate in after - properties = properties - set(['mandatory', 'frozen']) - set_properties = self.get_properties() - if force_properties is not None: - set_properties.extend(force_properties) - set_properties = set(set_properties) - properties = properties & set_properties - if force_permissive is True or self.has_property('permissive', is_apply_req=False): - properties = properties - set(self.get_permissive()) - properties = properties - set(self.get_permissive(opt_or_descr)) - properties = list(properties) - raise_text = _("trying to access" - " to an option named: {0} with properties" - " {1}") - if not is_descr: - if self.context.cfgimpl_get_values().is_mandatory_err(opt_or_descr, - value, - force_properties=force_properties): - properties.append('mandatory') - if is_write and (self.has_property('everything_frozen') or ( - self.has_property('frozen') and - self.has_property('frozen', opt_or_descr, - is_apply_req=False))): - properties.append('frozen') - raise_text = _('cannot change the value to {0} for ' - 'option {1} this option is frozen') + is_cached = False + if opt_or_descr in self._cache: + t = time() + props, raise_text, created = self._cache[opt_or_descr] + if t - created < expires_time: + properties = props + is_cached = True + if not is_cached: + properties = set(self.get_properties(opt_or_descr)) + #remove this properties, those properties are validate in after + properties = properties - set(['mandatory', 'frozen']) + set_properties = self.get_properties() + if force_properties is not None: + set_properties.extend(force_properties) + set_properties = set(set_properties) + properties = properties & set_properties + if force_permissive is True or self.has_property('permissive', is_apply_req=False): + properties = properties - set(self.get_permissive()) + properties = properties - set(self.get_permissive(opt_or_descr)) + properties = list(properties) + raise_text = _("trying to access" + " to an option named: {0} with properties" + " {1}") + if not is_descr: + if self.context.cfgimpl_get_values().is_mandatory_err(opt_or_descr, + value, + force_properties=force_properties): + properties.append('mandatory') + if is_write and (self.has_property('everything_frozen') or ( + self.has_property('frozen') and + self.has_property('frozen', opt_or_descr, + is_apply_req=False))): + properties.append('frozen') + raise_text = _('cannot change the value to {0} for ' + 'option {1} this option is frozen') + self._set_cache(opt_or_descr, properties, raise_text) if properties != []: raise PropertiesOptionError(raise_text.format(opt_or_descr._name, str(properties)), @@ -285,6 +302,13 @@ class Setting(object): self.enable_property('validator') self.disable_property('permissive') + def _set_cache(self, opt, props, raise_text): + if self.has_property('expire'): + self._cache[opt] = (props, raise_text, time()) + + def reset_cache(self): + self._cache = {} + def apply_requires(opt, config): "carries out the jit (just in time requirements between options" diff --git a/tiramisu/value.py b/tiramisu/value.py index 5709c4a..fbd52e9 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -17,14 +17,52 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ____________________________________________________________ +from time import time from tiramisu.error import ConfigError -from tiramisu.setting import owners, multitypes +from tiramisu.setting import owners, multitypes, expires_time from tiramisu.autolib import carry_out_calculation from tiramisu.i18n import _ -class Values(object): - __slots__ = ('values', 'context') +class ExpirValues(object): + __slots__ = ('_cache') + + def __init__(self): + self._cache = {} + + def __getitem__(self, opt): + return self.getitem(opt) + + def getitem(self, opt, validate=True, force_permissive=False, + force_properties=None): + if opt in self._cache: + t = time() + value, created = self._cache[opt] + if t - created < expires_time: + return value + val = self._getitem(opt, validate, force_permissive, force_properties) + self._set_cache(opt, val) + return val + + def __setitem__(self, opt, value): + self.setitem(opt, value) + + def setitem(self, opt, value, force_permissive=False): + self._setitem(opt, value, force_permissive) + + def __delitem__(self, opt): + self._reset(opt) + + def _set_cache(self, opt, val): + if self.context.cfgimpl_get_settings().has_property('expire'): + self._cache[opt] = (val, time()) + + def reset_cache(self): + self._cache = {} + + +class Values(ExpirValues): + __slots__ = ('context', '_values') def __init__(self, context): """ @@ -33,13 +71,14 @@ class Values(object): :param context: the context is the home config's values """ "Config's root indeed is in charge of the `Option()`'s values" - self.values = {} self.context = context + self._values = {} + super(Values, self).__init__() def _get_value(self, opt): "return value or default value if not set" #if no value - if opt not in self.values: + if opt not in self._values: value = opt.getdefault() if opt.is_multi(): value = Multi(value, self.context, opt) @@ -58,12 +97,13 @@ class Values(object): #FIXME si inferieur ?? else: #if value - value = self.values[opt][1] + value = self._values[opt][1] return value - def reset(self, opt): - if opt in self.values: - del(self.values[opt]) + def _reset(self, opt): + if opt in self._values: + self.context.cfgimpl_clean_cache() + del(self._values[opt]) def _is_empty(self, opt, value): "convenience method to know if an option is empty" @@ -104,11 +144,7 @@ class Values(object): callback=callback, callback_params=callback_params) - def __getitem__(self, opt): - return self._getitem(opt) - - def _getitem(self, opt, validate=True, force_permissive=False, - force_properties=None): + def _getitem(self, opt, validate, force_permissive, force_properties): # options with callbacks setting = self.context.cfgimpl_get_settings() value = self._get_value(opt) @@ -126,7 +162,7 @@ class Values(object): if opt.is_multi(): value = self.fill_multi(opt, value) #suppress value if already set - self.reset(opt) + self._reset(opt) # frozen and force default elif is_frozen and setting.has_property('force_default_on_freeze', opt, False): value = opt.getdefault() @@ -137,23 +173,16 @@ class Values(object): ' for option {0}: {1}').format(opt._name, value)) if self.is_default_owner(opt) and \ setting.has_property('force_store_value', opt, False): - self.setitem(opt, value, validate=validate) + self.setitem(opt, value) setting.validate_properties(opt, False, False, value=value, force_permissive=force_permissive, force_properties=force_properties) return value - def __setitem__(self, opt, value): - self._setitem(opt, value) - def _setitem(self, opt, value, force_permissive=False, force_properties=None): - setting = self.context.cfgimpl_get_settings() - setting.validate_properties(opt, False, True, - value=value, force_permissive=force_permissive, - force_properties=force_properties) #valid opt if not opt.validate(value, self.context, - setting.has_property('validator')): + self.context.cfgimpl_get_settings().has_property('validator')): raise ValueError(_('invalid value {}' ' for option {}').format(value, opt._name)) if opt.is_multi(): @@ -176,26 +205,30 @@ class Values(object): opt._name, opt.master_slaves._name)) if not isinstance(value, Multi): value = Multi(value, self.context, opt) - self.setitem(opt, value) - - def setitem(self, opt, value): if type(value) == list: raise ValueError(_("the type of the value {0} which is multi shall " "be Multi and not list").format(str(value))) - self.values[opt] = (self.context.cfgimpl_get_settings().getowner(), value) + self._setvalue(opt, value, force_permissive=force_permissive, + force_properties=force_properties) - def __contains__(self, opt): - return opt in self.values + def _setvalue(self, opt, value, force_permissive=False, force_properties=None): + self.context.cfgimpl_clean_cache() + setting = self.context.cfgimpl_get_settings() + setting.validate_properties(opt, False, True, + value=value, + force_permissive=force_permissive, + force_properties=force_properties) + self._values[opt] = (setting.getowner(), value) def getowner(self, opt): - return self.values.get(opt, (owners.default, None))[0] + return self._values.get(opt, (owners.default, None))[0] def setowner(self, opt, owner): - if opt not in self.values: + if opt not in self._values: raise ConfigError(_('no value for {1} cannot change owner to {2}').format(opt)) if not isinstance(owner, owners.Owner): raise TypeError(_("invalid generic owner {0}").format(str(owner))) - self.values[opt] = (owner, self.values[opt][1]) + self._values[opt] = (owner, self._values[opt][1]) def is_default_owner(self, opt): """ @@ -205,6 +238,9 @@ class Values(object): """ return self.getowner(opt) == owners.default + def __contains__(self, opt): + return opt in self._values + # ____________________________________________________________ # multi types @@ -226,7 +262,8 @@ class Multi(list): def __setitem__(self, key, value): self._validate(value) - self.context.cfgimpl_get_values()[self.opt] = self + #assume not checking mandatory property + self.context.cfgimpl_get_values()._setvalue(self.opt, self) super(Multi, self).__setitem__(key, value) def append(self, value, force=False): @@ -241,11 +278,9 @@ class Multi(list): for slave in self.opt.get_master_slaves(): self.context.cfgimpl_get_values()[slave].append( slave.getdefault_multi(), force=True) - self.context.cfgimpl_get_settings().validate_properties(self.opt, - False, True, - value=value) self._validate(value) - self.context.cfgimpl_get_values().setitem(self.opt, self) + #assume not checking mandatory property + self.context.cfgimpl_get_values()._setvalue(self.opt, self) super(Multi, self).append(value) def _validate(self, value): @@ -268,5 +303,5 @@ class Multi(list): elif self.opt.multitype == multitypes.master: for slave in self.opt.get_master_slaves(): self.context.cfgimpl_get_values()[slave].pop(key, force=True) - self.context.cfgimpl_get_values().setitem(self.opt, self) + self.context.cfgimpl_get_values()._setvalue(self.opt, self) return super(Multi, self).pop(key)