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