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