first version with sqlalchemy option's storage
[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
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16 #
17 # The original `Config` design model is unproudly borrowed from
18 # the rough gus of pypy: pypy: http://codespeak.net/svn/pypy/dist/pypy/config/
19 # the whole pypy projet is under MIT licence
20 # ____________________________________________________________
21 "enables us to carry out a calculation and return an option's value"
22 from tiramisu.error import PropertiesOptionError, ConfigError
23 from tiramisu.i18n import _
24 # ____________________________________________________________
25
26
27 def carry_out_calculation(name, config, callback, callback_params,
28                           index=None, max_len=None):
29     """a function that carries out a calculation for an option's value
30
31     :param name: the option name (`opt.impl_getname()`)
32     :param config: 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 max_len: max length for a multi
42     :type max_len: int
43
44     The callback_params is a dict. Key is used to build args (if key is '')
45     and kwargs (otherwise). Values are tuple of:
46     - values
47     - tuple with option and boolean's force_permissive (True when don't raise
48     if PropertiesOptionError)
49     Values could have multiple values only when key is ''.
50
51     * if no callback_params:
52       => calculate()
53
54     * if callback_params={'': ('yes',)}
55       => calculate('yes')
56
57     * if callback_params={'value': ('yes',)}
58       => calculate(value='yes')
59
60     * if callback_params={'': ('yes', 'no')}
61       => calculate('yes', 'no')
62
63     * if callback_params={'value': ('yes', 'no')}
64       => ValueError()
65
66     * if callback_params={'': ((opt1, False),)}
67
68        - a simple option:
69          opt1 == 11
70          => calculate(11)
71
72        - a multi option:
73          opt1 == [1, 2, 3]
74          => calculate(1)
75          => calculate(2)
76          => calculate(3)
77
78     * if callback_params={'value': ((opt1, False),)}
79
80        - a simple option:
81          opt1 == 11
82          => calculate(value=11)
83
84        - a multi option:
85          opt1 == [1, 2, 3]
86          => calculate(value=1)
87          => calculate(value=2)
88          => calculate(value=3)
89
90     * if callback_params={'': ((opt1, False), (opt2, False))}
91
92       - a multi option with a simple option
93           opt1 == [1, 2, 3]
94           opt2 == 11
95           => calculate(1, 11)
96           => calculate(2, 11)
97           => calculate(3, 11)
98
99       - a multi option with an other multi option but with same length
100           opt1 == [1, 2, 3]
101           opt2 == [11, 12, 13]
102           => calculate(1, 11)
103           => calculate(2, 12)
104           => calculate(3, 13)
105
106       - a multi option with an other multi option but with different length
107           opt1 == [1, 2, 3]
108           opt2 == [11, 12]
109           => ConfigError()
110
111       - a multi option without value with a simple option
112           opt1 == []
113           opt2 == 11
114           => []
115
116     * if callback_params={'value': ((opt1, False), (opt2, False))}
117       => ConfigError()
118
119     If index is not None, return a value, otherwise return:
120
121     * a list if one parameters have multi option
122     * a value otherwise
123
124     If calculate return list, this list is extend to return value.
125     """
126     tcparams = {}
127     # if callback_params has a callback, launch several time calculate()
128     one_is_multi = False
129     # multi's option should have same value for all option
130     len_multi = None
131
132     if callback_params != []:
133         for callbacks in callback_params:
134             key = callbacks.name
135             for callbk in callbacks.params:
136                 if callbk.option is not None:
137                     # callbk is something link (opt, True|False)
138                     option = callbk.get_option(config)
139                     force_permissive = callbk.force_permissive
140                     path = config.cfgimpl_get_description().impl_get_path_by_opt(
141                         option)
142                     # get value
143                     try:
144                         value = config._getattr(path, force_permissive=True)
145                     except PropertiesOptionError as err:
146                         if force_permissive:
147                             continue
148                         raise ConfigError(_('unable to carry out a calculation, '
149                                             'option {0} has properties: {1} for: '
150                                             '{2}').format(option.impl_getname(),
151                                                         err.proptype,
152                                                         name))
153                     is_multi = option.impl_is_multi()
154                     if is_multi:
155                         len_value = len(value)
156                         if len_multi is not None and len_multi != len_value:
157                             raise ConfigError(_('unable to carry out a '
158                                                 'calculation, option value with'
159                                                 ' multi types must have same '
160                                                 'length for: {0}').format(name))
161                         len_multi = len_value
162                         one_is_multi = True
163                     tcparams.setdefault(key, []).append((value, is_multi))
164                 else:
165                     # callbk is a value and not a multi
166                     tcparams.setdefault(key, []).append((callbk.value, False))
167
168     # if one value is a multi, launch several time calculate
169     # if index is set, return a value
170     # if no index, return a list
171     if one_is_multi:
172         ret = []
173         if index:
174             if index < len_multi:
175                 range_ = [index]
176             else:
177                 range_ = []
178                 ret = None
179         else:
180             if max_len and max_len < len_multi:
181                 range_ = range(max_len)
182             else:
183                 range_ = range(len_multi)
184         for incr in range_:
185             args = []
186             kwargs = {}
187             for key, couples in tcparams.items():
188                 for couple in couples:
189                     value, ismulti = couple
190                     if ismulti:
191                         val = value[incr]
192                     else:
193                         val = value
194                     if key == '':
195                         args.append(val)
196                     else:
197                         kwargs[key] = val
198             calc = calculate(callback, args, kwargs)
199             if index:
200                 ret = calc
201             else:
202                 if isinstance(calc, list):
203                     ret.extend(calc)
204                 else:
205                     ret.append(calc)
206         return ret
207     else:
208         # no value is multi
209         # return a single value
210         args = []
211         kwargs = {}
212         for key, couples in tcparams.items():
213             for couple in couples:
214                 # couple[1] (ismulti) is always False
215                 if key == '':
216                     args.append(couple[0])
217                 else:
218                     kwargs[key] = couple[0]
219         return calculate(callback, args, kwargs)
220
221
222 def calculate(callback, args, kwargs):
223     """wrapper that launches the 'callback'
224
225     :param callback: callback function
226     :param args: in the callback's arity, the unnamed parameters
227     :param kwargs: in the callback's arity, the named parameters
228
229     """
230     return callback(*args, **kwargs)