NoValueReturn is not needed now + apply_requires is now in settings
authorEmmanuel Garette <egarette@cadoles.com>
Mon, 8 Apr 2013 14:05:56 +0000 (16:05 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Mon, 8 Apr 2013 14:05:56 +0000 (16:05 +0200)
test/test_config_api.py
tiramisu/config.py
tiramisu/error.py
tiramisu/option.py
tiramisu/setting.py
tiramisu/value.py

index a26f1d7..26589e4 100644 (file)
@@ -108,7 +108,7 @@ def test_getpaths_with_hidden():
 def test_str():
     descr = make_description()
     c = Config(descr)
-    print c # does not crash
+    c # does not crash
 
 #def test_dir():
 #    descr = make_description()
index d0b870c..306f69b 100644 (file)
@@ -23,8 +23,7 @@
 #from inspect import getmembers, ismethod
 from tiramisu.error import (PropertiesOptionError, NotFoundError,
                             AmbigousOptionError, NoMatchingOptionFound, MandatoryError)
-from tiramisu.option import (OptionDescription, Option, SymLinkOption,
-                             apply_requires)
+from tiramisu.option import OptionDescription, Option, SymLinkOption
 from tiramisu.setting import groups, Setting
 from tiramisu.value import Values
 
@@ -78,7 +77,6 @@ class SubConfig(object):
 
     def _validate(self, name, opt_or_descr, force_permissive=False):
         "validation for the setattr and the getattr"
-        apply_requires(opt_or_descr, self)
         if not isinstance(opt_or_descr, Option) and \
                 not isinstance(opt_or_descr, OptionDescription):
             raise TypeError('Unexpected object: {0}'.format(repr(opt_or_descr)))
@@ -87,7 +85,7 @@ class SubConfig(object):
         properties = properties - set(['mandatory', 'frozen'])
         set_properties = set(self.cfgimpl_get_settings().get_properties())
         properties = properties & set_properties
-        if force_permissive is True or self.cfgimpl_get_settings().has_property('permissive'):
+        if force_permissive is True or self.cfgimpl_get_settings().has_property('permissive', is_apply_req=False):
             properties = properties - set(self.cfgimpl_get_settings().get_permissive())
         properties = properties - set(self.cfgimpl_get_settings().get_permissive(opt_or_descr))
         properties = list(properties)
@@ -306,9 +304,13 @@ class SubConfig(object):
             raise ValueError("make_dict can't filtering with value without option")
         if withoption is not None:
             mypath = self.getpath()
-            for path in self.cfgimpl_get_context()._find(bytype=Option, byname=withoption,
-                                                         byvalue=withvalue, byattrs=None,
-                                                         first=False, ret='path', _subpath=mypath):
+            for path in self.cfgimpl_get_context()._find(bytype=Option,
+                                                         byname=withoption,
+                                                         byvalue=withvalue,
+                                                         byattrs=None,
+                                                         first=False,
+                                                         type_='path',
+                                                         _subpath=mypath):
                 path = '.'.join(path.split('.')[:-1])
                 opt = self.cfgimpl_get_context().cfgimpl_get_description().get_opt_by_path(path)
                 if mypath is not None:
@@ -362,7 +364,7 @@ class Config(SubConfig):
         :param context: the current root config
         :type context: `Config`
         """
-        self._cfgimpl_settings = Setting()
+        self._cfgimpl_settings = Setting(self)
         self._cfgimpl_values = Values(self)
         super(Config, self).__init__(descr, self)  # , slots)
         self._cfgimpl_build_all_paths()
@@ -390,6 +392,7 @@ class Config(SubConfig):
 
         :param kwargs: dict of name strings to values.
         """
+        #opts, paths = self.cfgimpl_get_description()._cache_paths
         all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
         for key, value in kwargs.iteritems():
             key_p = key.split('.')
index fae89a7..3d82c65 100644 (file)
@@ -21,8 +21,6 @@ class RequirementRecursionError(RequiresError):
     pass
 class MandatoryError(Exception):
     pass
-class NoValueReturned(Exception):
-    pass
 class OptionValueError(Exception):
     pass
 class MultiTypeError(Exception):
index 04f0c10..cedea5e 100644 (file)
@@ -24,10 +24,8 @@ import re
 from copy import copy
 from types import FunctionType
 from tiramisu.error import (ConfigError, NotFoundError, ConflictConfigError,
-                            RequiresError, RequirementRecursionError,
-                            PropertiesOptionError)
-from tiramisu.autolib import carry_out_calculation
-from tiramisu.setting import groups, multitypes
+                            RequiresError)
+from tiramisu.setting import groups, multitypes, apply_requires
 
 name_regexp = re.compile(r'^\d+')
 
