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