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