@@ -84,6 +82,7 @@ class Option(BaseInformation):
                  requires=None, multi=False, callback=None,
                  callback_params=None, validator=None, validator_args=None,
                  properties=None):
+        #FIXME : validation de callback et callback_params !!!
         """
         :param name: the option's name
         :param doc: the option's description
@@ -109,10 +108,11 @@ class Option(BaseInformation):
         validate_requires_arg(requires, self._name)
         self._requires = requires
         self.multi = multi
-        #self._validator_args = None
         if validator is not None:
             if type(validator) != FunctionType:
                 raise TypeError("validator must be a function")
+            if validator_args is None:
+                validator_args = {}
             self._validator = (validator, validator_args)
         else:
             self._validator = None
@@ -139,14 +139,14 @@ class Option(BaseInformation):
                 raise ConfigError("invalid default value {0} "
                                   "for option {1} : not list type"
                                   "".format(str(default), name))
-            if not self.validate(default, False):
+            if not self.validate(default):
                 raise ConfigError("invalid default value {0} "
                                   "for option {1}"
                                   "".format(str(default), name))
             self.multitype = multitypes.default
             self.default_multi = default_multi
         else:
-            if default is not None and not self.validate(default, False):
+            if default is not None and not self.validate(default):
                 raise ConfigError("invalid default value {0} "
                                   "for option {1}".format(str(default), name))
         self.default = default
@@ -216,14 +216,6 @@ class Option(BaseInformation):
         else:
             return True
 
-    def getcallback_value(self, config):
-        callback, callback_params = self.callback
-        if callback_params is None:
-            callback_params = {}
-        return carry_out_calculation(self._name, config=config,
-                                     callback=callback,
-                                     callback_params=callback_params)
-
     def reset(self, config):
         """resets the default value and owner
         """
@@ -558,54 +550,3 @@ def validate_requires_arg(requires, name):
                                         " action: {1}".format(name, action))
             else:
                 config_action[action] = inverse
-
-
-def apply_requires(opt, config):
-    "carries out the jit (just in time requirements between options"
-    def build_actions(requires):
-        "action are hide, show, enable, disable..."
-        trigger_actions = {}
-        for require in requires:
-            action = require[2]
-            trigger_actions.setdefault(action, []).append(require)
-        return trigger_actions
-    #for symlink
-    if hasattr(opt, '_requires') and opt._requires is not None:
-        # filters the callbacks
-        setting = config.cfgimpl_get_settings()
-        trigger_actions = build_actions(opt._requires)
-        optpath = config.cfgimpl_get_context().cfgimpl_get_description().get_path_by_opt(opt)
-        for requires in trigger_actions.values():
-            matches = False
-            for require in requires:
-                if len(require) == 3:
-                    path, expected, action = require
-                    inverse = False
-                elif len(require) == 4:
-                    path, expected, action, inverse = require
-                if path.startswith(optpath):
-                    raise RequirementRecursionError("malformed requirements "
-                                                    "imbrication detected for option: '{0}' "
-                                                    "with requirement on: '{1}'".format(optpath, path))
-                try:
-                    value = config.cfgimpl_get_context()._getattr(path, force_permissive=True)
-                except PropertiesOptionError, err:
-                    properties = err.proptype
-                    raise NotFoundError("option '{0}' has requirement's property error: "
-                                        "{1} {2}".format(opt._name, path, properties))
-                except Exception, err:
-                    raise NotFoundError("required option not found: "
-                                        "{0}".format(path))
-                if value == expected:
-                    if inverse:
-                        setting.del_property(action, opt)
-                    else:
-                        setting.add_property(action, opt)
-                    matches = True
-                    #FIXME optimisation : fait un double break non ? voire un return
-            # no requirement has been triggered, then just reverse the action
-            if not matches:
-                if inverse:
-                    setting.add_property(action, opt)
-                else:
-                    setting.del_property(action, opt)
index 964ec81..c3ed7c6 100644 (file)
@@ -21,6 +21,9 @@
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
 
+from tiramisu.error import (RequirementRecursionError, PropertiesOptionError,
+                            NotFoundError)
+
 
 class _const:
     """convenient class that emulates a module
