# -*- coding: utf-8 -*- "takes care of the option's values and multi values" # 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 # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # ____________________________________________________________ from tiramisu.error import MandatoryError, MultiTypeError, \ ConfigError, ValidateError from tiramisu.setting import owners, multitypes from tiramisu.autolib import carry_out_calculation class Values(object): __slots__ = ('values', 'context') def __init__(self, context): """ Initializes the values's dict. :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 def _get_value(self, opt): "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(value, self.context, opt) #if slave, had values until master's one if opt.get_multitype() == multitypes.slave: masterpath = self.context.cfgimpl_get_description().get_path_by_opt(opt.get_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(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): "convenience method to know if an option is empty" #FIXME: buggy ? #if value is not None: # return False if (not opt.is_multi() and value is None) or \ (opt.is_multi() and (value == [] or None in self._get_value(opt))): return True return False def _test_mandatory(self, opt, value, force_properties=None): setting = self.context.cfgimpl_get_settings() if force_properties is None: set_mandatory = setting.has_property('mandatory') else: set_mandatory = ('mandatory' in force_properties or setting.has_property('mandatory')) if setting.has_property('mandatory', opt, False) and set_mandatory: 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 if opt.is_multi(): for val in value: if val == '': raise MandatoryError("option: {0} is mandatory " "and shall have not empty value".format(opt._name)) else: if value == '': raise MandatoryError("option: {0} is mandatory " "and shall have not empty value".format(opt._name)) def fill_multi(self, opt, result): """fills a multi option with default and calculated values """ if not isinstance(result, list): _result = [result] else: _result = result 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) def _getitem(self, opt, force_properties=None, validate=True): # options with callbacks value = self._get_value(opt) setting = self.context.cfgimpl_get_settings() is_frozen = setting.has_property('frozen', opt, False) if opt.has_callback(): #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, False))): pass else: value = self._getcallback_value(opt) if opt.is_multi(): value = self.fill_multi(opt, value) #suppress value if already set self.reset(opt) # frozen and force default elif is_frozen and setting.has_property('force_default_on_freeze', opt, False): value = opt.getdefault() if opt.is_multi(): value = self.fill_multi(opt, value) self._test_mandatory(opt, value, force_properties) if validate and not opt.validate(value, self.context, setting.has_property('validator')): raise ValidateError('invalid calculated value returned' ' for option {0}: {1}'.format(opt._name, value)) return value def __setitem__(self, opt, value): if not opt.validate(value, self.context, self.context.cfgimpl_get_settings().has_property('validator')): raise ValidateError('invalid value {}' ' for option {}'.format(value, opt._name)) if opt.is_multi(): if opt.get_multitype() == multitypes.master: masterlen = len(value) for slave in opt.master_slaves: value_slave = self._get_value(slave) if len(value_slave) > masterlen: raise MultiTypeError("invalid len for the slave: {0}" " which has {1} as master".format( slave._name, opt._name)) elif len(value_slave) < masterlen: for num in range(0, masterlen - len(value_slave)): value_slave.append(slave.getdefault_multi(), force=True) elif opt.get_multitype() == multitypes.slave: if len(self._get_value(opt.master_slaves)) != len(value): raise MultiTypeError("invalid len for the slave: {0}" " which has {1} as master".format( 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 MultiTypeError("the type of the value {0} which is multi shall " "be Multi and not list".format(str(value))) self._test_mandatory(opt, value) self.values[opt] = (self.context.cfgimpl_get_settings().getowner(), value) def __contains__(self, opt): return opt in self.values def getowner(self, opt): return self.values.get(opt, (owners.default, None))[0] def setowner(self, opt, owner): if opt not in self.values: raise ConfigError('no value for {} cannot change owner to {}'.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]) def is_default_owner(self, opt): """ :param config: *must* be only the **parent** config (not the toplevel config) :return: boolean """ return self.getowner(opt) == owners.default # ____________________________________________________________ # multi types class Multi(list): """multi options values container that support item notation for the values of multi options""" __slots__ = ('opt', 'context') def __init__(self, lst, context, opt): """ :param lst: the Multi wraps a list value :param context: the home config that has the values :param opt: the option object that have this Multi value """ self.opt = opt self.context = context super(Multi, self).__init__(lst) def __setitem__(self, key, value): self._validate(value) self.context.cfgimpl_get_values()[self.opt] = self super(Multi, self).__setitem__(key, value) def append(self, value, force=False): """the list value can be updated (appened) only if the option is a master """ if not force: if self.opt.get_multitype() == multitypes.slave: raise MultiTypeError("cannot append a value on a multi option {0}" " which is a slave".format(self.opt._name)) elif self.opt.get_multitype() == multitypes.master: for slave in self.opt.get_master_slaves(): 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) def _validate(self, value): if value is not None and not self.opt._validate(value): raise ConfigError("invalid value {0} " "for option {1}".format(str(value), self.opt._name)) def pop(self, key, force=False): """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 """ if not force: if self.opt.multitype == multitypes.slave: raise MultiTypeError("cannot append a value on a multi option {0}" " which is a slave".format(self.opt._name)) 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) return super(Multi, self).pop(key)