* to "reset" a value, now you just have to delete it
[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 tiramisu.error import ConfigError
22 from tiramisu.setting import owners, multitypes, expires_time
23 from tiramisu.autolib import carry_out_calculation
24 from tiramisu.i18n import _
25
26
27 class ExpirValues(object):
28     __slots__ = ('_cache')
29
30     def __init__(self):
31         self._cache = {}
32
33     def __getitem__(self, opt):
34         return self.getitem(opt)
35
36     def getitem(self, opt, validate=True, force_permissive=False,
37                 force_properties=None):
38         if opt in self._cache:
39             t = time()
40             value, created = self._cache[opt]
41             if t - created < expires_time:
42                 return value
43         val = self._getitem(opt, validate, force_permissive, force_properties)
44         self._set_cache(opt, val)
45         return val
46
47     def __setitem__(self, opt, value):
48         self.setitem(opt, value)
49
50     def setitem(self, opt, value, force_permissive=False):
51         self._setitem(opt, value, force_permissive)
52
53     def __delitem__(self, opt):
54         self._reset(opt)
55
56     def _set_cache(self, opt, val):
57         if self.context.cfgimpl_get_settings().has_property('expire'):
58             self._cache[opt] = (val, time())
59
60     def reset_cache(self):
61         self._cache = {}
62
63
64 class Values(ExpirValues):
65     __slots__ = ('context', '_values')
66
67     def __init__(self, context):
68         """
69         Initializes the values's dict.
70
71         :param context: the context is the home config's values
72         """
73         "Config's root indeed is in charge of the `Option()`'s values"
74         self.context = context
75         self._values = {}
76         super(Values, self).__init__()
77
78     def _get_value(self, opt):
79         "return value or default value if not set"
80         #if no value
81         if opt not in self._values:
82             value = opt.getdefault()
83             if opt.is_multi():
84                 value = Multi(value, self.context, opt)
85                 #if slave, had values until master's one
86                 if opt.get_multitype() == multitypes.slave:
87                     masterpath = self.context.cfgimpl_get_description().get_path_by_opt(opt.get_master_slaves())
88                     mastervalue = getattr(self.context, masterpath)
89                     masterlen = len(mastervalue)
90                     if len(value) > masterlen:
91                         raise ValueError(_("invalid len for the slave: {0}"
92                                            " which has {1} as master").format(
93                                                opt._name, masterpath))
94                     if len(value) < masterlen:
95                         for num in range(0, masterlen - len(value)):
96                             value.append(opt.getdefault_multi(), force=True)
97                     #FIXME si inferieur ??
98         else:
99             #if value
100             value = self._values[opt][1]
101         return value
102
103     def _reset(self, opt):
104         if opt in self._values:
105             self.context.cfgimpl_clean_cache()
106             del(self._values[opt])
107
108     def _is_empty(self, opt, value):
109         "convenience method to know if an option is empty"
110         empty = opt._empty
111         if (not opt.is_multi() and (value is None or value == empty)) or \
112            (opt.is_multi() and (value == [] or
113                                 None in value or empty in value)):
114             return True
115         if self.is_default_owner(opt) and opt.is_empty_by_default():
116             return True
117         return False
118
119     def is_mandatory_err(self, opt, value, force_properties=None):
120         setting = self.context.cfgimpl_get_settings()
121         set_mandatory = setting.has_property('mandatory')
122         if force_properties is not None:
123             set_mandatory = ('mandatory' in force_properties or
124                              set_mandatory)
125         if set_mandatory and setting.has_property('mandatory', opt, False) and \
126                 self._is_empty(opt, value):
127             return True
128         return False
129
130     def fill_multi(self, opt, result):
131         """fills a multi option with default and calculated values
132         """
133         if not isinstance(result, list):
134             _result = [result]
135         else:
136             _result = result
137         return Multi(_result, self.context, opt)  # , multitype)
138
139     def _getcallback_value(self, opt):
140         callback, callback_params = opt._callback
141         if callback_params is None:
142             callback_params = {}
143         return carry_out_calculation(opt._name, config=self.context,
144                                      callback=callback,
145                                      callback_params=callback_params)
146
147     def _getitem(self, opt, validate, force_permissive, force_properties):
148         # options with callbacks
149         setting = self.context.cfgimpl_get_settings()
150         value = self._get_value(opt)
151         is_frozen = setting.has_property('frozen', opt, False)
152         if opt.has_callback():
153             #if value is set and :
154             # - not frozen
155             # - frozen and not force_default_on_freeze
156             if not self.is_default_owner(opt) and (
157                     not is_frozen or (is_frozen and
158                                       not setting.has_property('force_default_on_freeze', opt, False))):
159                 pass
160             else:
161                 value = self._getcallback_value(opt)
162                 if opt.is_multi():
163                     value = self.fill_multi(opt, value)
164                 #suppress value if already set
165                 self._reset(opt)
166         # frozen and force default
167         elif is_frozen and setting.has_property('force_default_on_freeze', opt, False):
168             value = opt.getdefault()
169             if opt.is_multi():
170                 value = self.fill_multi(opt, value)
171         if validate and not opt.validate(value, self.context, setting.has_property('validator')):
172             raise ValueError(_('invalid calculated value returned'
173                              ' for option {0}: {1}').format(opt._name, value))
174         if self.is_default_owner(opt) and \
175                 setting.has_property('force_store_value', opt, False):
176             self.setitem(opt, value)
177         setting.validate_properties(opt, False, False, value=value,
178                                     force_permissive=force_permissive,
179                                     force_properties=force_properties)
180         return value
181
182     def _setitem(self, opt, value, force_permissive=False, force_properties=None):
183         #valid opt
184         if not opt.validate(value, self.context,
185                             self.context.cfgimpl_get_settings().has_property('validator')):
186             raise ValueError(_('invalid value {}'
187                              ' for option {}').format(value, opt._name))
188         if opt.is_multi():
189             if opt.get_multitype() == multitypes.master:
190                 masterlen = len(value)
191                 for slave in opt.master_slaves:
192                     value_slave = self._get_value(slave)
193                     if len(value_slave) > masterlen:
194                         raise ValueError(_("invalid len for the slave: {0}"
195                                            " which has {1} as master").format(
196                                                slave._name, opt._name))
197                     elif len(value_slave) < masterlen:
198                         for num in range(0, masterlen - len(value_slave)):
199                             value_slave.append(slave.getdefault_multi(), force=True)
200
201             elif opt.get_multitype() == multitypes.slave:
202                 if len(self._get_value(opt.master_slaves)) != len(value):
203                     raise ValueError(_("invalid len for the slave: {0}"
204                                        " which has {1} as master").format(
205                                            opt._name, opt.master_slaves._name))
206             if not isinstance(value, Multi):
207                 value = Multi(value, self.context, opt)
208         if type(value) == list:
209             raise ValueError(_("the type of the value {0} which is multi shall "
210                                "be Multi and not list").format(str(value)))
211         self._setvalue(opt, value, force_permissive=force_permissive,
212                        force_properties=force_properties)
213
214     def _setvalue(self, opt, value, force_permissive=False, force_properties=None):
215         self.context.cfgimpl_clean_cache()
216         setting = self.context.cfgimpl_get_settings()
217         setting.validate_properties(opt, False, True,
218                                     value=value,
219                                     force_permissive=force_permissive,
220                                     force_properties=force_properties)
221         self._values[opt] = (setting.getowner(), value)
222
223     def getowner(self, opt):
224         return self._values.get(opt, (owners.default, None))[0]
225
226     def setowner(self, opt, owner):
227         if opt not in self._values:
228             raise ConfigError(_('no value for {1} cannot change owner to {2}').format(opt))
229         if not isinstance(owner, owners.Owner):
230             raise TypeError(_("invalid generic owner {0}").format(str(owner)))
231         self._values[opt] = (owner, self._values[opt][1])
232
233     def is_default_owner(self, opt):
234         """
235         :param config: *must* be only the **parent** config
236                        (not the toplevel config)
237         :return: boolean
238         """
239         return self.getowner(opt) == owners.default
240
241     def __contains__(self, opt):
242         return opt in self._values
243
244 # ____________________________________________________________
245 # multi types
246
247
248 class Multi(list):
249     """multi options values container
250     that support item notation for the values of multi options"""
251     __slots__ = ('opt', 'context')
252
253     def __init__(self, lst, context, opt):
254         """
255         :param lst: the Multi wraps a list value
256         :param context: the home config that has the values
257         :param opt: the option object that have this Multi value
258         """
259         self.opt = opt
260         self.context = context
261         super(Multi, self).__init__(lst)
262
263     def __setitem__(self, key, value):
264         self._validate(value)
265         #assume not checking mandatory property
266         self.context.cfgimpl_get_values()._setvalue(self.opt, self)
267         super(Multi, self).__setitem__(key, value)
268
269     def append(self, value, force=False):
270         """the list value can be updated (appened)
271         only if the option is a master
272         """
273         if not force:
274             if self.opt.get_multitype() == multitypes.slave:
275                 raise ValueError(_("cannot append a value on a multi option {0}"
276                                    " which is a slave").format(self.opt._name))
277             elif self.opt.get_multitype() == multitypes.master:
278                 for slave in self.opt.get_master_slaves():
279                     self.context.cfgimpl_get_values()[slave].append(
280                         slave.getdefault_multi(), force=True)
281         self._validate(value)
282         #assume not checking mandatory property
283         self.context.cfgimpl_get_values()._setvalue(self.opt, self)
284         super(Multi, self).append(value)
285
286     def _validate(self, value):
287         if value is not None and not self.opt._validate(value):
288             raise ValueError(_("invalid value {0} "
289                              "for option {1}").format(str(value),
290                                                       self.opt._name))
291
292     def pop(self, key, force=False):
293         """the list value can be updated (poped)
294         only if the option is a master
295
296         :param key: index of the element to pop
297         :return: the requested element
298         """
299         if not force:
300             if self.opt.multitype == multitypes.slave:
301                 raise ValueError(_("cannot append a value on a multi option {0}"
302                                    " which is a slave").format(self.opt._name))
303             elif self.opt.multitype == multitypes.master:
304                 for slave in self.opt.get_master_slaves():
305                     self.context.cfgimpl_get_values()[slave].pop(key, force=True)
306         self.context.cfgimpl_get_values()._setvalue(self.opt, self)
307         return super(Multi, self).pop(key)