test force_default_on_freeze with multi and correction in Multi()
[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 # ____________________________________________________________
319 # multi types
320
321
322 class Multi(list):
323     """multi options values container
324     that support item notation for the values of multi options"""
325     __slots__ = ('opt', 'path', 'context')
326
327     def __init__(self, value, context, opt, path, validate=True):
328         """
329         :param value: the Multi wraps a list value
330         :param context: the home config that has the values
331         :param opt: the option object that have this Multi value
332         """
333         self.opt = opt
334         self.path = path
335         if not isinstance(context, weakref.ReferenceType):
336             raise ValueError('context must be a Weakref')
337         self.context = context
338         if not isinstance(value, list):
339             value = [value]
340         if validate and self.opt.impl_get_multitype() == multitypes.slave:
341             value = self._valid_slave(value)
342         elif self.opt.impl_get_multitype() == multitypes.master:
343             self._valid_master(value)
344         super(Multi, self).__init__(value)
345
346     def _valid_slave(self, value):
347         #if slave, had values until master's one
348         masterp = self.context().cfgimpl_get_description().impl_get_path_by_opt(
349             self.opt.impl_get_master_slaves())
350         mastervalue = getattr(self.context(), masterp)
351         masterlen = len(mastervalue)
352         valuelen = len(value)
353         if valuelen > masterlen or (valuelen < masterlen and
354                                     not self.context().cfgimpl_get_values(
355                                     )._is_default_owner(self.path)):
356             raise SlaveError(_("invalid len for the slave: {0}"
357                                " which has {1} as master").format(
358                                    self.opt._name, masterp))
359         elif valuelen < masterlen:
360             for num in range(0, masterlen - valuelen):
361                 value.append(self.opt.impl_getdefault_multi())
362         #else: same len so do nothing
363         return value
364
365     def _valid_master(self, value):
366         masterlen = len(value)
367         values = self.context().cfgimpl_get_values()
368         for slave in self.opt._master_slaves:
369             path = values._get_opt_path(slave)
370             if not values._is_default_owner(path):
371                 value_slave = values._getvalue(slave, path)
372                 if len(value_slave) > masterlen:
373                     raise SlaveError(_("invalid len for the master: {0}"
374                                        " which has {1} as slave with"
375                                        " greater len").format(
376                                            self.opt._name, slave._name))
377                 elif len(value_slave) < masterlen:
378                     for num in range(0, masterlen - len(value_slave)):
379                         value_slave.append(slave.impl_getdefault_multi(),
380                                            force=True)
381
382     def __setitem__(self, key, value):
383         self._validate(value)
384         #assume not checking mandatory property
385         super(Multi, self).__setitem__(key, value)
386         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
387
388     def append(self, value, force=False):
389         """the list value can be updated (appened)
390         only if the option is a master
391         """
392         if not force:
393             if self.opt.impl_get_multitype() == multitypes.slave:
394                 raise SlaveError(_("cannot append a value on a multi option {0}"
395                                    " which is a slave").format(self.opt._name))
396             elif self.opt.impl_get_multitype() == multitypes.master:
397                 values = self.context().cfgimpl_get_values()
398                 if value is None and self.opt.impl_has_callback():
399                     value = values._getcallback_value(self.opt)
400                     #Force None il return a list
401                     if isinstance(value, list):
402                         value = None
403         self._validate(value)
404         super(Multi, self).append(value)
405         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
406         if not force and self.opt.impl_get_multitype() == multitypes.master:
407             for slave in self.opt.impl_get_master_slaves():
408                 path = values._get_opt_path(slave)
409                 if not values._is_default_owner(path):
410                     if slave.impl_has_callback():
411                         index = self.__len__() - 1
412                         dvalue = values._getcallback_value(slave, index=index)
413                     else:
414                         dvalue = slave.impl_getdefault_multi()
415                     old_value = values.getitem(slave, path,
416                                                validate_properties=False)
417                     if len(old_value) < self.__len__():
418                         values.getitem(slave, path,
419                                        validate_properties=False).append(
420                                            dvalue, force=True)
421                     else:
422                         values.getitem(slave, path,
423                                        validate_properties=False)[
424                                            index] = dvalue
425
426     def sort(self, cmp=None, key=None, reverse=False):
427         if self.opt.impl_get_multitype() in [multitypes.slave,
428                                              multitypes.master]:
429             raise SlaveError(_("cannot sort multi option {0} if master or slave"
430                                "").format(self.opt._name))
431         if sys.version_info[0] >= 3:
432             if cmp is not None:
433                 raise ValueError(_('cmp is not permitted in python v3 or greater'))
434             super(Multi, self).sort(key=key, reverse=reverse)
435         else:
436             super(Multi, self).sort(cmp=cmp, key=key, reverse=reverse)
437         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
438
439     def reverse(self):
440         if self.opt.impl_get_multitype() in [multitypes.slave,
441                                              multitypes.master]:
442             raise SlaveError(_("cannot reverse multi option {0} if master or "
443                                "slave").format(self.opt._name))
444         super(Multi, self).reverse()
445         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
446
447     def insert(self, index, obj):
448         if self.opt.impl_get_multitype() in [multitypes.slave,
449                                              multitypes.master]:
450             raise SlaveError(_("cannot insert multi option {0} if master or "
451                                "slave").format(self.opt._name))
452         super(Multi, self).insert(index, obj)
453         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
454
455     def extend(self, iterable):
456         if self.opt.impl_get_multitype() in [multitypes.slave,
457                                              multitypes.master]:
458             raise SlaveError(_("cannot extend multi option {0} if master or "
459                                "slave").format(self.opt._name))
460         super(Multi, self).extend(iterable)
461         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self)
462
463     def _validate(self, value):
464         if value is not None:
465             try:
466                 self.opt._validate(value)
467             except ValueError as err:
468                 raise ValueError(_("invalid value {0} "
469                                    "for option {1}: {2}"
470                                    "").format(str(value),
471                                               self.opt._name, err))
472
473     def pop(self, key, force=False):
474         """the list value can be updated (poped)
475         only if the option is a master
476
477         :param key: index of the element to pop
478         :return: the requested element
479         """
480         if not force:
481             if self.opt.impl_get_multitype() == multitypes.slave:
482                 raise SlaveError(_("cannot pop a value on a multi option {0}"
483                                    " which is a slave").format(self.opt._name))
484             elif self.opt.impl_get_multitype() == multitypes.master:
485                 for slave in self.opt.impl_get_master_slaves():
486                     values = self.context().cfgimpl_get_values()
487                     if not values.is_default_owner(slave):
488                         #get multi without valid properties
489                         values.getitem(slave,
490                                        validate_properties=False
491                                        ).pop(key, force=True)
492         #set value without valid properties
493         ret = super(Multi, self).pop(key)
494         self.context().cfgimpl_get_values()._setvalue(self.opt, self.path, self, validate_properties=not force)
495         return ret