support slave with list has calculated value
[tiramisu.git] / tiramisu / autolib.py
1 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
2 #
3 # This program is free software: you can redistribute it and/or modify it
4 # under the terms of the GNU Lesser General Public License as published by the
5 # Free Software Foundation, either version 3 of the License, or (at your
6 # option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but WITHOUT
9 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
11 # details.
12 #
13 # You should have received a copy of the GNU Lesser General Public License
14 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
15 #
16 # The original `Config` design model is unproudly borrowed from
17 # the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
18 # the whole pypy projet is under MIT licence
19 # ____________________________________________________________
20 "enables us to carry out a calculation and return an option's value"
21 from .error import PropertiesOptionError, ConfigError, SlaveError
22 from .i18n import _
23 from .setting import undefined
24 # ____________________________________________________________
25
26
27 def carry_out_calculation(option, context, callback, callback_params,
28                           index=undefined, returns_raise=False):
29     """a function that carries out a calculation for an option's value
30
31     :param option: the option
32     :param context: the context config in order to have
33                    the whole options available
34     :param callback: the name of the callback function
35     :type callback: str
36     :param callback_params: the callback's parameters
37                             (only keyword parameters are allowed)
38     :type callback_params: dict
39     :param index: if an option is multi, only calculates the nth value
40     :type index: int
41
42     The callback_params is a dict. Key is used to build args (if key is '')
43     and kwargs (otherwise). Values are tuple of:
44     - values
45     - tuple with option and boolean's force_permissive (True when don't raise
46     if PropertiesOptionError)
47     Values could have multiple values only when key is ''.
48
49     * if no callback_params:
50       => calculate(<function func at 0x2092320>, [], {})
51
52     * if callback_params={'': ('yes',)}
53       => calculate(<function func at 0x2092320>, ['yes'], {})
54
55     * if callback_params={'value': ('yes',)}
56       => calculate(<function func at 0x165b320>, [], {'value': 'yes'})
57
58     * if callback_params={'': ('yes', 'no')}
59       => calculate('yes', 'no')
60
61     * if callback_params={'value': ('yes', 'no')}
62       => ValueError()
63
64     * if callback_params={'': (['yes', 'no'],)}
65       => calculate(<function func at 0x176b320>, ['yes', 'no'], {})
66
67     * if callback_params={'value': ('yes', 'no')}
68       => raises ValueError()
69
70     * if callback_params={'': ((opt1, False),)}
71
72        - a simple option:
73          opt1 == 11
74          => calculate(<function func at 0x1cea320>, [11], {})
75
76        - a multi option and not master/slave:
77          opt1 == [1, 2, 3]
78          => calculate(<function func at 0x223c320>, [[1, 2, 3]], {})
79
80        - option is master or slave of opt1:
81          opt1 == [1, 2, 3]
82          => calculate(<function func at 0x223c320>, [1], {})
83          => calculate(<function func at 0x223c320>, [2], {})
84          => calculate(<function func at 0x223c320>, [3], {})
85
86       - opt is a master or slave but not related to option:
87         opt1 == [1, 2, 3]
88         => calculate(<function func at 0x11b0320>, [[1, 2, 3]], {})
89
90     * if callback_params={'value': ((opt1, False),)}
91
92        - a simple option:
93          opt1 == 11
94          => calculate(<function func at 0x17ff320>, [], {'value': 11})
95
96        - a multi option:
97          opt1 == [1, 2, 3]
98          => calculate(<function func at 0x1262320>, [], {'value': [1, 2, 3]})
99
100     * if callback_params={'': ((opt1, False), (opt2, False))}
101
102       - two single options
103           opt1 = 11
104           opt2 = 12
105           => calculate(<function func at 0x217a320>, [11, 12], {})
106
107       - a multi option with a simple option
108           opt1 == [1, 2, 3]
109           opt2 == 12
110           => calculate(<function func at 0x2153320>, [[1, 2, 3], 12], {})
111
112       - a multi option with an other multi option but with same length
113           opt1 == [1, 2, 3]
114           opt2 == [11, 12, 13]
115           => calculate(<function func at 0x1981320>, [[1, 2, 3], [11, 12, 13]], {})
116
117       - a multi option with an other multi option but with different length
118           opt1 == [1, 2, 3]
119           opt2 == [11, 12]
120           => calculate(<function func at 0x2384320>, [[1, 2, 3], [11, 12]], {})
121
122       - a multi option without value with a simple option
123           opt1 == []
124           opt2 == 11
125           => calculate(<function func at 0xb65320>, [[], 12], {})
126
127     * if callback_params={'value': ((opt1, False), (opt2, False))}
128       => raises ValueError()
129
130     If index is not undefined, return a value, otherwise return:
131
132     * a list if one parameters have multi option
133     * a value otherwise
134
135     If calculate return list, this list is extend to return value.
136     """
137     tcparams = {}
138     # if callback_params has a callback, launch several time calculate()
139     master_slave = False
140     has_option = False
141     # multi's option should have same value for all option
142     if option._is_subdyn():
143         tcparams['suffix'] = [(option.impl_getsuffix(), False)]
144     for key, callbacks in callback_params.items():
145         for callbk in callbacks:
146             if isinstance(callbk, tuple):
147                 if context is undefined:
148                     return undefined
149                 if callbk[0] is None:  # pragma: optional cover
150                     #Not an option, set full context
151                     tcparams.setdefault(key, []).append((context, False))
152                 else:
153                     # callbk is something link (opt, True|False)
154                     opt, force_permissive = callbk
155                     if opt._is_subdyn():
156                         root = '.'.join(option.impl_getpath(context).split('.')[:-1])
157                         name = opt.impl_getname() + option.impl_getsuffix()
158                         path = root + '.' + name
159                         opt = opt._impl_to_dyn(name, path)
160                     else:
161                         path = context.cfgimpl_get_description(
162                         ).impl_get_path_by_opt(opt)
163                     # get value
164                     value = context.getattr(path, force_permissive=True,
165                                             validate=False,
166                                             returns_raise=True)
167                     if isinstance(value, Exception):
168                         if isinstance(value, PropertiesOptionError):
169                             if force_permissive:
170                                 continue
171                             err = ConfigError(_('unable to carry out a calculation'
172                                                 ', option {0} has properties: {1} '
173                                                 'for: {2}').format(opt.impl_getname(),
174                                                                    value.proptype,
175                                                                    option.impl_getname()))
176                             if returns_raise:
177                                 return err
178                             else:
179                                 raise err
180                         else:
181                             raise value
182                     # convert to list, not modifie this multi
183                     if value.__class__.__name__ == 'Multi':
184                         has_option = True
185                         value = list(value)
186
187                     if opt.impl_is_master_slaves() and \
188                             opt.impl_get_master_slaves().in_same_group(option):
189                         master_slave = True
190                         is_multi = True
191                     else:
192                         is_multi = False
193                     tcparams.setdefault(key, []).append((value, is_multi))
194             else:
195                 # callbk is a value and not a multi
196                 tcparams.setdefault(key, []).append((callbk, False))
197
198     # if one value is a multi, launch several time calculate
199     # if index is set, return a value
200     # if no index, return a list
201     if master_slave:
202         ret = []
203         args = []
204         kwargs = {}
205         for key, couples in tcparams.items():
206             for couple in couples:
207                 value, ismulti = couple
208                 if ismulti:
209                     val = value[index]
210                 else:
211                     val = value
212                 if key == '':
213                     args.append(val)
214                 else:
215                     kwargs[key] = val
216         return calculate(callback, args, kwargs, returns_raise)
217     else:
218         # no value is multi
219         # return a single value
220         args = []
221         kwargs = {}
222         for key, couples in tcparams.items():
223             for couple in couples:
224                 # couple[1] (ismulti) is always False
225                 if key == '':
226                     args.append(couple[0])
227                 else:
228                     kwargs[key] = couple[0]
229         ret = calculate(callback, args, kwargs, returns_raise)
230         if not option.impl_is_optiondescription() and callback_params != {} and isinstance(ret, list) and \
231                 option.impl_is_master_slaves('slave'):
232             if not has_option and index not in [None, undefined]:
233                 if index < len(ret):
234                     ret = ret[index]
235                 else:
236                     #FIXME really?
237                     ret = None
238             else:
239                 raise SlaveError(_("callback cannot return a list for a "
240                                    "slave option ({0})").format(option.impl_getname()))
241         return ret
242
243
244 def calculate(callback, args, kwargs, returns_raise):
245     """wrapper that launches the 'callback'
246
247     :param callback: callback function
248     :param args: in the callback's arity, the unnamed parameters
249     :param kwargs: in the callback's arity, the named parameters
250
251     """
252     if returns_raise:
253         try:
254             return callback(*args, **kwargs)
255         except ValueError as err:
256             return err
257     else:
258         return callback(*args, **kwargs)