# -*- 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 time import time from tiramisu.error import ConfigError, SlaveError from tiramisu.setting import owners, multitypes, expires_time from tiramisu.autolib import carry_out_calculation from tiramisu.i18n import _ class Values(object): """The `Config`'s root is indeed in charge of the `Option()`'s values, but the values are physicaly located here, in `Values`, wich is also responsible of a caching utility. """ __slots__ = ('context', '_values', '_cache') def __init__(self, context): """ Initializes the values's dict. :param context: the context is the home config's values """ self.context = context self._values = {} self._cache = {} super(Values, self).__init__() def _get_default(self, opt): meta = self.context.cfgimpl_get_meta() if meta is not None: return meta.cfgimpl_get_values()[opt] else: return opt.impl_getdefault() def _get_value(self, opt, validate=True): "return value or default value if not set" #if no value if opt not in self._values: value = self._get_default(opt) if opt.impl_is_multi(): value = Multi(value, self.context, opt, validate) else: #if value value = self._values[opt][1] return value def __delitem__(self, opt): self._reset(opt) def _reset(self, opt): if opt in self._values: setting = self.context.cfgimpl_get_settings() opt.impl_validate(opt.impl_getdefault(), self.context, 'validator' in setting) self.context.cfgimpl_reset_cache() del(self._values[opt]) def _is_empty(self, opt, value): "convenience method to know if an option is empty" empty = opt._empty if (not opt.impl_is_multi() and (value is None or value == empty)) or \ (opt.impl_is_multi() and (value == [] or None in value or empty in value)): return True return False 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 get_modified_values(self): return self._values def getitem(self, opt, validate=True, force_permissive=False, force_properties=None, validate_properties=True): if opt in self._cache: exp = time() value, created = self._cache[opt] if exp < created: return value val = self._getitem(opt, validate, force_permissive, force_properties, validate_properties) if validate and validate_properties and force_permissive is False and \ force_properties is None: self._set_cache(opt, val) return val def _getitem(self, opt, validate, force_permissive, force_properties, validate_properties): # options with callbacks setting = self.context.cfgimpl_get_settings() is_frozen = 'frozen' in setting[opt] #if value is callback and is not set or frozen with force_default_on_freeze if opt.impl_has_callback() and (self.is_default_owner(opt) or (is_frozen and 'force_default_on_freeze' in setting[opt])): no_value_slave = False if opt.impl_is_multi() and opt.impl_get_multitype() == multitypes.slave: masterp = self.context.cfgimpl_get_description().impl_get_path_by_opt( opt.impl_get_master_slaves()) mastervalue = getattr(self.context, masterp) lenmaster = len(mastervalue) if lenmaster == 0: value = [] no_value_slave = True if not no_value_slave: value = self._getcallback_value(opt) if opt.impl_is_multi() and opt.impl_get_multitype() == multitypes.slave: if isinstance(value, list): raise ValueError('callback must not return list ' 'in slave {0}: {1}'.format(opt._name, value)) value = [value for i in range(lenmaster)] if opt.impl_is_multi(): value = Multi(value, self.context, opt, validate) #suppress value if already set self._reset(opt) # frozen and force default elif is_frozen and 'force_default_on_freeze' in setting[opt]: value = self._get_default(opt) if opt.impl_is_multi(): value = Multi(value, self.context, opt, validate) else: value = self._get_value(opt, validate) if validate: opt.impl_validate(value, self.context, 'validator' in setting) if self.is_default_owner(opt) and \ 'force_store_value' in setting[opt]: self.setitem(opt, value, is_write=False) if validate_properties: setting.validate_properties(opt, False, False, value=value, force_permissive=force_permissive, force_properties=force_properties) return value def __setitem__(self, opt, value): self.setitem(opt, value) def setitem(self, opt, value, force_permissive=False, is_write=True): #is_write is, for example, used with "force_store_value" #user didn't change value, so not write #valid opt opt.impl_validate(value, self.context, 'validator' in self.context.cfgimpl_get_settings()) if opt.impl_is_multi() and not isinstance(value, Multi): value = Multi(value, self.context, opt) self._setvalue(opt, value, force_permissive=force_permissive, is_write=is_write) def _setvalue(self, opt, value, force_permissive=False, force_properties=None, is_write=True, validate_properties=True): self.context.cfgimpl_reset_cache() setting = self.context.cfgimpl_get_settings() if validate_properties: setting.validate_properties(opt, False, is_write, value=value, force_permissive=force_permissive, force_properties=force_properties) self._values[opt] = (setting.getowner(), value) def getowner(self, opt): owner = self._values.get(opt, (owners.default, None))[0] meta = self.context.cfgimpl_get_meta() if owner is owners.default and meta is not None: owner = meta.cfgimpl_get_values().getowner(opt) return owner def setowner(self, opt, owner): if opt not in self._values: raise ConfigError(_('no value for {0} cannot change owner to {1}').format(opt._name, owner)) 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 def _set_cache(self, opt, val): if 'expire' in self.context.cfgimpl_get_settings(): self._cache[opt] = (val, time() + expires_time) def reset_cache(self, only_expired): if only_expired: exp = time() keys = self._cache.keys() for key in keys: val, created = self._cache[key] if exp > created: del(self._cache[key]) else: self._cache.clear() def __contains__(self, opt): return opt in self._values # ____________________________________________________________ # 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, value, context, opt, validate=True): """ :param value: 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 if not isinstance(value, list): value = [value] if validate and self.opt.impl_get_multitype() == multitypes.slave: value = self._valid_slave(value) elif self.opt.impl_get_multitype() == multitypes.master: self._valid_master(value) super(Multi, self).__init__(value) def _valid_slave(self, value): #if slave, had values until master's one masterp = self.context.cfgimpl_get_description().impl_get_path_by_opt( self.opt.impl_get_master_slaves()) mastervalue = getattr(self.context, masterp) masterlen = len(mastervalue) if len(value) > masterlen or (len(value) < masterlen and not self.context.cfgimpl_get_values().is_default_owner(self.opt)): raise SlaveError(_("invalid len for the slave: {0}" " which has {1} as master").format( self.opt._name, masterp)) elif len(value) < masterlen: for num in range(0, masterlen - len(value)): value.append(self.opt.impl_getdefault_multi()) #else: same len so do nothing return value def _valid_master(self, value): masterlen = len(value) values = self.context.cfgimpl_get_values() for slave in self.opt._master_slaves: if not values.is_default_owner(slave): value_slave = values._get_value(slave) if len(value_slave) > masterlen: raise SlaveError(_("invalid len for the master: {0}" " which has {1} as slave with" " greater len").format( self.opt._name, slave._name)) elif len(value_slave) < masterlen: for num in range(0, masterlen - len(value_slave)): value_slave.append(slave.impl_getdefault_multi(), force=True) def __setitem__(self, key, value): self._validate(value) #assume not checking mandatory property self.context.cfgimpl_get_values()._setvalue(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.impl_get_multitype() == multitypes.slave: raise SlaveError(_("cannot append a value on a multi option {0}" " which is a slave").format(self.opt._name)) elif self.opt.impl_get_multitype() == multitypes.master: for slave in self.opt.impl_get_master_slaves(): values = self.context.cfgimpl_get_values() if not values.is_default_owner(slave): if slave.impl_has_callback(): dvalue = values._getcallback_value(slave) else: dvalue = slave.impl_getdefault_multi() #get multi without valid properties values.getitem(slave, validate_properties=False).append( dvalue, force=True) self._validate(value) #set value without valid properties self.context.cfgimpl_get_values()._setvalue(self.opt, self, validate_properties=not force) super(Multi, self).append(value) def _validate(self, value): if value is not None and not self.opt._validate(value): raise ValueError(_("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.impl_get_multitype() == multitypes.slave: raise SlaveError(_("cannot pop a value on a multi option {0}" " which is a slave").format(self.opt._name)) elif self.opt.impl_get_multitype() == multitypes.master: for slave in self.opt.impl_get_master_slaves(): values = self.context.cfgimpl_get_values() if not values.is_default_owner(slave): #get multi without valid properties values.getitem(slave, validate_properties=False).pop(key, force=True) #set value without valid properties self.context.cfgimpl_get_values()._setvalue(self.opt, self, validate_properties=not force) return super(Multi, self).pop(key)