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