impl_get_information and impl_set_information are, now, persistent in storage
[tiramisu.git] / tiramisu / value.py
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)
4 #
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.
9 #
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.
14 #
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
18 #
19 # ____________________________________________________________
20 from time import time
21 from copy import copy
22 import sys
23 import weakref
24 from tiramisu.error import ConfigError, SlaveError
25 from tiramisu.setting import owners, multitypes, expires_time
26 from tiramisu.autolib import carry_out_calculation
27 from tiramisu.i18n import _
28 from tiramisu.option import SymLinkOption
29
30
31 class Values(object):
32     """The `Config`'s root is indeed  in charge of the `Option()`'s values,
33     but the values are physicaly located here, in `Values`, wich is also
34     responsible of a caching utility.
35     """
36     __slots__ = ('context', '_p_', '__weakref__')
37
38     def __init__(self, context, storage):
39         """
40         Initializes the values's dict.
41
42         :param context: the context is the home config's values
43
44         """
45         self.context = weakref.ref(context)
46         # the storage type is dictionary or sqlite3
47         import_lib = 'tiramisu.storage.{0}.value'.format(storage.storage)
48         self._p_ = __import__(import_lib, globals(), locals(), ['Values'],
49                               ).Values(storage)
50
51     def _getdefault(self, opt):
52         """
53         actually retrieves the default value
54
55         :param opt: the `option.Option()` object
56         """
57         meta = self.context().cfgimpl_get_meta()
58         if meta is not None:
59             value = meta.cfgimpl_get_values()[opt]
60         else:
61             value = opt.impl_getdefault()
62         if opt.impl_is_multi():
63             return copy(value)
64         else:
65             return value
66
67     def _getvalue(self, opt, path, validate=True):
68         """actually retrieves the value
69
70         :param opt: the `option.Option()` object
71         :returns: the option's value (or the default value if not set)
72         """
73         if not self._p_.hasvalue(path):
74             # if there is no value
75             value = self._getdefault(opt)
76             if opt.impl_is_multi():
77                 value = Multi(value, self.context, opt, path, validate)
78         else:
79             # if there is a value
80             value = self._p_.getvalue(path)
81             if opt.impl_is_multi() and not isinstance(value, Multi):
82                 # load value so don't need to validate if is not a Multi
83                 value = Multi(value, self.context, opt, path, validate=False)
84         return value
85
86     def get_modified_values(self):
87         return self._p_.get_modified_values()
88
89     def __contains__(self, opt):
90         """
91         implements the 'in' keyword syntax in order provide a pythonic way
92         to kow if an option have a value
93
94         :param opt: the `option.Option()` object
95         """
96         path = self._get_opt_path(opt)
97         return self._contains(path)
98
99     def _contains(self, path):
100         return self._p_.hasvalue(path)
101
102     def __delitem__(self, opt):
103         """overrides the builtins `del()` instructions"""
104         self.reset(opt)
105
106     def reset(self, opt, path=None):
107         if path is None:
108             path = self._get_opt_path(opt)
109         if self._p_.hasvalue(path):
110             setting = self.context().cfgimpl_get_settings()
111             opt.impl_validate(opt.impl_getdefault(), self.context(),
112                               'validator' in setting)
113             self.context().cfgimpl_reset_cache()
114             if (opt.impl_is_multi() and
115                     opt.impl_get_multitype() == multitypes.master):
116                 for slave in opt.impl_get_master_slaves():
117                     self.reset(slave)
118             self._p_.resetvalue(path)
119
120     def _isempty(self, opt, value):
121         "convenience method to know if an option is empty"
122         empty = opt._empty
123         if (not opt.impl_is_multi() and (value is None or value == empty)) or \
124            (opt.impl_is_multi() and (value == [] or
125                                      None in value or empty in value)):
126             return True
127         return False
128
129     def _getcallback_value(self, opt, index=None):
130         """
131         retrieves a value for the options that have a callback
132
133         :param opt: the `option.Option()` object
134         :param index: if an option is multi, only calculates the nth value
135         :type index: int
136         :returns: a calculated value
137         """
138         callback, callback_params = opt._callback
139         if callback_params is None:
140             callback_params = {}
141         return carry_out_calculation(opt._name, config=self.context(),
142                                      callback=callback,
143                                      callback_params=callback_params,
144                                      index=index)
145
146     def __getitem__(self, opt):
147         "enables us to use the pythonic dictionary-like access to values"
148         return self.getitem(opt)
149
150     def getitem(self, opt, path=None, validate=True, force_permissive=False,
151                 force_properties=None, validate_properties=True):
152         ntime = None
153         if path is None:
154             path = self._get_opt_path(opt)
155         if self._p_.hascache('value', path):
156             ntime = time()
157             is_cached, value = self._p_.getcache('value', path, ntime)
158             if is_cached:
159                 if opt.impl_is_multi() and not isinstance(value, Multi):
160                     #load value so don't need to validate if is not a Multi
161                     value = Multi(value, self.context, opt, path, validate=False)
162                 return value
163         val = self._getitem(opt, path, validate, force_permissive, force_properties,
164                             validate_properties)
165         if 'expire' in self.context().cfgimpl_get_settings() and validate and \
166                 validate_properties and force_permissive is False and \
167                 force_properties is None:
168             if ntime is None:
169                 ntime = time()
170             self._p_.setcache('value', path, val, ntime + expires_time)
171
172         return val
173
174     def _getitem(self, opt, path, validate, force_permissive, force_properties,
175                  validate_properties):
176         # options with callbacks
177         setting = self.context().cfgimpl_get_settings()
178         is_frozen = 'frozen' in setting[opt]
179         # if value is callback and is not set
180         # or frozen with force_default_on_freeze
181         if opt.impl_has_callback() and (
182                 self._is_default_owner(path) or
183                 (is_frozen and 'force_default_on_freeze' in setting[opt])):
184             no_value_slave = False
185             if (opt.impl_is_multi() and
186                     opt.impl_get_multitype() == multitypes.slave):
187                 masterp = self._get_opt_path(opt.impl_get_master_slaves())
188                 mastervalue = getattr(self.context(), masterp)
189                 lenmaster = len(mastervalue)
190                 if lenmaster == 0:
191                     value = []
192                     no_value_slave = True
193
194             if not no_value_slave:
195                 value = self._getcallback_value(opt)
196                 if (opt.impl_is_multi() and
197                         opt.impl_get_multitype() == multitypes.slave):
198                     if not isinstance(value, list):
199                         value = [value for i in range(lenmaster)]
200             if opt.impl_is_multi():
201                 value = Multi(value, self.context, opt, path, validate)
202             # suppress value if already set
203             self.reset(opt, path)
204         # frozen and force default
205         elif is_frozen and 'force_default_on_freeze' in setting[opt]:
206             value = self._getdefault(opt)
207             if opt.impl_is_multi():
208                 value = Multi(value, self.context, opt, path, validate)
209         else:
210             value = self._getvalue(opt, path, validate)
211         if validate:
212             opt.impl_validate(value, self.context(), 'validator' in setting)
213         if self._is_default_owner(path) and \
214                 'force_store_value' in setting[opt]:
215             self.setitem(opt, value, path, is_write=False)
216         if validate_properties:
217             setting.validate_properties(opt, False, False, value=value, path=path,
218                                         force_permissive=force_permissive,
219                                         force_properties=force_properties)
220         return value
221
222     def __setitem__(self, opt, value):
223         raise ValueError('you should only set value with config')
224
225     def setitem(self, opt, value, path, force_permissive=False,
226                 is_write=True):
227         # is_write is, for example, used with "force_store_value"
228         # user didn't change value, so not write
229         # valid opt
230         opt.impl_validate(value, self.context(),
231                           'validator' in self.context().cfgimpl_get_settings())
232         if opt.impl_is_multi() and not isinstance(value, Multi):
233             value = Multi(value, self.context, opt, path)
234         self._setvalue(opt, path, value, force_permissive=force_permissive,
235                        is_write=is_write)
236
237     def _setvalue(self, opt, path, value, force_permissive=False,
238                   force_properties=None,
239                   is_write=True, validate_properties=True):
240         self.context().cfgimpl_reset_cache()
241         if validate_properties:
242             setting = self.context().cfgimpl_get_settings()
243             setting.validate_properties(opt, False, is_write,
244                                         value=value, path=path,
245                                         force_permissive=force_permissive,
246                                         force_properties=force_properties)
247         owner = self.context().cfgimpl_get_settings().getowner()
248         self._p_.setvalue(path, value, owner)
249
250     def getowner(self, opt):
251         """
252         retrieves the option's owner
253
254         :param opt: the `option.Option` object
255         :returns: a `setting.owners.Owner` object
256         """
257         if isinstance(opt, SymLinkOption):
258             opt = opt._opt
259         path = self._get_opt_path(opt)
260         return self._getowner(path)
261
262     def _getowner(self, path):
263         owner = self._p_.getowner(path, owners.default)
264         meta = self.context().cfgimpl_get_meta()
265         if owner is owners.default and meta is not None:
266             owner = meta.cfgimpl_get_values()._getowner(path)
267         return owner
268
269     def setowner(self, opt, owner):
270         """
271         sets a owner to an option
272
273         :param opt: the `option.Option` object
274         :param owner: a valid owner, that is a `setting.owners.Owner` object
275         """
276         if not isinstance(owner, owners.Owner):
277             raise TypeError(_("invalid generic owner {0}").format(str(owner)))
278
279         path = self._get_opt_path(opt)
280         self._setowner(path, owner)
281
282     def _setowner(self, path, owner):
283         if self._getowner(path) == owners.default:
284             raise ConfigError(_('no value for {0} cannot change owner to {1}'
285                                 '').format(path, owner))
286         self._p_.setowner(path, owner)
287
288     def is_default_owner(self, opt):
289         """
290         :param config: *must* be only the **parent** config
291                        (not the toplevel config)
292         :return: boolean
293         """
294         path = self._get_opt_path(opt)
295         return self._is_default_owner(path)
296
297     def _is_default_owner(self, path):
298         return self._getowner(path) == owners.default
299
300     def reset_cache(self, only_expired):
301         """
302         clears the cache if necessary
303         """
304         if only_expired:
305             self._p_.reset_expired_cache('value', time())
306         else:
307             self._p_.reset_all_cache('value')
308
309     def _get_opt_path(self, opt):
310         """
311         retrieve the option's path in the config
312
313         :param opt: the `option.Option` object
314         :returns: a string with points like "gc.dummy.my_option"
315         """
316         return self.context().cfgimpl_get_description().impl_get_path_by_opt(opt)
317
318     # information
319     def set_information(self, key, value):
320         """updates the information's attribute
321
322         :param key: information's key (ex: "help", "doc"
323         :param value: information's value (ex: "the help string")
324         """
325         self._p_.set_information(key, value)
326
327     def get_information(self, key, default=None):
328         """retrieves one information's item
329
330         :param key: the item string (ex: "help")
331         """
332         try:
333             return self._p_.get_information(key)
334         except ValueError:
335             if default is not None:
336                 return default
337             else:
338                 raise ValueError(_("information's item"
339                                    " not found: {0}").format(key))
340
341
342 # ____________________________________________________________
343 # multi types
344
345
346 class Multi(list):
347     """multi options values container
348     that support item notation for the values of multi options"""
349     __slots__ = ('opt', 'path', 'context')
350
351     def __init__(self, value, context, opt, path, validate=True):
352         """
353         :param value: the Multi wraps a list value
354         :param context: the home config that has the values
355         :param opt: the option object that have this Multi value
356         """
357         self.opt = opt
358         self.path = path
359         if not isinstance(context, weakref.ReferenceType):
360             raise ValueError('context must be a Weakref')
361         self.context = context
362         if not isinstance(value, list):
363             value = [value]
364         if validate and self.opt.impl_get_multitype() == multitypes.slave:
365             value = self._valid_slave(value)
366         elif self.opt.impl_get_multitype() == multitypes.master:
367             self._valid_master(value)
368         super(Multi, self).__init__(value)
369
370     def _valid_slave(self, value):
371         #if slave, had values until master's one
372         masterp = self.context().cfgimpl_get_description().impl_get_path_by_opt(
373             self.opt.impl_get_master_slaves())
374         mastervalue = getattr(self.context(), masterp)
375         masterlen = len(mastervalue)
376         valuelen = len(value)
377         if valuelen > masterlen or (valuelen < masterlen and
378                                     not self.context().cfgimpl_get_values(
379                                     )._is_default_owner(self.path)):
380             raise SlaveError(_("invalid len for the slave: {0}"
381                                " which has {1} as master").format(
382                                    self.opt._name, masterp))
383         elif valuelen < masterlen:
384             for num in range(0, masterlen - valuelen):
385                 value.append(self.opt.impl_getdefault_multi())
386         #else: same len so do nothing
387         return value
388
389     def _valid_master(self, value):
390         masterlen = len(value)
391         values = self.context().cfgimpl_get_values()
392         for slave in self.opt._master_slaves:
393             path = values._get_opt_path(slave)
394             if not values._is_default_owner(path):
395                 value_slave = values._getvalue(slave, path)
396                 if len(value_slave) > masterlen:
397                     raise SlaveError(_("invalid len for the master: {0}"
398                                        " which has {1} as slave with"
399                                        " greater len").format(
400                                            self.opt._name, slave._name))
401                 elif len(value_slave) < masterlen:
402                     for num in range(0, masterlen - len(value_slave)):
403                         value_slave.append(slave.impl_getdefault_multi(),
404                                            force=True)
405
406     def __setitem__(self, key, value):
407         self._validate(value)
408         #assume not checking mandatory property
409         super(Multi, self).__setitem__(key, value)
410         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
411
412     def append(self, value, force=False):
413         """the list value can be updated (appened)
414         only if the option is a master
415         """
416         if not force:
417             if self.opt.impl_get_multitype() == multitypes.slave:
418                 raise SlaveError(_("cannot append a value on a multi option {0}"
419                                    " which is a slave").format(self.opt._name))
420             elif self.opt.impl_get_multitype() == multitypes.master:
421                 values = self.context().cfgimpl_get_values()
422                 if value is None and self.opt.impl_has_callback():
423                     value = values._getcallback_value(self.opt)
424                     #Force None il return a list
425                     if isinstance(value, list):
426                         value = None
427         self._validate(value)
428         super(Multi, self).append(value)
429         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
430         if not force and self.opt.impl_get_multitype() == multitypes.master:
431             for slave in self.opt.impl_get_master_slaves():
432                 path = values._get_opt_path(slave)
433                 if not values._is_default_owner(path):
434                     if slave.impl_has_callback():
435                         index = self.__len__() - 1
436                         dvalue = values._getcallback_value(slave, index=index)
437                     else:
438                         dvalue = slave.impl_getdefault_multi()
439                     old_value = values.getitem(slave, path,
440                                                validate_properties=False)
441                     if len(old_value) < self.__len__():
442                         values.getitem(slave, path,
443                                        validate_properties=False).append(
444                                            dvalue, force=True)
445                     else:
446                         values.getitem(slave, path,
447                                        validate_properties=False)[
448                                            index] = dvalue
449
450     def sort(self, cmp=None, key=None, reverse=False):
451         if self.opt.impl_get_multitype() in [multitypes.slave,
452                                              multitypes.master]:
453             raise SlaveError(_("cannot sort multi option {0} if master or slave"
454                                "").format(self.opt._name))
455         if sys.version_info[0] >= 3:
456             if cmp is not None:
457                 raise ValueError(_('cmp is not permitted in python v3 or greater'))
458             super(Multi, self).sort(key=key, reverse=reverse)
459         else:
460             super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse)
461         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
462
463     def reverse(self):
464         if self.opt.impl_get_multitype() in [multitypes.slave,
465                                              multitypes.master]:
466             raise SlaveError(_("cannot reverse multi option {0} if master or "
467                                "slave").format(self.opt._name))
468         super(Multi, self).reverse()
469         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
470
471     def insert(self, index, obj):
472         if self.opt.impl_get_multitype() in [multitypes.slave,
473                                              multitypes.master]:
474             raise SlaveError(_("cannot insert multi option {0} if master or "
475                                "slave").format(self.opt._name))
476         super(Multi, self).insert(index, obj)
477         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
478
479     def extend(self, iterable):
480         if self.opt.impl_get_multitype() in [multitypes.slave,
481                                              multitypes.master]:
482             raise SlaveError(_("cannot extend multi option {0} if master or "
483                                "slave").format(self.opt._name))
484         super(Multi, self).extend(iterable)
485         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
486
487     def _validate(self, value):
488         if value is not None:
489             try:
490                 self.opt._validate(value)
491             except ValueError as err:
492                 raise ValueError(_("invalid value {0} "
493                                    "for option {1}: {2}"
494                                    "").format(str(value),
495                                               self.opt._name, err))
496
497     def pop(self, key, force=False):
498         """the list value can be updated (poped)
499         only if the option is a master
500
501         :param key: index of the element to pop
502         :return: the requested element
503         """
504         if not force:
505             if self.opt.impl_get_multitype() == multitypes.slave:
506                 raise SlaveError(_("cannot pop a value on a multi option {0}"
507                                    " which is a slave").format(self.opt._name))
508             elif self.opt.impl_get_multitype() == multitypes.master:
509                 for slave in self.opt.impl_get_master_slaves():
510                     values = self.context().cfgimpl_get_values()
511                     if not values.is_default_owner(slave):
512                         #get multi without valid properties
513                         values.getitem(slave,
514                                        validate_properties=False
515                                        ).pop(key, force=True)
516         #set value without valid properties
517         ret = super(Multi, self).pop(key)
518         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
519         return ret