# -*- 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 NoValueReturned, MandatoryError, MultiTypeError, \ ConfigError # , OptionValueError from tiramisu.setting import owners, multitypes 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): "special case for the multis: they never return None" if opt not in self.values: if opt.is_multi(): value = Multi(opt.getdefault(), self.context, opt) if opt.multitype == multitypes.slave: masterpath = self.context._cfgimpl_descr.get_path_by_opt(opt.master_slaves) mastervalue = getattr(self.context, masterpath) masterlen = len(mastervalue) 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] def reset(self, opt): if opt in self.values: del(self.values[opt]) def _is_empty(self, opt, value=None): "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 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: set_mandatory = setting.has_property('mandatory') else: 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(): 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 #multitype = self._get_multitype(opt) return Multi(_result, self.context, opt) # , multitype) def __getitem__(self, opt): return self._getitem(opt) def _getitem(self, opt, force_properties=None): # options with callbacks value = self._get_value(opt) if opt.has_callback(): setting = self.context.cfgimpl_get_settings() if (not setting.has_property('frozen', opt) or (setting.has_property('frozen', opt) and not setting.has_property('force_default_on_freeze', opt) )) and not self.context.cfgimpl_get_values().is_default_owner(opt): return self._get_value(opt) 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)) # frozen and force default if not opt.has_callback() and self.context.cfgimpl_get_settings().has_property('force_default_on_freeze', opt): value = opt.getdefault() if opt.is_multi(): value = self.fill_multi(opt, value) self._test_mandatory(opt, value, force_properties) return value def __setitem__(self, opt, value): if opt.is_multi(): if opt.multitype == multitypes.master: masterlen = len(value) for slave in self.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(None, force=True) elif opt.multitype == multitypes.slave: if len(self._get_value(self.opt.master_slaves)) != len(value): raise MultiTypeError("invalid len for the slave: {0}" " which has {1} as master".format( opt._name, self.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.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.master_slaves: self.context.cfgimpl_get_values()[slave].append(None, 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.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)