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