diff --git a/test/test_option_setting.py b/test/test_option_setting.py index 3a5a4db..97a0705 100644 --- a/test/test_option_setting.py +++ b/test/test_option_setting.py @@ -111,10 +111,10 @@ def test_access_with_multi_default(): s = StrOption("string", "", default=["string"], multi=True) descr = OptionDescription("options", "", [s]) config = Config(descr) - assert config._cfgimpl_values.owners[s] == 'default' + assert config._cfgimpl_values.getowner(s) == 'default' config.string = ["foo", "bar"] assert config.string == ["foo", "bar"] - assert config._cfgimpl_values.owners[s] == 'user' + assert config._cfgimpl_values.getowner(s) == 'user' #def test_attribute_access_with_multi2(): # s = StrOption("string", "", default="string", multi=True) diff --git a/tiramisu/config.py b/tiramisu/config.py index 514a985..47ee6dc 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -25,9 +25,9 @@ from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError, AmbigousOptionError, ConflictConfigError, NoMatchingOptionFound, MandatoryError, MethodCallError, NoValueReturned) from tiramisu.option import (OptionDescription, Option, SymLinkOption, - Multi, apply_requires) + apply_requires) from tiramisu.setting import groups, owners, Setting -from tiramisu.value import OptionValues +from tiramisu.value import OptionValues, Multi # ____________________________________________________________ class Config(object): @@ -43,20 +43,23 @@ class Config(object): :param context: the current root config :type context: `Config` """ + # main option description self._cfgimpl_descr = descr + # sub option descriptions + self._cfgimpl_subconfigs = {} self._cfgimpl_parent = parent + if context is None: + self._cfgimpl_context = self + else: + self._cfgimpl_context = context if parent == None: self._cfgimpl_settings = Setting() - self._cfgimpl_values = OptionValues() + self._cfgimpl_values = OptionValues(self._cfgimpl_context) else: if context is None: raise ConfigError("cannot find a value for this config") self._cfgimpl_settings = None self._cfgimpl_values = None - if context is None: - self._cfgimpl_context = self - else: - self._cfgimpl_context = context "warnings are a great idea, let's make up a better use of it" self._cfgimpl_warnings = [] self._cfgimpl_toplevel = self._cfgimpl_get_toplevel() @@ -91,21 +94,9 @@ class Config(object): #max len for a master/slave group max_len_child = 0 for child in self._cfgimpl_descr._children: - if isinstance(child, Option): - if child.is_multi(): - childdef = Multi(copy(child.getdefault()), config=self, - opt=child) - max_len_child = max(max_len_child, len(childdef)) - self._cfgimpl_context._cfgimpl_values[child] = childdef - self._cfgimpl_context._cfgimpl_values.previous_values[child] = list(childdef) - else: - childdef = child.getdefault() - self._cfgimpl_context._cfgimpl_values[child] = childdef - self._cfgimpl_context._cfgimpl_values.previous_values[child] = childdef - child.setowner(self, owners.default) - elif isinstance(child, OptionDescription): + if isinstance(child, OptionDescription): self._validate_duplicates(child._children) - self._cfgimpl_context._cfgimpl_values[child] = Config(child, parent=self, + self._cfgimpl_subconfigs[child] = Config(child, parent=self, context=self._cfgimpl_context) # def cfgimpl_update(self): @@ -139,7 +130,7 @@ class Config(object): if type(getattr(self._cfgimpl_descr, name)) != SymLinkOption: self._validate(name, getattr(self._cfgimpl_descr, name)) self.setoption(name, value, - self._cfgimpl_context._cfgimpl_settings.get_owner()) + self._cfgimpl_context._cfgimpl_settings.getowner()) def _validate(self, name, opt_or_descr, permissive=False): "validation for the setattr and the getattr" @@ -161,23 +152,6 @@ class Config(object): " {1}".format(name, str(properties)), properties) - def _is_empty(self, opt): - "convenience method to know if an option is empty" - if (not opt.is_multi() and self._cfgimpl_context._cfgimpl_values[opt] == None) or \ - (opt.is_multi() and (self._cfgimpl_context._cfgimpl_values[opt] == [] or \ - None in self._cfgimpl_context._cfgimpl_values[opt])): - return True - return False - - def _test_mandatory(self, path, opt): - # mandatory options - mandatory = self._cfgimpl_context._cfgimpl_settings.mandatory - if opt.is_mandatory() and mandatory: - if self._is_empty(opt) and \ - opt.is_empty_by_default(): - raise MandatoryError("option: {0} is mandatory " - "and shall have a value".format(path)) - def __getattr__(self, name): return self._getattr(name) @@ -185,12 +159,11 @@ class Config(object): """fills a multi option with default and calculated values """ # FIXME C'EST ENCORE DU N'IMPORTE QUOI - value = self._cfgimpl_context._cfgimpl_values[opt] if not isinstance(result, list): _result = [result] else: _result = result - return Multi(_result, value.config, opt=value.opt) + return Multi(_result, self._cfgimpl_context, opt) def _getattr(self, name, permissive=False): """ @@ -211,56 +184,18 @@ class Config(object): if type(opt_or_descr) == SymLinkOption: rootconfig = self._cfgimpl_get_toplevel() return getattr(rootconfig, opt_or_descr.path) - if opt_or_descr not in self._cfgimpl_context._cfgimpl_values: - raise AttributeError("%s object has no attribute %s" % - (self.__class__, name)) + self._validate(name, opt_or_descr, permissive) + if isinstance(opt_or_descr, OptionDescription): + if opt_or_descr not in self._cfgimpl_subconfigs: + raise AttributeError("%s with name %s object has no attribute %s" % + (self.__class__, opt_or_descr._name, name)) + return self._cfgimpl_subconfigs[opt_or_descr] # special attributes if name.startswith('_cfgimpl_'): # if it were in __dict__ it would have been found already return self.__dict__[name] - raise AttributeError("%s object has no attribute %s" % - (self.__class__, name)) - if not isinstance(opt_or_descr, OptionDescription): - # options with callbacks - if opt_or_descr.has_callback(): - value = self._cfgimpl_context._cfgimpl_values[opt_or_descr] - if (not opt_or_descr.is_frozen() or \ - not opt_or_descr.is_forced_on_freeze()) and \ - not opt_or_descr.is_default_owner(self): - return value - try: - result = opt_or_descr.getcallback_value( - self._cfgimpl_get_toplevel()) - except NoValueReturned, err: - pass - else: - if opt_or_descr.is_multi(): - _result = self.fill_multi(opt_or_descr, 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(name)) - _result = result - if _result != None and not opt_or_descr.validate(_result, - self._cfgimpl_context._cfgimpl_settings.validator): - raise ConfigError('invalid calculated value returned' - ' for option {0}'.format(name)) - self._cfgimpl_context._cfgimpl_values[opt_or_descr] = _result - opt_or_descr.setowner(self, owners.default) - # frozen and force default - if not opt_or_descr.has_callback() and opt_or_descr.is_forced_on_freeze(): - value = opt_or_descr.getdefault() - if opt_or_descr.is_multi(): - value = self.fill_multi(opt_or_descr, value, - use_default_multi=True, - default_multi=opt_or_descr.getdefault_multi()) - self._cfgimpl_context._cfgimpl_values[opt_or_descr] = value - opt_or_descr.setowner(self, owners.default) - self._test_mandatory(name, opt_or_descr) - value = self._cfgimpl_context._cfgimpl_values[opt_or_descr] - return value + return self._cfgimpl_context._cfgimpl_values[opt_or_descr] def unwrap_from_name(self, name): """convenience method to extract and Option() object from the Config() @@ -301,7 +236,7 @@ class Config(object): if not isinstance(who, owners.DefaultOwner): if type(value) != Multi: if type(value) == list: - value = Multi(value, self, child) + value = Multi(value, self._cfgimpl_context, child) else: raise ConfigError("invalid value for option:" " {0} that is set to multi".format(name)) @@ -339,7 +274,7 @@ class Config(object): except Exception, e: raise e # HiddenOptionError or DisabledOptionError homeconfig.setoption(name, value, - self._cfgimpl_context._cfgimpl_settings.get_owner()) + self._cfgimpl_context._cfgimpl_settings.getowner()) elif len(candidates) > 1: raise AmbigousOptionError( 'more than one option that ends with %s' % (key, )) diff --git a/tiramisu/option.py b/tiramisu/option.py index 2dc6221..ca949af 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -27,6 +27,7 @@ from tiramisu.error import (ConfigError, ConflictConfigError, NotFoundError, PropertiesOptionError) from tiramisu.autolib import carry_out_calculation from tiramisu.setting import groups, owners +from tiramisu.value import Multi requires_actions = [('hide', 'show'), ('enable', 'disable'), ('freeze', 'unfreeze')] @@ -37,62 +38,6 @@ for act1, act2 in requires_actions: reverse_actions[act1] = act2 reverse_actions[act2] = act1 # ____________________________________________________________ -# multi types - -class Multi(list): - """multi options values container - that support item notation for the values of multi options""" - def __init__(self, lst, config, opt): - """ - :param lst: the Multi wraps a list value - :param config: the parent config - :param opt: the option object that have this Multi value - """ - self.config = config - self.opt = opt - super(Multi, self).__init__(lst) - - def __setitem__(self, key, value): - self._setvalue(value, key, - who=self.config._cfgimpl_context._cfgimpl_settings.get_owner()) - - def append(self, value): - """the list value can be updated (appened) - only if the option is a master - """ - self._setvalue(value, - who=self.config._cfgimpl_context._cfgimpl_settings.get_owner()) - - def _setvalue(self, value, key=None, who=None): - if value != None: - if not self.opt._validate(value): - raise ConfigError("invalid value {0} " - "for option {1}".format(str(value), self.opt._name)) - oldvalue = list(self) - if key is None: - super(Multi, self).append(value) - else: - super(Multi, self).__setitem__(key, value) - if who != None: - if not isinstance(who, owners.Owner): - raise TypeError("invalid owner {0} for the value {1}".format( - str(who), str(value))) - self.opt.setowner(self.config, getattr(owners, who)) - self.config._cfgimpl_context._cfgimpl_values.previous_values[self.opt] = oldvalue - - def pop(self, key): - """the list value can be updated (poped) - only if the option is a master - - :param key: index of the element to pop - :return: the requested element - - """ - self.opt.setowner(self.config, - self.config._cfgimpl_context._cfgimpl_settings.get_owner()) - self.config._cfgimpl_context._cfgimpl_values.previous_values[self.opt] = list(self) - return super(Multi, self).pop(key) -# ____________________________________________________________ # class Option(HiddenBaseType, DisabledBaseType): """ @@ -267,7 +212,7 @@ class Option(HiddenBaseType, DisabledBaseType): def getowner(self, config): "config *must* be only the **parent** config (not the toplevel config)" - return config._cfgimpl_context._cfgimpl_values.owners[self] + return config._cfgimpl_context._cfgimpl_values.getowner(self) def reset(self, config): """resets the default value and owner @@ -297,13 +242,14 @@ class Option(HiddenBaseType, DisabledBaseType): if not self.is_multi() and value == '': value = None if self.is_multi() and '' in value: - value = Multi([{'': None}.get(i, i) for i in value], config, self) + value = Multi([{'': None}.get(i, i) for i in value], + config._cfgimpl_context, self) if config._cfgimpl_context._cfgimpl_settings.is_mandatory() \ and ((self.is_multi() and value == []) or \ (not self.is_multi() and value is None)): raise MandatoryError('cannot change the value to %s for ' 'option %s' % (value, name)) - if self not in config._cfgimpl_context._cfgimpl_values: + if self not in config._cfgimpl_descr._children: raise AttributeError('unknown option %s' % (name)) if config._cfgimpl_context._cfgimpl_settings.is_frozen_for_everything(): @@ -315,10 +261,10 @@ class Option(HiddenBaseType, DisabledBaseType): raise TypeError('cannot change the value to %s for ' 'option %s this option is frozen' % (str(value), name)) apply_requires(self, config) - if type(config._cfgimpl_context._cfgimpl_values[self]) == Multi: - config._cfgimpl_context._cfgimpl_values.previous_values[self] = list(config._cfgimpl_context._cfgimpl_values[self]) - else: - config._cfgimpl_context._cfgimpl_values.previous_values[self] = config._cfgimpl_context._cfgimpl_values[self] +# if type(config._cfgimpl_context._cfgimpl_values[self]) == Multi: +# config._cfgimpl_context._cfgimpl_values.previous_values[self] = list(config._cfgimpl_context._cfgimpl_values[self]) +# else: +# config._cfgimpl_context._cfgimpl_values.previous_values[self] = config._cfgimpl_context._cfgimpl_values[self] config._cfgimpl_context._cfgimpl_values[self] = value def getkey(self, value): diff --git a/tiramisu/setting.py b/tiramisu/setting.py index 7690dfe..ae9fe94 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -197,11 +197,11 @@ class Setting(): "freeze flag at Config level" return self.frozen - def set_owner(self, owner): + def setowner(self, owner): ":param owner: sets the default value for owner at the Config level" if not isinstance(owner, owners.Owner): raise TypeError("invalid generic owner {0}".format(str(owner))) self.owner = owner - def get_owner(self): + def getowner(self): return self.owner diff --git a/tiramisu/value.py b/tiramisu/value.py index 3ce8d1b..3abdccd 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"takes care of the option's values" +"takes care of the option's values and multi values" # Copyright (C) 2012 Team tiramisu (see AUTHORS for all contributors) # # This program is free software; you can redistribute it and/or modify @@ -20,19 +20,166 @@ # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ +from tiramisu.error import NoValueReturned, MandatoryError +from tiramisu.setting import owners class OptionValues(object): - def __init__(self): + def __init__(self, context): self.owners = {} "Config's root indeed is in charge of the `Option()`'s values" self.values = {} self.previous_values = {} + self.context = context - def __getitem__(self, opt_or_descr): - return self.values[opt_or_descr] + def _get_value(self, opt): + "special case for the multis: they never return None" + if opt.is_multi(): + if opt not in self.values: + # FIXME : default value for a multi, we shall work on groups + return Multi(opt.getdefault(), self.context, opt) + else: + if opt not in self.values: + return opt.getdefault() + return self.values[opt] - def __setitem__(self, opt_or_descr, value): - self.values[opt_or_descr] = value + def _is_empty(self, opt): + "convenience method to know if an option is empty" + if (not opt.is_multi() and self._get_value(opt) == None) or \ + (opt.is_multi() and (self._get_value(opt) == [] or \ + None in self._get_value(opt))): + return True + return False - def __contains__(self, opt_or_descr): - return opt_or_descr in self.values + def _test_mandatory(self, opt): + # mandatory options + mandatory = self.context._cfgimpl_settings.mandatory + if opt.is_mandatory() and mandatory: + if self._is_empty(opt) and \ + opt.is_empty_by_default(): + raise MandatoryError("option: {0} is mandatory " + "and shall have a value".format(opt._name)) + + def fill_multi(self, opt, result, use_default_multi=False, default_multi=None): + """fills a multi option with default and calculated values + """ + value = self._get_value(opt) + if not isinstance(result, list): + _result = [result] + else: + _result = result + return Multi(_result, self.context, opt) + + def __getitem__(self, opt): + # options with callbacks + if opt.has_callback(): + if (not opt.is_frozen() or \ + not opt.is_forced_on_freeze()) and \ + not opt.is_default_owner(self): + return self._get_value(opt) + try: + result = opt.getcallback_value( + self.context) + except NoValueReturned, err: + pass + else: + if opt.is_multi(): + #FIXME revoir les multis + _result = 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(name)) + _result = result + if _result != None and not opt.validate(_result, + self.context._cfgimpl_settings.validator): + raise ConfigError('invalid calculated value returned' + ' for option {0}'.format(name)) + self.values[opt] = _result + self.owners[opt] = owners.default + # frozen and force default + if not opt.has_callback() and opt.is_forced_on_freeze(): + value = opt.getdefault() + if opt.is_multi(): + #FIXME le fill_multi + value = self.fill_multi(opt, value, + use_default_multi=True, + default_multi=opt.getdefault_multi()) + self.values[opt] = value + self.owners[opt] = owners.default + self._test_mandatory(opt) + value = self._get_value(opt) + return value + + def __setitem__(self, opt, value): + if opt in self.values: + old_value = self.values[opt] + else: + old_value = None + if type(old_value) == Multi: + self.previous_values[opt] = list(value) + else: + self.previous_values[opt] = value + self.values[opt] = value + + def __contains__(self, opt): + return opt in self.values + + #____________________________________________________________ + def setowner(self, opt, owner): + pass + + def getowner(self, opt): + return self.owners.get(opt, owners.default) +# ____________________________________________________________ +# multi types +class Multi(list): + """multi options values container + that support item notation for the values of multi options""" + def __init__(self, lst, context, opt): + """ + :param lst: the Multi wraps a list value + :param context: the context has the settings and the values + :param opt: the option object that have this Multi value + """ + self.settings = context._cfgimpl_settings + self.opt = opt + self.values = context._cfgimpl_values + super(Multi, self).__init__(lst) + + def __setitem__(self, key, value): + self._setvalue(value, key, who=self.settings.getowner()) + + def append(self, value): + """the list value can be updated (appened) + only if the option is a master + """ + self._setvalue(value, who=self.settings.getowner(self.opt)) + + def _setvalue(self, value, key=None, who=None): + if value != None: + if not self.opt._validate(value): + raise ConfigError("invalid value {0} " + "for option {1}".format(str(value), self.opt._name)) + oldvalue = list(self) + if key is None: + super(Multi, self).append(value) + else: + super(Multi, self).__setitem__(key, value) + if who != None: + if not isinstance(who, owners.Owner): + raise TypeError("invalid owner {0} for the value {1}".format( + str(who), str(value))) + self.values.setowner(self.opt, getattr(owners, who)) + self.values.previous_values[self.opt] = oldvalue + + def pop(self, key): + """the list value can be updated (poped) + only if the option is a master + + :param key: index of the element to pop + :return: the requested element + """ + self.values.setowner(opt, self.settings.get_owner()) + self.values.previous_values[self.opt] = list(self) + return super(Multi, self).pop(key)