never same calculated properties has properties (only in cache)
authorEmmanuel Garette <egarette@cadoles.com>
Thu, 22 Aug 2013 20:46:02 +0000 (22:46 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Thu, 22 Aug 2013 20:48:09 +0000 (22:48 +0200)
test/test_requires.py
tiramisu/option.py
tiramisu/setting.py

index 9fa3837..9376364 100644 (file)
@@ -429,3 +429,13 @@ def test_requires_multi_disabled_inverse_2():
         except PropertiesOptionError, err:
             props = err.proptype
         assert props == ['disabled']
+
+
+def test_requires_requirement_append():
+    a = BoolOption('activate_service', '', True)
+    b = IPOption('ip_address_service', '',
+                 requires=[{'option': a, 'expected': False, 'action': 'disabled'}])
+    od = OptionDescription('service', '', [a, b])
+    c = Config(od)
+    c.read_write()
+    raises(ValueError, 'c.cfgimpl_get_settings()[b].append("disabled")')
index d95998a..1a36272 100644 (file)
@@ -97,10 +97,9 @@ class Option(BaseInformation):
     Reminder: an Option object is **not** a container for the value
     """
     __slots__ = ('_name', '_requires', '_multi', '_validator',
-                 '_default_multi',
-                 '_default',
-                 '_properties', '_callback', '_multitype',
-                 '_master_slaves', '_consistencies', '_empty')
+                 '_default_multi', '_default', '_properties', '_callback',
+                 '_multitype', '_master_slaves', '_consistencies', '_empty',
+                 '_calc_properties')
     _empty = ''
 
     def __init__(self, name, doc, default=None, default_multi=None,
@@ -130,8 +129,8 @@ class Option(BaseInformation):
         self._name = name
         self._impl_informations = {}
         self.impl_set_information('doc', doc)
-        requires = validate_requires_arg(requires, self._name)
-        self._requires = requires
+        self._calc_properties, self._requires = validate_requires_arg(
+            requires, self._name)
         self._multi = multi
         self._consistencies = None
         if validator is not None:
@@ -714,7 +713,8 @@ class OptionDescription(BaseInformation):
     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
     """
     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
-                 '_properties', '_children', '_consistencies')
+                 '_properties', '_children', '_consistencies',
+                 '_calc_properties')
 
     def __init__(self, name, doc, children, requires=None, properties=None):
         """
@@ -738,8 +738,7 @@ class OptionDescription(BaseInformation):
                                       '{0}').format(child))
             old = child
         self._children = (tuple(child_names), tuple(children))
-        requires = validate_requires_arg(requires, self._name)
-        self._requires = requires
+        self._calc_properties, self._requires = validate_requires_arg(requires, self._name)
         self._cache_paths = None
         self._consistencies = None
         if properties is None:
@@ -933,7 +932,7 @@ def validate_requires_arg(requires, name):
                      the description of the requires dictionnary
     """
     if requires is None:
-        return None
+        return None, None
     ret_requires = {}
     config_action = {}
 
@@ -999,7 +998,6 @@ def validate_requires_arg(requires, name):
                                             inverse, transitive, same_action)
         else:
             ret_requires[action][option][1].append(expected)
-
     ret = []
     for opt_requires in ret_requires.values():
         ret_action = []
@@ -1011,4 +1009,4 @@ def validate_requires_arg(requires, name):
                                      require[5])
             ret_action.append(req)
         ret.append(tuple(ret_action))
-    return tuple(ret)
+    return tuple(config_action.keys()), tuple(ret)
index 13176de..0b7551c 100644 (file)
@@ -161,6 +161,11 @@ class Property(object):
         self._properties = prop
 
     def append(self, propname):
+        if self._opt is not None and self._opt._calc_properties is not None \
+                and propname in self._opt._calc_properties:
+            raise ValueError(_('cannot append {0} property for option {1}: '
+                               'this property is calculated').format(
+                                   propname, self._opt._name))
         self._properties.add(propname)
         self._setting._setproperties(self._properties, self._opt, self._path)
 
@@ -250,9 +255,9 @@ class Settings(object):
                 is_cached, props = self._p_.getcache('property', path, ntime)
                 if is_cached:
                     return props
-            if is_apply_req:
-                self.apply_requires(opt, path)
             props = self._p_.getproperties(path, opt._properties)