@@ -136,9 +139,9 @@ populate_multitypes()
 #____________________________________________________________
 class Setting(object):
     "``Config()``'s configuration options"
-    __slots__ = ('properties', 'permissives', 'owner')
+    __slots__ = ('properties', 'permissives', 'owner', 'context')
 
-    def __init__(self):
+    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']}
@@ -146,24 +149,27 @@ class Setting(object):
         self.permissives = {}
         # generic owner
         self.owner = owners.user
+        self.context = context
 
     #____________________________________________________________
     # properties methods
-    def has_properties(self, opt=None):
+    def has_properties(self, opt=None, is_apply_req=True):
         "has properties means the Config's properties attribute is not empty"
-        return bool(len(self.get_properties(opt)))
+        return bool(len(self.get_properties(opt, is_apply_req)))
 
-    def get_properties(self, opt=None):
+    def get_properties(self, opt=None, is_apply_req=True):
         if opt is None:
             default = []
         else:
+            if is_apply_req:
+                apply_requires(opt, self.context)
             default = list(opt._properties)
         return self.properties.get(opt, default)
 
-    def has_property(self, propname, opt=None):
+    def has_property(self, propname, opt=None, is_apply_req=True):
         """has property propname in the Config's properties attribute
         :param property: string wich is the name of the property"""
-        return propname in self.get_properties(opt)
+        return propname in self.get_properties(opt, is_apply_req)
 
     def enable_property(self, propname):
         "puts property propname in the Config's properties attribute"
@@ -192,14 +198,14 @@ class Setting(object):
             else:
                 self.properties[opt] = properties
 
-    def add_property(self, propname, opt):
-        properties = self.get_properties(opt)
+    def add_property(self, propname, opt, is_apply_req=True):
+        properties = self.get_properties(opt, is_apply_req)
         if not propname in properties:
             properties.append(propname)
             self.set_properties(properties, opt)
 
-    def del_property(self, propname, opt):
-        properties = self.get_properties(opt)
+    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)
@@ -241,5 +247,56 @@ class Setting(object):
         self.enable_property('hidden')
         self.enable_property('disabled')
         self.disable_property('mandatory')
-        self.disable_property('validator')
+        self.enable_property('validator')
         self.disable_property('permissive')
