python 3.5 support
[tiramisu.git] / tiramisu / autolib.py
1 # Copyright (C) 2012-2017 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, validate=True, is_validator=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     :param is_validator: to know if carry_out_calculation is used to validate a value
42
43     The callback_params is a dict. Key is used to build args (if key is '')
44     and kwargs (otherwise). Values are tuple of:
45     - values
46     - tuple with option and boolean's force_permissive (True when don't raise
47     if PropertiesOptionError)
48     Values could have multiple values only when key is ''.
49
50     * if no callback_params:
51       => calculate(<function func at 0x2092320>, [], {})
52
53     * if callback_params={'': ('yes',)}
54       => calculate(<function func at 0x2092320>, ['yes'], {})
55
56     * if callback_params={'value': ('yes',)}
57       => calculate(<function func at 0x165b320>, [], {'value': 'yes'})
58
59     * if callback_params={'': ('yes', 'no')}
60       => calculate('yes', 'no')
61
62     * if callback_params={'value': ('yes', 'no')}
63       => ValueError()
64
65     * if callback_params={'': (['yes', 'no'],)}
66       => calculate(<function func at 0x176b320>, ['yes', 'no'], {})
67
68     * if callback_params={'value': ('yes', 'no')}
69       => raises ValueError()
70
71     * if callback_params={'': ((opt1, False),)}
72
73        - a simple option:
74          opt1 == 11
75          => calculate(<function func at 0x1cea320>, [11], {})
76
77        - a multi option and not master/slave:
78          opt1 == [1, 2, 3]
79          => calculate(<function func at 0x223c320>, [[1, 2, 3]], {})
80
81        - option is master or slave of opt1:
82          opt1 == [1, 2, 3]
83          => calculate(<function func at 0x223c320>, [1], {})
84          => calculate(<function func at 0x223c320>, [2], {})
85          => calculate(<function func at 0x223c320>, [3], {})
86
87       - opt is a master or slave but not related to option:
88         opt1 == [1, 2, 3]
89         => calculate(<function func at 0x11b0320>, [[1, 2, 3]], {})
90
91     * if callback_params={'value': ((opt1, False),)}
92
93        - a simple option:
94          opt1 == 11
95          => calculate(<function func at 0x17ff320>, [], {'value': 11})
96
97        - a multi option:
98          opt1 == [1, 2, 3]
99          => calculate(<function func at 0x1262320>, [], {'value': [1, 2, 3]})
100
101     * if callback_params={'': ((opt1, False), (opt2, False))}
102
103       - two single options
104           opt1 = 11
105           opt2 = 12
106           => calculate(<function func at 0x217a320>, [11, 12], {})
107
108       - a multi option with a simple option
109           opt1 == [1, 2, 3]
110           opt2 == 12
111           => calculate(<function func at 0x2153320>, [[1, 2, 3], 12], {})
112
113       - a multi option with an other multi option but with same length
114           opt1 == [1, 2, 3]
115           opt2 == [11, 12, 13]
116           => calculate(<function func at 0x1981320>, [[1, 2, 3], [11, 12, 13]], {})
117
118       - a multi option with an other multi option but with different length
119           opt1 == [1, 2, 3]
120           opt2 == [11, 12]
121           => calculate(<function func at 0x2384320>, [[1, 2, 3], [11, 12]], {})
122
123       - a multi option without value with a simple option
124           opt1 == []
125           opt2 == 11
126           => calculate(<function func at 0xb65320>, [[], 12], {})
127
128     * if callback_params={'value': ((opt1, False), (opt2, False))}
129       => raises ValueError()
130
131     If index is not undefined, return a value, otherwise return:
132
133     * a list if one parameters have multi option
134     * a value otherwise
135
136     If calculate return list, this list is extend to return value.
137     """
138     tcparams = {}
139     # if callback_params has a callback, launch several time calculate()
140     master_slave = False
141     has_option = False
142     # multi's option should have same value for all option
143     if option._is_subdyn():
144         tcparams['suffix'] = [(option.impl_getsuffix(), False)]
145     for key, callbacks in callback_params.items():
146         for callbk in callbacks:
147             if isinstance(callbk, tuple):
148                 if context is undefined:
149                     return undefined
150                 if callbk[0] is None:  # pragma: optional cover
151                     #Not an option, set full context
152                     tcparams.setdefault(key, []).append((context.duplicate(), False))
153                 elif callbk[0] == 'index':
154                     tcparams.setdefault(key, []).append((index, False))
155                 else:
156                     # callbk is something link (opt, True|False)
157                     opt, force_permissive = callbk
158                     if opt._is_subdyn():
159                         root = '.'.join(option.impl_getpath(context).split('.')[:-1])
160                         name = opt.impl_getname() + option.impl_getsuffix()
161                         path = root + '.' + name
162                         opt = opt._impl_to_dyn(name, path)
163                     else:
164                         path = context.cfgimpl_get_description(
165                         ).impl_get_path_by_opt(opt)
166                     # don't validate if option is option that we tried to validate
167                     if opt == option:
168                         valid = False
169                     else:
170                         valid = validate
171                     # get value
172                     value = context.getattr(path, force_permissive=True,
173                                             validate=valid, returns_raise=True)
174                     if isinstance(value, Exception):
175                         if isinstance(value, PropertiesOptionError):
176                             if force_permissive:
177                                 continue
178                             err = ConfigError(_('unable to carry out a calculation'
179                                                 ', option {0} has properties: {1} '
180                                                 'for: {2}').format(opt.impl_getname(),
181                                                                    value.proptype,
182                                                                    option.impl_getname()))
183                             return err
184                         else:
185                             raise value
186                     # convert to list, not modifie this multi
187                     if value.__class__.__name__ == 'Multi':
188                         has_option = True
189                         value = list(value)
190
191                     if opt != option and opt.impl_is_master_slaves() and \
192                             opt.impl_get_master_slaves().in_same_group(option):
193                         master_slave = True
194                         is_multi = True
195                     else:
196                         is_multi = False
197                     tcparams.setdefault(key, []).append((value, is_multi))
198             else:
199                 # callbk is a value and not a multi
200                 tcparams.setdefault(key, []).append((callbk, False))
201
202     # if one value is a multi, launch several time calculate
203     # if index is set, return a value
204     # if no index, return a list
205     if master_slave:
206         ret = []
207         args = []
208         kwargs = {}
209         for key, couples in tcparams.items():
210             for couple in couples:
211                 value, ismulti = couple
212                 if ismulti:
213                     val = value[index]
214                 else:
215                     val = value
216                 if key == '':
217                     args.append(val)
218                 else:
219                     kwargs[key] = val
220         return calculate(option, callback, is_validator, args, kwargs)
221     else:
222         # no value is multi
223         # return a single value
224         args = []
225         kwargs = {}
226         for key, couples in tcparams.items():
227             for couple in couples:
228                 # couple[1] (ismulti) is always False
229                 if key == '':
230                     args.append(couple[0])
231                 else:
232                     kwargs[key] = couple[0]
233         ret = calculate(option, callback, is_validator, args, kwargs)
234         if not option.impl_is_optiondescription() and callback_params != {} and isinstance(ret, list) and \
235                 option.impl_is_master_slaves('slave'):
236             if not has_option and index not in [None, undefined]:
237                 if index < len(ret):
238                     ret = ret[index]
239                 else:
240                     ret = None
241             else:
242                 raise SlaveError(_("callback cannot return a list for a "
243                                    "slave option ({0})").format(option.impl_getname()))
244         return ret
245
246
247 def calculate(option, callback, is_validator, args, kwargs):
248     """wrapper that launches the 'callback'
249
250     :param callback: callback function
251     :param args: in the callback's arity, the unnamed parameters
252     :param kwargs: in the callback's arity, the named parameters
253
254     """
255     try:
256         return callback(*args, **kwargs)
257     except ValueError as err:
258         if is_validator:
259             return err
260         error = err
261     except Exception as err:
262         error = err
263     if len(args) != 0 or len(kwargs) != 0:
264         msg = _('unexpected error "{0}" in function "{1}" with arguments "{3}" and "{4}" '
265                 'for option "{2}"').format(str(error),
266                                            callback.__name__,
267                                            option.impl_get_display_name(),
268                                            args,
269                                            kwargs)
270     else:
271         msg = _('unexpected error "{0}" in function "{1}" for option "{2}"'
272                 '').format(str(error),
273                            callback.__name__,
274                            option.impl_get_display_name())
275     raise ConfigError(msg)