+            if is_apply_req:
+                props |= self.apply_requires(opt, path)
             if 'expire' in self:
                 if ntime is None:
                     ntime = time()
@@ -261,11 +266,16 @@ class Settings(object):
 
     def append(self, propname):
         "puts property propname in the Config's properties attribute"
-        Property(self, self._getproperties()).append(propname)
+        props = self._p_.getproperties(None, default_properties)
+        props.add(propname)
+        self._setproperties(props, None, None)
 
     def remove(self, propname):
         "deletes property propname in the Config's properties attribute"
-        Property(self, self._getproperties()).remove(propname)
+        props = self._p_.getproperties(None, default_properties)
+        if propname in props:
+            props.remove(propname)
+            self._setproperties(props, None, None)
 
     def _setproperties(self, properties, opt, path):
         """save properties for specified opt
@@ -274,6 +284,8 @@ class Settings(object):
         if opt is None:
             self._p_.setproperties(None, properties)
         else:
+            if opt._calc_properties is not None:
+                properties -= opt._calc_properties
             if set(opt._properties) == properties:
                 self._p_.reset_properties(path)
             else:
@@ -378,56 +390,53 @@ class Settings(object):
 
     def apply_requires(self, opt, path):
         """carries out the jit (just in time) requirements between options
-        
-        a requirement is a tuple of this form that comes from the option's 
+
+        a requirement is a tuple of this form that comes from the option's
         requirements validation::
 
             (option, expected, action, inverse, transitive, same_action)
 
         let's have a look at all the tuple's items:
-        
+
         - **option** is the target option's name or path
-        
+
         - **expected** is the target option's value that is going to trigger an action
-        
-        - **action** is the (property) action to be accomplished if the target option 
+
+        - **action** is the (property) action to be accomplished if the target option
           happens to have the expected value
-        
-        - if **inverse** is `True` and if the target option's value does not 
-          apply, then the property action must be removed from the option's 
+
+        - if **inverse** is `True` and if the target option's value does not
+          apply, then the property action must be removed from the option's
           properties list (wich means that the property is inverted)
-          
-        - **transitive**: but what happens if the target option cannot be 
-          accessed ? We don't kown the target option's value. Actually if some 
-          property in the target option is not present in the permissive, the 
-          target option's value cannot be accessed. In this case, the 
-          **action** have to be applied to the option. (the **action** property 
+
+        - **transitive**: but what happens if the target option cannot be
+          accessed ? We don't kown the target option's value. Actually if some
+          property in the target option is not present in the permissive, the
+          target option's value cannot be accessed. In this case, the
+          **action** have to be applied to the option. (the **action** property
           is then added to the option).
-        
-        - **same_action**: actually, if **same_action** is `True`, the 
-          transitivity is not accomplished. The transitivity is accomplished 
-          only if the target option **has the same property** that the demanded 
-          action. If the target option's value is not accessible because of 
-          another reason, because of a property of another type, then an 
+
+        - **same_action**: actually, if **same_action** is `True`, the
+          transitivity is not accomplished. The transitivity is accomplished
+          only if the target option **has the same property** that the demanded
+          action. If the target option's value is not accessible because of
+          another reason, because of a property of another type, then an
           exception :exc:`~error.RequirementError` is raised.
 
-        And at last, if no target option matches the expected values, the 
+        And at last, if no target option matches the expected values, the
         action must be removed from the option's properties list.
-        
+
         :param opt: the option on wich the requirement occurs
         :type opt: `option.Option()`
         :param path: the option's path in the config
         :type path: str
         """
         if opt._requires is None:
-            return
+            return frozenset()
 
         # filters the callbacks
-        setting = Property(self,
-                           self._getproperties(opt, path, False),
-                           opt, path=path)
+        calc_properties = set()
         for requires in opt._requires:
-            matches = False
             for require in requires:
                 option, expected, action, inverse, \
                     transitive, same_action = require
@@ -460,13 +469,10 @@ class Settings(object):
                 if (not inverse and
                         value in expected or
                         inverse and value not in expected):
-                    matches = True
-                    setting.append(action)
+                    calc_properties.add(action)
                     # the calculation cannot be carried out
                     break
-            # no requirement has been triggered, then just reverse the action
-            if not matches:
-                setting.remove(action)
+            return calc_properties
 
     def _get_opt_path(self, opt):
         return self.context.cfgimpl_get_description().impl_get_path_by_opt(opt)