+
+
+def apply_requires(opt, config):
+    "carries out the jit (just in time requirements between options"
+    def build_actions(requires):
+        "action are hide, show, enable, disable..."
+        trigger_actions = {}
+        for require in requires:
+            action = require[2]
+            trigger_actions.setdefault(action, []).append(require)
+        return trigger_actions
+    #for symlink
+    if hasattr(opt, '_requires') and opt._requires is not None:
+        # filters the callbacks
+        setting = config.cfgimpl_get_settings()
+        trigger_actions = build_actions(opt._requires)
+        optpath = config.cfgimpl_get_context().cfgimpl_get_description().get_path_by_opt(opt)
+        for requires in trigger_actions.values():
+            matches = False
+            for require in requires:
+                if len(require) == 3:
+                    path, expected, action = require
+                    inverse = False
+                elif len(require) == 4:
+                    path, expected, action, inverse = require
+                if path == optpath or path.startswith(optpath + '.'):
+                    raise RequirementRecursionError("malformed requirements "
+                                                    "imbrication detected for option: '{0}' "
+                                                    "with requirement on: '{1}'".format(optpath, path))
+                try:
+                    value = config.cfgimpl_get_context()._getattr(path, force_permissive=True)
+                except PropertiesOptionError, err:
+                    properties = err.proptype
+                    raise NotFoundError("option '{0}' has requirement's property error: "
+                                        "{1} {2}".format(opt._name, path, properties))
+                except AttributeError:
+                    raise NotFoundError("required option not found: "
+                                        "{0}".format(path))
+                if value == expected:
+                    if inverse:
+                        setting.del_property(action, opt, False)
+                    else:
+                        setting.add_property(action, opt, False)
+                    matches = True
+                    #FIXME optimisation : fait un double break non ? voire un return
+            # no requirement has been triggered, then just reverse the action
+            if not matches:
+                if inverse:
+                    setting.add_property(action, opt, False)
+                else:
+                    setting.del_property(action, opt, False)
index d24d8d8..9fc9a95 100644 (file)
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 # ____________________________________________________________
-from tiramisu.error import NoValueReturned, MandatoryError, MultiTypeError, \
+from tiramisu.error import MandatoryError, MultiTypeError, \
     ConfigError  # , OptionValueError
 from tiramisu.setting import owners, multitypes
+from tiramisu.autolib import carry_out_calculation
 
 
 class Values(object):
@@ -36,28 +37,35 @@ class Values(object):
         self.context = context
 
     def _get_value(self, opt):
-        "special case for the multis: they never return None"
+        "return value or default value if not set"
+        #if no value
         if opt not in self.values:
+            value = opt.getdefault()
             if opt.is_multi():
-                value = Multi(opt.getdefault(), self.context, opt)
+                value = Multi(value, self.context, opt)
+                #if slave, had values until master's one
                 if opt.multitype == multitypes.slave:
-                    masterpath = self.context._cfgimpl_descr.get_path_by_opt(opt.master_slaves)
+                    masterpath = self.context.cfgimpl_get_description().get_path_by_opt(opt.master_slaves)
                     mastervalue = getattr(self.context, masterpath)
                     masterlen = len(mastervalue)
+                    if len(value) > masterlen:
+                        raise MultiTypeError("invalid len for the slave: {0}"
+                                             " which has {1} as master".format(
+                                                 opt._name, masterpath))
                     if len(value) < masterlen:
                         for num in range(0, masterlen - len(value)):
-                            value.append(None, force=True)
-            else:
-                value = opt.getdefault()
-
-            return value
-        return self.values[opt][1]
+                            value.append(opt.getdefault_multi(), force=True)
+                    #FIXME si inferieur ??
+        else:
+            #if value
+            value = self.values[opt][1]
+        return value
 
     def reset(self, opt):
         if opt in self.values:
             del(self.values[opt])
 
-    def _is_empty(self, opt, value=None):
+    def _is_empty(self, opt, value):
         "convenience method to know if an option is empty"
         #FIXME: buggy ?
         #if value is not None:
@@ -68,22 +76,6 @@ class Values(object):
             return True
         return False
 
-    def is_empty(self, opt):
-        #FIXME that not empty ... just no value!
-        if opt not in self.values:
-            return True
-        value = self.values[opt][1]
-        if not opt.is_multi():
-            if self._get_value(opt) is None:
-                return True
-            return False
-        else:
-            value = list(value)
-            for val in value:
-                if val is not None:
-                    return False
-            return True
-
     def _test_mandatory(self, opt, value, force_properties=None):
         setting = self.context.cfgimpl_get_settings()
         if force_properties is None:
