* to "reset" a value, now you just have to delete it
authorEmmanuel Garette <egarette@cadoles.com>
Thu, 18 Apr 2013 18:26:40 +0000 (20:26 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Thu, 18 Apr 2013 18:26:40 +0000 (20:26 +0200)
config.unwrap_from_path("string").reset(config) => del(config.string)
* add cache for value/setting to 5 secds
to "reset" cache just do: config.cfgimpl_clean_cache()
* can desactivate cache by removing "expire" property

test/test_freeze.py
test/test_option_setting.py
test/test_parsing_group.py
tiramisu/config.py
tiramisu/option.py
tiramisu/setting.py
tiramisu/value.py

index d4f6d65..81127e4 100644 (file)
@@ -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'
index aa5e674..8bae976 100644 (file)
@@ -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")
index f3ea34f..5ddd2f3 100644 (file)
@@ -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 == []
index add91b2..9543f1d 100644 (file)
@@ -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()
index cad39f9..d905de5 100644 (file)
@@ -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
 
index 5af26f9..05071ca 100644 (file)
 # 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"
index 5709c4a..fbd52e9 100644 (file)
 # 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)