diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index b726c2b..84eb57d 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -89,6 +89,14 @@ 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) + 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) @@ -108,7 +116,7 @@ def test_sub_group_in_master_group(): invalid_group = OptionDescription('ip_admin_eth0', '', [subgroup, ip_admin_eth0, netmask_admin_eth0]) raises(ConfigError, "invalid_group.set_group_type(groups.master)") -def test_group_has_always_multis(): +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]) diff --git a/tiramisu/config.py b/tiramisu/config.py index ddead27..c31ada8 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "pretty small and local configuration management tool" -# Copyright (C) 2012 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -27,7 +27,7 @@ from tiramisu.error import (PropertiesOptionError, ConfigError, NotFoundError, from tiramisu.option import (OptionDescription, Option, SymLinkOption, apply_requires) from tiramisu.setting import groups, owners, Setting -from tiramisu.value import OptionValues, Multi +from tiramisu.value import OptionValues # ____________________________________________________________ class Config(object): @@ -92,32 +92,23 @@ class Config(object): - settles various default values for options """ self._validate_duplicates(self._cfgimpl_descr._children) - #max len for a master/slave group - max_len_child = 0 + if self._cfgimpl_descr.group_type == groups.master: + mastername = self._cfgimpl_descr._name + masteropt = getattr(self._cfgimpl_descr, mastername) + self._cfgimpl_values.master_groups[masteropt] = [] + for child in self._cfgimpl_descr._children: if isinstance(child, OptionDescription): self._validate_duplicates(child._children) self._cfgimpl_subconfigs[child] = Config(child, parent=self, context=self._cfgimpl_context) + if (self._cfgimpl_descr.group_type == groups.master and + child != masteropt): + self._cfgimpl_values.master_groups[child] = [] + self._cfgimpl_values.master_groups[masteropt].append(child) -# def cfgimpl_update(self): -# """dynamically adds `Option()` or `OptionDescription()` -# """ -# # FIXME this is an update for new options in the schema only -# # see the update_child() method of the descr object -# for child in self._cfgimpl_descr._children: -# if isinstance(child, Option): -# if child._name not in self._cfgimpl_values: -# if child.is_multi(): -# self._cfgimpl_values[child._name] = Multi( -# copy(child.getdefault()), config=self, opt=child) -# else: -# self._cfgimpl_values[child._name] = copy(child.getdefault()) -# child.setowner(self, owners.default) -# elif isinstance(child, OptionDescription): -# if child._name not in self._cfgimpl_values: -# self._cfgimpl_values[child._name] = Config(child, parent=self) - + if self._cfgimpl_descr.group_type == groups.master: + print self._cfgimpl_values.master_groups # ____________________________________________________________ # attribute methods def __setattr__(self, name, value): @@ -130,8 +121,7 @@ class Config(object): return setattr(homeconfig, name, value) 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.getowner()) + self.setoption(name, value) def _validate(self, name, opt_or_descr, permissive=False): "validation for the setattr and the getattr" @@ -156,15 +146,15 @@ class Config(object): def __getattr__(self, name): return self._getattr(name) - def fill_multi(self, opt, result, use_default_multi=False, default_multi=None): - """fills a multi option with default and calculated values - """ - # FIXME C'EST ENCORE DU N'IMPORTE QUOI - if not isinstance(result, list): - _result = [result] - else: - _result = result - return Multi(_result, self._cfgimpl_context, opt) +# def fill_multi(self, opt, result, use_default_multi=False, default_multi=None): +# """fills a multi option with default and calculated values +# """ +# # FIXME C'EST ENCORE DU N'IMPORTE QUOI +# if not isinstance(result, list): +# _result = [result] +# else: +# _result = result +# return Multi(_result, self._cfgimpl_context, opt) def _getattr(self, name, permissive=False): """ @@ -227,40 +217,16 @@ class Config(object): def setoption(self, name, value, who=None): """effectively modifies the value of an Option() (typically called by the __setattr__) - - :param who: an object that lives in `setting.owners` """ child = getattr(self._cfgimpl_descr, name) - if type(child) != SymLinkOption: - if who == None: - who = self._cfgimpl_context._cfgimpl_settings.owner - if child.is_multi(): - if not isinstance(who, owners.DefaultOwner): - if type(value) != Multi: - if type(value) == list: - value = Multi(value, self._cfgimpl_context, child) - else: - raise ConfigError("invalid value for option:" - " {0} that is set to multi".format(name)) - else: - value = self.fill_multi(child, child.getdefault(), - use_default_multi=True, - default_multi=child.getdefault_multi()) - if not isinstance(who, owners.Owner): - raise TypeError("invalid owner [{0}] for option: {1}".format( - str(who), name)) - child.setoption(self, value) - child.setowner(self, who) - else: - homeconfig = self._cfgimpl_get_toplevel() - child.setoption(homeconfig, value) + child.setoption(self, value) def set(self, **kwargs): """ do what I mean"-interface to option setting. Searches all paths starting from that config for matches of the optional arguments and sets the found option if the match is not ambiguous. - + :param kwargs: dict of name strings to values. """ all_paths = [p.split(".") for p in self.getpaths(allpaths=True)] @@ -276,8 +242,7 @@ class Config(object): pass except Exception, e: raise e # HiddenOptionError or DisabledOptionError - homeconfig.setoption(name, value, - self._cfgimpl_context._cfgimpl_settings.getowner()) + homeconfig.setoption(name, value) elif len(candidates) > 1: raise AmbigousOptionError( 'more than one option that ends with %s' % (key, )) @@ -380,7 +345,7 @@ class Config(object): """iteration on groups objects only. All groups are returned if `group_type` is `None`, otherwise the groups can be filtered by categories (families, or whatever). - + :param group_type: if defined, is an instance of `groups.GroupType` or `groups.MasterGroupType` that lives in `setting.groups` diff --git a/tiramisu/error.py b/tiramisu/error.py index ba4a289..0880e0f 100644 --- a/tiramisu/error.py +++ b/tiramisu/error.py @@ -23,3 +23,5 @@ class MandatoryError(Exception): pass class NoValueReturned(Exception): pass +class OptionValueError(Exception): + pass diff --git a/tiramisu/option.py b/tiramisu/option.py index ca949af..3fcf808 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "option types and option description for the configuration management" -# Copyright (C) 2012 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -27,7 +27,6 @@ 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')] @@ -217,7 +216,7 @@ class Option(HiddenBaseType, DisabledBaseType): def reset(self, config): """resets the default value and owner """ - config.setoption(self._name, self.getdefault(), owners.default) + config._cfgimpl_context._cfgimpl_values.reset(self) def is_default_owner(self, config): """ @@ -241,9 +240,9 @@ class Option(HiddenBaseType, DisabledBaseType): # so '' is considered as being None 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._cfgimpl_context, self) +# if self.is_multi() and '' in value: +# 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)): @@ -261,10 +260,6 @@ 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] config._cfgimpl_context._cfgimpl_values[self] = value def getkey(self, value): @@ -341,7 +336,7 @@ class SymLinkOption(object): self.opt = opt def setoption(self, config, value): - setattr(config, self.path, value) + setattr(config._cfgimpl_get_toplevel(), self.path, value) def __getattr__(self, name): if name in ('_name', 'path', 'opt', 'setoption'): diff --git a/tiramisu/setting.py b/tiramisu/setting.py index ae9fe94..23d2157 100644 --- a/tiramisu/setting.py +++ b/tiramisu/setting.py @@ -95,6 +95,26 @@ def populate_owners(): # names are in the module now populate_owners() + +class MultiTypeModule(_const): + class MultiType(str): + pass + class DefaultMultiType(MultiType): + pass + class MasterMultiType(MultiType): + pass + class SlaveMultiType(MultiType): + pass + +multitypes = MultiTypeModule() + +def populate_multitypes(): + setattr(multitypes, 'default', multitypes.DefaultMultiType('default')) + setattr(multitypes, 'master', multitypes.MasterMultiType('master')) + setattr(multitypes, 'slave', multitypes.SlaveMultiType('slave')) + +populate_multitypes() + #____________________________________________________________ class Setting(): "``Config()``'s configuration options" diff --git a/tiramisu/value.py b/tiramisu/value.py index 3abdccd..d6182f6 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "takes care of the option's values and multi values" -# Copyright (C) 2012 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,10 +25,16 @@ from tiramisu.setting import owners class OptionValues(object): def __init__(self, context): + """ + Initializes the values's dict. + + :param context: the context is the home config's values and properties + """ self.owners = {} "Config's root indeed is in charge of the `Option()`'s values" self.values = {} self.previous_values = {} + self.master_groups = {} self.context = context def _get_value(self, opt): @@ -42,24 +48,42 @@ class OptionValues(object): return opt.getdefault() return self.values[opt] - def _is_empty(self, opt): + def reset(self, opt): + if opt in self.values: + self.set_previous_value(opt) + del(self.values[opt]) + self.setowner(opt, owners.default) + + def set_previous_value(self, opt): + if opt in self.values: + old_value = self.values[opt] + else: + old_value = None + if type(old_value) == Multi: + self.previous_values[opt] = list(old_value) + else: + self.previous_values[opt] = old_value + + def _is_empty(self, opt, value=None): "convenience method to know if an option is empty" + if value is not None: + return False 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 _test_mandatory(self, opt): + def _test_mandatory(self, opt, value=None): # mandatory options mandatory = self.context._cfgimpl_settings.mandatory if opt.is_mandatory() and mandatory: - if self._is_empty(opt) and \ + 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)) - def fill_multi(self, opt, result, use_default_multi=False, default_multi=None): + def fill_multi(self, opt, result): """fills a multi option with default and calculated values """ value = self._get_value(opt) @@ -71,6 +95,7 @@ class OptionValues(object): def __getitem__(self, opt): # options with callbacks + value = self._get_value(opt) if opt.has_callback(): if (not opt.is_frozen() or \ not opt.is_forced_on_freeze()) and \ @@ -83,68 +108,57 @@ class OptionValues(object): pass else: if opt.is_multi(): - #FIXME revoir les multis - _result = fill_multi(opt, result) + value = 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, + raise ConfigError('invalid calculated value returned ' + 'for option {0} : shall not be a list'.format(name)) + value = result + if value != None and not opt.validate(value, 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) + value = self.fill_multi(opt, value) + self._test_mandatory(opt, value) 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.set_previous_value(opt) self.values[opt] = value + self.setowner(opt, self.context._cfgimpl_settings.getowner()) def __contains__(self, opt): return opt in self.values - #____________________________________________________________ def setowner(self, opt, owner): - pass + if isinstance(owner, owners.Owner): + self.owners[opt] = owner + else: + raise OptionValueError("Bad owner: " + str(owner)) 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): + def __init__(self, lst, context, opt, multitype=settings.multitypes.default): """ :param lst: the Multi wraps a list value - :param context: the context has the settings and the values + :param context: the home config that 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 + self.multitype = multitype super(Multi, self).__init__(lst) def __setitem__(self, key, value):