@@ -92,8 +84,7 @@ class Values(object):
             set_mandatory = ('mandatory' in force_properties or
                              setting.has_property('mandatory'))
         if setting.has_property('mandatory', opt) and set_mandatory:
-            if self._is_empty(opt, value) and \
-                    opt.is_empty_by_default():
+            if self._is_empty(opt, value) and opt.is_empty_by_default():
                 raise MandatoryError("option: {0} is mandatory "
                                      "and shall have a value".format(opt._name))
             #empty value
@@ -114,9 +105,16 @@ class Values(object):
             _result = [result]
         else:
             _result = result
-        #multitype = self._get_multitype(opt)
         return Multi(_result, self.context, opt)  # , multitype)
 
+    def _getcallback_value(self, opt):
+        callback, callback_params = opt.callback
+        if callback_params is None:
+            callback_params = {}
+        return carry_out_calculation(opt._name, config=self.context,
+                                     callback=callback,
+                                     callback_params=callback_params)
+
     def __getitem__(self, opt):
         return self._getitem(opt)
 
@@ -124,32 +122,25 @@ class Values(object):
         # options with callbacks
         value = self._get_value(opt)
         setting = self.context.cfgimpl_get_settings()
+        is_frozen = setting.has_property('frozen', opt)
         if opt.has_callback():
-            is_frozen = setting.has_property('frozen', opt)
-            if (not is_frozen or (is_frozen and
-                                  not setting.has_property('force_default_on_freeze', opt)
-                                  )) and not self.context.cfgimpl_get_values().is_default_owner(opt):
+            #if value is set and :
+            # - not frozen
+            # - frozen and not force_default_on_freeze
+            if not self.is_default_owner(opt) and (
+                    not is_frozen or (is_frozen and
+                                      not setting.has_property('force_default_on_freeze', opt))):
                 return value
-            try:
-                result = opt.getcallback_value(self.context)
-            except NoValueReturned:
-                pass
-            else:
-                if opt.is_multi():
-                    value = self.fill_multi(opt, result)
-                else:
-                    # this result **shall not** be a list
-                    if isinstance(result, list):
-                        raise ConfigError('invalid calculated value returned '
-                                          'for option {0} : shall not be a list'
-                                          ''.format(opt._name))
-                    value = result
-                if value is not None and \
-                        not opt.validate(value, setting.has_property('validator')):
-                    raise ConfigError('invalid calculated value returned'
-                                      ' for option {0}'.format(opt._name))
+            value = self._getcallback_value(opt)
+            if opt.is_multi():
+                value = self.fill_multi(opt, value)
+            if not opt.validate(value, setting.has_property('validator')):
+                raise ConfigError('invalid calculated value returned'
+                                  ' for option {0}: {1}'.format(opt._name, value))
+            #suppress value if already set
+            self.reset(opt)
         # frozen and force default
-        elif setting.has_property('force_default_on_freeze', opt):
+        elif is_frozen and setting.has_property('force_default_on_freeze', opt):
             value = opt.getdefault()
             if opt.is_multi():
                 value = self.fill_multi(opt, value)
@@ -168,7 +159,7 @@ class Values(object):
                                                  slave._name, opt._name))
                     elif len(value_slave) < masterlen:
                         for num in range(0, masterlen - len(value_slave)):
-                            value_slave.append(None, force=True)
+                            value_slave.append(slave.getdefault_multi(), force=True)
 
             elif opt.multitype == multitypes.slave:
                 if len(self._get_value(opt.master_slaves)) != len(value):
@@ -241,7 +232,7 @@ class Multi(list):
                                      " which is a slave".format(self.opt._name))
             elif self.opt.multitype == multitypes.master:
                 for slave in self.opt.master_slaves:
-                    self.context.cfgimpl_get_values()[slave].append(None, force=True)
+                    self.context.cfgimpl_get_values()[slave].append(slave.getdefault_multi(), force=True)
         self._validate(value)
         self.context.cfgimpl_get_values().setitem(self.opt, self)
         super(Multi, self).append(value)