1 # -*- coding: utf-8 -*-
2 "takes care of the option's values and multi values"
3 # Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors)
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 # ____________________________________________________________
22 from tiramisu.error import ConfigError, SlaveError
23 from tiramisu.setting import owners, multitypes, expires_time, storage_type
24 from tiramisu.autolib import carry_out_calculation
25 from tiramisu.i18n import _
26 from tiramisu.option import SymLinkOption
30 """The `Config`'s root is indeed in charge of the `Option()`'s values,
31 but the values are physicaly located here, in `Values`, wich is also
32 responsible of a caching utility.
34 __slots__ = ('context', '_p_')
36 def __init__(self, context, storage):
38 Initializes the values's dict.
40 :param context: the context is the home config's values
43 self.context = context
44 # the storage type is dictionary or sqlite3
45 import_lib = 'tiramisu.storage.{0}.value'.format(storage_type)
46 self._p_ = __import__(import_lib, globals(), locals(), ['Values'],
49 def _getkey(self, opt):
50 """depends on the storage utility.
51 typically, the option's path in the parent `Config` or `SubConfig`
53 if self._p_.key_is_path:
54 return self._get_opt_path(opt)
58 def _getdefault(self, opt):
60 actually retrieves the default value
62 :param opt: the `option.Option()` object
64 meta = self.context.cfgimpl_get_meta()
66 value = meta.cfgimpl_get_values()[opt]
68 value = opt.impl_getdefault()
69 if opt.impl_is_multi():
74 def _getvalue(self, opt, validate=True):
75 """actually retrieves the value
77 :param opt: the `option.Option()` object
78 :returns: the option's value (or the default value if not set)
80 key = self._getkey(opt)
81 if not self._p_.hasvalue(key):
82 # if there is no value
83 value = self._getdefault(opt)
84 if opt.impl_is_multi():
85 value = Multi(value, self.context, opt, validate)
88 value = self._p_.getvalue(key)
89 if opt.impl_is_multi() and not isinstance(value, Multi):
90 # load value so don't need to validate if is not a Multi
91 value = Multi(value, self.context, opt, validate=False)
94 def get_modified_values(self):
95 return self._p_.get_modified_values()
97 def __contains__(self, opt):
99 implements the 'in' keyword syntax in order provide a pythonic way
100 to kow if an option have a value
102 :param opt: the `option.Option()` object
104 return self._p_.hasvalue('value', self._getkey(opt))
106 def __delitem__(self, opt):
107 """overrides the builtins `del()` instructions"""
110 def reset(self, opt):
111 key = self._getkey(opt)
112 if self._p_.hasvalue(key):
113 setting = self.context.cfgimpl_get_settings()
114 opt.impl_validate(opt.impl_getdefault(), self.context,
115 'validator' in setting)
116 self.context.cfgimpl_reset_cache()
117 if (opt.impl_is_multi() and
118 opt.impl_get_multitype() == multitypes.master):
119 for slave in opt.impl_get_master_slaves():
121 self._p_.resetvalue(key)
123 def _isempty(self, opt, value):
124 "convenience method to know if an option is empty"
126 if (not opt.impl_is_multi() and (value is None or value == empty)) or \
127 (opt.impl_is_multi() and (value == [] or
128 None in value or empty in value)):
132 def _getcallback_value(self, opt, index=None):
134 retrieves a value for the options that have a callback
136 :param opt: the `option.Option()` object
137 :param index: if an option is multi, only calculates the nth value
139 :returns: a calculated value
141 callback, callback_params = opt._callback
142 if callback_params is None:
144 return carry_out_calculation(opt._name, config=self.context,
146 callback_params=callback_params,
149 def __getitem__(self, opt):
150 "enables us to use the pythonic dictionnary-like access to values"
151 return self.getitem(opt)
153 def getitem(self, opt, validate=True, force_permissive=False,
154 force_properties=None, validate_properties=True):
156 key = self._getkey(opt)
157 if self._p_.hascache('value', self._getkey(opt)):
159 is_cached, value = self._p_.getcache('value', key, ntime)
161 if opt.impl_is_multi() and not isinstance(value, Multi):
162 #load value so don't need to validate if is not a Multi
163 value = Multi(value, self.context, opt, validate=False)
165 val = self._getitem(opt, validate, force_permissive, force_properties,
167 if 'expire' in self.context.cfgimpl_get_settings() and validate and \
168 validate_properties and force_permissive is False and \
169 force_properties is None:
172 self._p_.setcache('value', key, val, ntime + expires_time)
176 def _getitem(self, opt, validate, force_permissive, force_properties,
177 validate_properties):
178 # options with callbacks
179 setting = self.context.cfgimpl_get_settings()
180 is_frozen = 'frozen' in setting[opt]
181 # if value is callback and is not set
182 # or frozen with force_default_on_freeze
183 if opt.impl_has_callback() and (
184 self.is_default_owner(opt) or
185 (is_frozen and 'force_default_on_freeze' in setting[opt])):
186 no_value_slave = False
187 if (opt.impl_is_multi() and
188 opt.impl_get_multitype() == multitypes.slave):
189 masterp = self._get_opt_path(opt.impl_get_master_slaves())
190 mastervalue = getattr(self.context, masterp)
191 lenmaster = len(mastervalue)
194 no_value_slave = True
196 if not no_value_slave:
197 value = self._getcallback_value(opt)
198 if (opt.impl_is_multi() and
199 opt.impl_get_multitype() == multitypes.slave):
200 if not isinstance(value, list):
201 value = [value for i in range(lenmaster)]
202 if opt.impl_is_multi():
203 value = Multi(value, self.context, opt, validate)
204 # suppress value if already set
206 # frozen and force default
207 elif is_frozen and 'force_default_on_freeze' in setting[opt]:
208 value = self._getdefault(opt)
209 if opt.impl_is_multi():
210 value = Multi(value, self.context, opt, validate)
212 value = self._getvalue(opt, validate)
214 opt.impl_validate(value, self.context, 'validator' in setting)
215 if self.is_default_owner(opt) and \
216 'force_store_value' in setting[opt]:
217 self.setitem(opt, value, is_write=False)
218 if validate_properties:
219 setting.validate_properties(opt, False, False, value=value,
220 force_permissive=force_permissive,
221 force_properties=force_properties)
224 def __setitem__(self, opt, value):
225 self.setitem(opt, value)
227 def setitem(self, opt, value, force_permissive=False, is_write=True):
228 # is_write is, for example, used with "force_store_value"
229 # user didn't change value, so not write
231 opt.impl_validate(value, self.context,
232 'validator' in self.context.cfgimpl_get_settings())
233 if opt.impl_is_multi() and not isinstance(value, Multi):
234 value = Multi(value, self.context, opt)
235 self._setvalue(opt, value, force_permissive=force_permissive,
238 def _setvalue(self, opt, value, force_permissive=False,
239 force_properties=None,
240 is_write=True, validate_properties=True):
241 self.context.cfgimpl_reset_cache()
242 if validate_properties:
243 setting = self.context.cfgimpl_get_settings()
244 setting.validate_properties(opt, False, is_write,
246 force_permissive=force_permissive,
247 force_properties=force_properties)
248 owner = self.context.cfgimpl_get_settings().getowner()
249 self._p_.setvalue(self._getkey(opt), value, owner)
251 def getowner(self, opt):
253 retrieves the option's owner
255 :param opt: the `option.Option` object
256 :returns: a `setting.owners.Owner` object
258 if isinstance(opt, SymLinkOption):
260 owner = self._p_.getowner(self._getkey(opt), owners.default)
261 meta = self.context.cfgimpl_get_meta()
262 if owner is owners.default and meta is not None:
263 owner = meta.cfgimpl_get_values().getowner(opt)
266 def setowner(self, opt, owner):
268 sets a owner to an option
270 :param opt: the `option.Option` object
271 :param owner: a valid owner, that is a `setting.owners.Owner` object
273 if not isinstance(owner, owners.Owner):
274 raise TypeError(_("invalid generic owner {0}").format(str(owner)))
275 if self.getowner(opt) == owners.default:
276 raise ConfigError(_('no value for {0} cannot change owner to {1}'
277 '').format(opt._name, owner))
278 self._p_.setowner(self._getkey(opt), owner)
280 def is_default_owner(self, opt):
282 :param config: *must* be only the **parent** config
283 (not the toplevel config)
286 return self.getowner(opt) == owners.default
288 def reset_cache(self, only_expired):
290 clears the cache if necessary
293 self._p_.reset_expired_cache('value', time())
295 self._p_.reset_all_cache('value')
297 def _get_opt_path(self, opt):
299 retrieve the option's path in the config
301 :param opt: the `option.Option` object
302 :returns: a string with points like "gc.dummy.my_option"
304 return self.context.cfgimpl_get_description().impl_get_path_by_opt(opt)
306 # ____________________________________________________________
311 """multi options values container
312 that support item notation for the values of multi options"""
313 __slots__ = ('opt', 'context')
315 def __init__(self, value, context, opt, validate=True):
317 :param value: the Multi wraps a list value
318 :param context: the home config that has the values
319 :param opt: the option object that have this Multi value
322 self.context = context
323 if not isinstance(value, list):
325 if validate and self.opt.impl_get_multitype() == multitypes.slave:
326 value = self._valid_slave(value)
327 elif self.opt.impl_get_multitype() == multitypes.master:
328 self._valid_master(value)
329 super(Multi, self).__init__(value)
331 def _valid_slave(self, value):
332 #if slave, had values until master's one
333 masterp = self.context.cfgimpl_get_description().impl_get_path_by_opt(
334 self.opt.impl_get_master_slaves())
335 mastervalue = getattr(self.context, masterp)
336 masterlen = len(mastervalue)
337 valuelen = len(value)
338 if valuelen > masterlen or (valuelen < masterlen and
339 not self.context.cfgimpl_get_values(
340 ).is_default_owner(self.opt)):
341 raise SlaveError(_("invalid len for the slave: {0}"
342 " which has {1} as master").format(
343 self.opt._name, masterp))
344 elif valuelen < masterlen:
345 for num in range(0, masterlen - valuelen):
346 value.append(self.opt.impl_getdefault_multi())
347 #else: same len so do nothing
350 def _valid_master(self, value):
351 masterlen = len(value)
352 values = self.context.cfgimpl_get_values()
353 for slave in self.opt._master_slaves:
354 if not values.is_default_owner(slave):
355 value_slave = values._getvalue(slave)
356 if len(value_slave) > masterlen:
357 raise SlaveError(_("invalid len for the master: {0}"
358 " which has {1} as slave with"
359 " greater len").format(
360 self.opt._name, slave._name))
361 elif len(value_slave) < masterlen:
362 for num in range(0, masterlen - len(value_slave)):
363 value_slave.append(slave.impl_getdefault_multi(),
366 def __setitem__(self, key, value):
367 self._validate(value)
368 #assume not checking mandatory property
369 super(Multi, self).__setitem__(key, value)
370 self.context.cfgimpl_get_values()._setvalue(self.opt, self)
372 def append(self, value, force=False):
373 """the list value can be updated (appened)
374 only if the option is a master
377 if self.opt.impl_get_multitype() == multitypes.slave:
378 raise SlaveError(_("cannot append a value on a multi option {0}"
379 " which is a slave").format(self.opt._name))
380 elif self.opt.impl_get_multitype() == multitypes.master:
381 values = self.context.cfgimpl_get_values()
382 if value is None and self.opt.impl_has_callback():
383 value = values._getcallback_value(self.opt)
384 #Force None il return a list
385 if isinstance(value, list):
387 self._validate(value)
388 super(Multi, self).append(value)
389 self.context.cfgimpl_get_values()._setvalue(self.opt, self, validate_properties=not force)
390 if not force and self.opt.impl_get_multitype() == multitypes.master:
391 for slave in self.opt.impl_get_master_slaves():
392 if not values.is_default_owner(slave):
393 if slave.impl_has_callback():
394 index = self.__len__() - 1
395 dvalue = values._getcallback_value(slave, index=index)
397 dvalue = slave.impl_getdefault_multi()
398 old_value = values.getitem(slave, validate_properties=False)
399 if len(old_value) < self.__len__():
400 values.getitem(slave, validate_properties=False).append(
403 values.getitem(slave, validate_properties=False)[index] = dvalue
405 def sort(self, cmp=None, key=None, reverse=False):
406 if self.opt.impl_get_multitype() in [multitypes.slave,
408 raise SlaveError(_("cannot sort multi option {0} if master or slave"
409 "").format(self.opt._name))
410 super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse)
411 self.context.cfgimpl_get_values()._setvalue(self.opt, self)
414 if self.opt.impl_get_multitype() in [multitypes.slave,
416 raise SlaveError(_("cannot reverse multi option {0} if master or "
417 "slave").format(self.opt._name))
418 super(Multi, self).reverse()
419 self.context.cfgimpl_get_values()._setvalue(self.opt, self)
421 def insert(self, index, obj):
422 if self.opt.impl_get_multitype() in [multitypes.slave,
424 raise SlaveError(_("cannot insert multi option {0} if master or "
425 "slave").format(self.opt._name))
426 super(Multi, self).insert(index, obj)
427 self.context.cfgimpl_get_values()._setvalue(self.opt, self)
429 def extend(self, iterable):
430 if self.opt.impl_get_multitype() in [multitypes.slave,
432 raise SlaveError(_("cannot extend multi option {0} if master or "
433 "slave").format(self.opt._name))
434 super(Multi, self).extend(iterable)
435 self.context.cfgimpl_get_values()._setvalue(self.opt, self)
437 def _validate(self, value):
438 if value is not None:
440 self.opt._validate(value)
441 except ValueError, err:
442 raise ValueError(_("invalid value {0} "
443 "for option {1}: {2}"
444 "").format(str(value),
445 self.opt._name, err))
447 def pop(self, key, force=False):
448 """the list value can be updated (poped)
449 only if the option is a master
451 :param key: index of the element to pop
452 :return: the requested element
455 if self.opt.impl_get_multitype() == multitypes.slave:
456 raise SlaveError(_("cannot pop a value on a multi option {0}"
457 " which is a slave").format(self.opt._name))
458 elif self.opt.impl_get_multitype() == multitypes.master:
459 for slave in self.opt.impl_get_master_slaves():
460 values = self.context.cfgimpl_get_values()
461 if not values.is_default_owner(slave):
462 #get multi without valid properties
463 values.getitem(slave,
464 validate_properties=False
465 ).pop(key, force=True)
466 #set value without valid properties
467 ret = super(Multi, self).pop(key)
468 self.context.cfgimpl_get_values()._setvalue(self.opt, self, validate_properties=not force)