Values validate now value
[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 MandatoryError, MultiTypeError, \
21     ConfigError  # , OptionValueError
22 from tiramisu.setting import owners, multitypes
23 from tiramisu.autolib import carry_out_calculation
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.multitype == multitypes.slave:
48                     masterpath = self.context.cfgimpl_get_description().get_path_by_opt(opt.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         #FIXME: buggy ?
71         #if value is not None:
72         #    return False
73         if (not opt.is_multi() and value is None) or \
74            (opt.is_multi() and (value == [] or
75                                 None in self._get_value(opt))):
76             return True
77         return False
78
79     def _test_mandatory(self, opt, value, force_properties=None):
80         setting = self.context.cfgimpl_get_settings()
81         if force_properties is None:
82             set_mandatory = setting.has_property('mandatory')
83         else:
84             set_mandatory = ('mandatory' in force_properties or
85                              setting.has_property('mandatory'))
86         if setting.has_property('mandatory', opt, False) and set_mandatory:
87             if self._is_empty(opt, value) and opt.is_empty_by_default():
88                 raise MandatoryError("option: {0} is mandatory "
89                                      "and shall have a value".format(opt._name))
90             #empty value
91             if opt.is_multi():
92                 for val in value:
93                     if val == '':
94                         raise MandatoryError("option: {0} is mandatory "
95                                              "and shall have not empty value".format(opt._name))
96             else:
97                 if value == '':
98                     raise MandatoryError("option: {0} is mandatory "
99                                          "and shall have not empty value".format(opt._name))
100
101     def fill_multi(self, opt, result):
102         """fills a multi option with default and calculated values
103         """
104         if not isinstance(result, list):
105             _result = [result]
106         else:
107             _result = result
108         return Multi(_result, self.context, opt)  # , multitype)
109
110     def _getcallback_value(self, opt):
111         callback, callback_params = opt.callback
112         if callback_params is None:
113             callback_params = {}
114         return carry_out_calculation(opt._name, config=self.context,
115                                      callback=callback,
116                                      callback_params=callback_params)
117
118     def __getitem__(self, opt):
119         return self._getitem(opt)
120
121     def _getitem(self, opt, force_properties=None):
122         # options with callbacks
123         value = self._get_value(opt)
124         setting = self.context.cfgimpl_get_settings()
125         is_frozen = setting.has_property('frozen', opt, False)
126         if opt.has_callback():
127             #if value is set and :
128             # - not frozen
129             # - frozen and not force_default_on_freeze
130             if not self.is_default_owner(opt) and (
131                     not is_frozen or (is_frozen and
132                                       not setting.has_property('force_default_on_freeze', opt, False))):
133                 return value
134             value = self._getcallback_value(opt)
135             if opt.is_multi():
136                 value = self.fill_multi(opt, value)
137             if not opt.validate(value, setting.has_property('validator')):
138                 raise ConfigError('invalid calculated value returned'
139                                   ' for option {0}: {1}'.format(opt._name, value))
140             #suppress value if already set
141             self.reset(opt)
142         # frozen and force default
143         elif is_frozen and setting.has_property('force_default_on_freeze', opt, False):
144             value = opt.getdefault()
145             if opt.is_multi():
146                 value = self.fill_multi(opt, value)
147         self._test_mandatory(opt, value, force_properties)
148         return value
149
150     def __setitem__(self, opt, value):
151         if not opt.validate(value,
152                 self.context.cfgimpl_get_settings().has_property('validator')):
153             raise ConfigError('invalid value {}'
154                     ' for option {}'.format(value, opt._name))
155         if opt.is_multi():
156             if opt.multitype == multitypes.master:
157                 masterlen = len(value)
158                 for slave in opt.master_slaves:
159                     value_slave = self._get_value(slave)
160                     if len(value_slave) > masterlen:
161                         raise MultiTypeError("invalid len for the slave: {0}"
162                                              " which has {1} as master".format(
163                                                  slave._name, opt._name))
164                     elif len(value_slave) < masterlen:
165                         for num in range(0, masterlen - len(value_slave)):
166                             value_slave.append(slave.getdefault_multi(), force=True)
167
168             elif opt.multitype == multitypes.slave:
169                 if len(self._get_value(opt.master_slaves)) != len(value):
170                     raise MultiTypeError("invalid len for the slave: {0}"
171                                          " which has {1} as master".format(
172                                              opt._name, opt.master_slaves._name))
173             if not isinstance(value, Multi):
174                 value = Multi(value, self.context, opt)
175         self.setitem(opt, value)
176
177     def setitem(self, opt, value):
178         if type(value) == list:
179             raise MultiTypeError("the type of the value {0} which is multi shall "
180                                  "be Multi and not list".format(str(value)))
181         self._test_mandatory(opt, value)
182         self.values[opt] = (self.context.cfgimpl_get_settings().getowner(), value)
183
184     def __contains__(self, opt):
185         return opt in self.values
186
187     def getowner(self, opt):
188         return self.values.get(opt, (owners.default, None))[0]
189
190     def setowner(self, opt, owner):
191         if opt not in self.values:
192             raise ConfigError('no value for {} cannot change owner to {}'.format(opt))
193         if not isinstance(owner, owners.Owner):
194             raise TypeError("invalid generic owner {0}".format(str(owner)))
195         self.values[opt] = (owner, self.values[opt][1])
196
197     def is_default_owner(self, opt):
198         """
199         :param config: *must* be only the **parent** config
200                        (not the toplevel config)
201         :return: boolean
202         """
203         return self.getowner(opt) == owners.default
204
205 # ____________________________________________________________
206 # multi types
207
208
209 class Multi(list):
210     """multi options values container
211     that support item notation for the values of multi options"""
212     __slots__ = ('opt', 'context')
213
214     def __init__(self, lst, context, opt):
215         """
216         :param lst: the Multi wraps a list value
217         :param context: the home config that has the values
218         :param opt: the option object that have this Multi value
219         """
220         self.opt = opt
221         self.context = context
222         super(Multi, self).__init__(lst)
223
224     def __setitem__(self, key, value):
225         self._validate(value)
226         self.context.cfgimpl_get_values()[self.opt] = self
227         super(Multi, self).__setitem__(key, value)
228
229     def append(self, value, force=False):
230         """the list value can be updated (appened)
231         only if the option is a master
232         """
233         if not force:
234             if self.opt.multitype == multitypes.slave:
235                 raise MultiTypeError("cannot append a value on a multi option {0}"
236                                      " which is a slave".format(self.opt._name))
237             elif self.opt.multitype == multitypes.master:
238                 for slave in self.opt.master_slaves:
239                     self.context.cfgimpl_get_values()[slave].append(slave.getdefault_multi(), force=True)
240         self._validate(value)
241         self.context.cfgimpl_get_values().setitem(self.opt, self)
242         super(Multi, self).append(value)
243
244     def _validate(self, value):
245         if value is not None and not self.opt._validate(value):
246             raise ConfigError("invalid value {0} "
247                               "for option {1}".format(str(value),
248                                                       self.opt._name))
249
250     def pop(self, key, force=False):
251         """the list value can be updated (poped)
252         only if the option is a master
253
254         :param key: index of the element to pop
255         :return: the requested element
256         """
257         if not force:
258             if self.opt.multitype == multitypes.slave:
259                 raise MultiTypeError("cannot append a value on a multi option {0}"
260                                      " which is a slave".format(self.opt._name))
261             elif self.opt.multitype == multitypes.master:
262                 for slave in self.opt.master_slaves:
263                     self.context.cfgimpl_get_values()[slave].pop(key, force=True)
264         self.context.cfgimpl_get_values().setitem(self.opt, self)
265         return super(Multi, self).pop(key)