coverage
[tiramisu.git] / tiramisu / storage / dictionary / option.py
1 # -*- coding: utf-8 -*-
2 ""
3 # Copyright (C) 2014-2017 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 import sys
21 from ...i18n import _
22 from ...setting import undefined
23 from ...error import ConfigError
24 static_tuple = tuple()
25 static_set = frozenset()
26 if sys.version_info[0] >= 3:  # pragma: no cover
27     xrange = range
28
29
30 #____________________________________________________________
31 #
32 # Base
33 #('_name', '_informations', '_multi', '_multitype', '_warnings_only', '_extra', '_readonly', '_subdyn)
34 class StorageBase(object):
35     __slots__ = ('_name',
36                  '_informations',
37                  '_extra',
38                  '_warnings_only',
39                  '_allow_empty_list',
40                  #multi
41                  '_multi',
42                  '_unique',
43                  #value
44                  '_default',
45                  '_default_multi',
46                  #calcul
47                  '_subdyn',
48                  '_requires',
49                  '_properties',
50                  '_calc_properties',
51                  '_val_call',
52                  #
53                  '_consistencies',
54                  '_master_slaves',
55                  '_choice_values',
56                  '_choice_values_params',
57                  #other
58                  '_has_dependency',
59                  '_state_master_slaves',
60                  '_state_val_call',
61                  '_state_requires',
62                  '_stated',
63                  '_state_consistencies',
64                  '_state_informations',
65                  '_state_extra',
66                  '_state_readonly',
67                  '__weakref__'
68                  )
69
70     def __init__(self, name, multi, warnings_only, doc, extra, calc_properties,
71                  requires, properties, allow_empty_list, unique, opt=undefined,
72                  session=None):
73         _setattr = object.__setattr__
74         _setattr(self, '_name', name)
75         if doc is not undefined:
76             if isinstance(doc, str):
77                 doc = doc.decode('utf8')
78             _setattr(self, '_informations', {'doc': doc})
79             if multi != 1:
80                 _setattr(self, '_multi', multi)
81             if extra is not None:
82                 _setattr(self, '_extra', extra)
83         if warnings_only is True:
84             _setattr(self, '_warnings_only', warnings_only)
85
86         if calc_properties is not undefined:
87             _setattr(self, '_calc_properties', calc_properties)
88         if requires is not undefined:
89             _setattr(self, '_requires', requires)
90         if properties is not undefined:
91             _setattr(self, '_properties', properties)
92         if opt is not undefined:
93             _setattr(self, '_opt', opt)
94         if allow_empty_list is not undefined:
95             _setattr(self, '_allow_empty_list', allow_empty_list)
96         if unique is not undefined:
97             setattr(self, '_unique', unique)
98
99     def _set_default_values(self, default, default_multi, is_multi):
100         _setattr = object.__setattr__
101         if (is_multi and default != []) or \
102                 (not is_multi and default is not None):
103             if is_multi:
104                 default = tuple(default)
105             _setattr(self, '_default', default)
106
107         if is_multi and default_multi is not None:
108             err = self._validate(default_multi)
109             if err:
110                 raise ValueError(_("invalid default_multi value {0} "
111                                    "for option {1}: {2}").format(
112                                        str(default_multi),
113                                        self.impl_getname(), str(err)))
114             _setattr(self, '_default_multi', default_multi)
115
116     # information
117     def impl_set_information(self, key, value):
118         """updates the information's attribute
119         (which is a dictionary)
120
121         :param key: information's key (ex: "help", "doc"
122         :param value: information's value (ex: "the help string")
123         """
124         if self.impl_is_readonly():
125             raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
126                                    " read-only").format(
127                                        self.__class__.__name__,
128                                        self,
129                                        #self.impl_getname(),
130                                        key))
131         self._informations[key] = value
132
133     def impl_get_information(self, key, default=undefined):
134         """retrieves one information's item
135
136         :param key: the item string (ex: "help")
137         """
138         dico = self._informations
139         if isinstance(dico, tuple):
140             if key in dico[0]:
141                 return dico[1][dico[0].index(key)]
142         elif self._is_string(dico):
143             if key == 'doc':
144                 return dico
145         elif isinstance(dico, dict):
146             if key in dico:
147                 return dico[key]
148         if default is not undefined:
149             return default
150         raise ValueError(_("information's item not found: {0}").format(
151             key))
152
153     def _add_consistency(self, func, all_cons_opts, params):
154         cons = (func, all_cons_opts, params)
155         consistencies = getattr(self, '_consistencies', None)
156         if consistencies is None:
157             self._consistencies = [cons]
158         else:
159             consistencies.append(cons)
160
161     def _del_consistency(self):
162         self._consistencies.pop(-1)
163
164     def _get_consistencies(self):
165         return getattr(self, '_consistencies', static_tuple)
166
167     def _set_callback(self, callback, callback_params):
168         val = getattr(self, '_val_call', (None,))[0]
169         if callback_params is None or callback_params == {}:
170             val_call = (callback,)
171         else:
172             val_call = tuple([callback, callback_params])
173         self._val_call = (val, val_call)
174
175     def impl_set_choice_values_params(self, values, values_params, session):
176         self._choice_values = values
177         if values_params is not None:
178             self._choice_values_params = values_params
179
180
181     def impl_get_callback(self):
182         call = getattr(self, '_val_call', (None, None))[1]
183         if call is None:
184             ret_call = (None, {})
185         elif len(call) == 1:
186             ret_call = (call[0], {})
187         else:
188             ret_call = call
189         return ret_call
190
191     def impl_get_choice_values_params(self):
192         return getattr(self, '_choice_values_params', {})
193
194     def impl_get_calc_properties(self):
195         return getattr(self, '_calc_properties', static_set)
196
197     def impl_getrequires(self):
198         return getattr(self, '_requires', static_tuple)
199
200     def _set_validator(self, validator, validator_params):
201         if validator_params is None:
202             val_call = (validator,)
203         else:
204             val_call = (validator, validator_params)
205         self._val_call = (val_call, None)
206
207     def impl_get_validator(self):
208         val = getattr(self, '_val_call', (None,))[0]
209         if val is None:
210             ret_val = (None, {})
211         elif len(val) == 1:
212             ret_val = (val[0], {})
213         else:
214             ret_val = val
215         return ret_val
216
217     def _impl_convert_callback(self, descr, load=False):
218         if self.__class__.__name__ in ['OptionDescription', 'SymLinkOption']:
219             return
220         if not load and self.impl_get_callback() == (None, {}):
221             self._state_val_call = None
222         elif load and self._state_val_call is None:
223             del(self._state_val_call)
224         else:
225             if load:
226                 val_call = self._state_val_call
227             else:
228                 val_call = self._val_call
229             new_val_call = []
230             for vals in val_call:
231                 if vals is None or len(vals) == 1:
232                     new_val_call.append(vals)
233                 else:
234                     prms = {}
235                     val, params = vals
236                     for key, values in params.items():
237                         vls = []
238                         for value in values:
239                             if isinstance(value, tuple) and value[0] is not None:
240                                 if load:
241                                     value = (descr.impl_get_opt_by_path(value[0]),
242                                              value[1])
243                                 else:
244                                     value = (descr.impl_get_path_by_opt(value[0]),
245                                              value[1])
246                             vls.append(value)
247                         prms[key] = tuple(vls)
248                     new_val_call.append((val, prms))
249             if load:
250                 del(self._state_val_call)
251                 self._val_call = new_val_call
252             else:
253                 self._state_val_call = new_val_call
254
255     def _get_id(self):
256         return id(self)
257
258     def _impl_getsubdyn(self):
259         return self._subdyn
260
261     def _impl_getopt(self):
262         return self._opt
263
264     def _set_readonly(self, has_extra):
265         if not self.impl_is_readonly():
266             _setattr = object.__setattr__
267             dico = self._informations
268             keys = tuple(dico.keys())
269             if len(keys) == 1:
270                 dico = dico['doc']
271             else:
272                 dico = tuple([keys, tuple(dico.values())])
273             _setattr(self, '_informations', dico)
274             if has_extra:
275                 extra = getattr(self, '_extra', None)
276                 if extra is not None:
277                     _setattr(self, '_extra', tuple([tuple(extra.keys()), tuple(extra.values())]))
278
279     def impl_getproperties(self):
280         return self._properties
281
282     def _impl_setsubdyn(self, subdyn):
283         self._subdyn = subdyn
284
285     def _impl_setopt(self, opt):
286         self._opt = opt
287
288     def _is_string(self, infos):
289         if sys.version_info[0] >= 3:  # pragma: no cover
290             return isinstance(infos, str)
291         else:
292             return isinstance(infos, str) or isinstance(infos, unicode)
293
294     def _impl_convert_zinformations(self, descr, load=False):
295         if not load:
296             infos = self._informations
297             if isinstance(infos, tuple):
298                 self._state_informations = {}
299                 for idx, key in enumerate(infos[0]):
300                     self._state_informations[key] = infos[1][idx]
301             elif self._is_string(infos):
302                 self._state_informations = {'doc': infos}
303             else:
304                 self._state_informations = infos
305             self._state_readonly = self.impl_is_readonly()
306         else:
307             _setattr = object.__setattr__
308             _setattr(self, '_informations', self._state_informations)
309             del(self._state_informations)
310             if self._state_readonly:
311                 self._set_readonly(True)
312             del(self._state_readonly)
313
314     def _impl_convert_extra(self, descr, load=False):
315         if not load:
316             extra = getattr(self, '_extra', None)
317             if isinstance(extra, tuple):
318                 self._state_extra = {}
319                 for idx, key in enumerate(extra[0]):
320                     self._state_extra[key] = extra[1][idx]
321             elif isinstance(extra, dict):
322                 self._state_extra = extra
323         else:
324             extra = getattr(self, '_state_extra', None)
325             if extra is not None:
326                 _setattr = object.__setattr__
327                 _setattr(self, '_extra', extra)
328                 del(self._state_extra)
329
330     def _impl_getattributes(self):
331         slots = set()
332         for subclass in self.__class__.__mro__:
333             if subclass is not object:
334                 slots.update(subclass.__slots__)
335         return slots
336
337     def impl_is_readonly(self):
338         return not isinstance(getattr(self, '_informations', dict()), dict)
339
340     def impl_getname(self):
341         return self._name
342
343     def impl_is_multi(self):
344         return getattr(self, '_multi', 1) != 1
345
346     def impl_is_submulti(self):
347         return getattr(self, '_multi', 1) == 2
348
349     def impl_allow_empty_list(self):
350         return getattr(self, '_allow_empty_list', undefined)
351
352     def impl_is_unique(self):
353         return getattr(self, '_unique', False)
354
355     def _get_extra(self, key):
356         extra = self._extra
357         if isinstance(extra, tuple):
358             return extra[1][extra[0].index(key)]
359         else:
360             return extra[key]
361
362     def _is_warnings_only(self):
363         return getattr(self, '_warnings_only', False)
364
365     def impl_getdefault(self):
366         "accessing the default value"
367         is_multi = self.impl_is_multi()
368         default = getattr(self, '_default', undefined)
369         if default is undefined:
370             if is_multi:
371                 default = []
372             else:
373                 default = None
374         else:
375             if is_multi:
376                 default = list(default)
377         return default
378
379     def impl_getdefault_multi(self):
380         "accessing the default value for a multi"
381         return getattr(self, '_default_multi', None)
382
383     def _get_master_slave(self):
384         return getattr(self, '_master_slaves', None)
385
386     def _set_master_slaves(self, option):
387         self._master_slaves = option
388
389     def getsession(self):
390         pass
391
392     def commit(self, session):
393         pass
394
395
396 class StorageOptionDescription(StorageBase):
397     __slots__ = ('_children', '_cache_paths', '_cache_consistencies',
398                  '_group_type', '_state_group_type', '_cache_force_store_values')
399
400     def __init__(self, name, multi, warnings_only, doc, extra):
401         super(StorageOptionDescription, self).__init__(name, multi,
402                                                        warnings_only, doc,
403                                                        None, undefined,
404                                                        undefined, undefined)
405
406     def _add_children(self, child_names, children):
407         _setattr = object.__setattr__
408         _setattr(self, '_children', (tuple(child_names), tuple(children)))
409
410     def impl_already_build_caches(self):
411         return getattr(self, '_cache_paths', None) is not None
412
413     def impl_get_opt_by_path(self, path):
414         if getattr(self, '_cache_paths', None) is None:
415             raise ConfigError(_('use impl_get_opt_by_path only with root OptionDescription'))
416         if path not in self._cache_paths[1]:
417             raise AttributeError(_('no option for path {0}').format(path))
418         return self._cache_paths[0][self._cache_paths[1].index(path)]
419
420     def impl_get_path_by_opt(self, opt):
421         if getattr(self, '_cache_paths', None) is None:
422             raise ConfigError(_('use impl_get_path_by_opt only with root OptionDescription'))
423         if opt not in self._cache_paths[0]:
424             raise AttributeError(_('no option {0} found').format(opt))
425         return self._cache_paths[1][self._cache_paths[0].index(opt)]
426
427     def impl_get_group_type(self):
428         return self._group_type
429
430     def impl_build_cache_option(self, _currpath=None, cache_path=None,
431                                 cache_option=None):
432
433         if self.impl_is_readonly() or (_currpath is None and getattr(self, '_cache_paths', None) is not None):
434             # cache already set
435             return
436         if _currpath is None:
437             save = True
438             _currpath = []
439         else:
440             save = False
441         if cache_path is None:
442             cache_path = []
443             cache_option = []
444         for option in self._impl_getchildren(dyn=False):
445             attr = option.impl_getname()
446             path = str('.'.join(_currpath + [attr]))
447             cache_option.append(option)
448             cache_path.append(path)
449             if option.impl_is_optiondescription():
450                 _currpath.append(attr)
451                 option.impl_build_cache_option(_currpath, cache_path,
452                                                cache_option)
453                 _currpath.pop()
454         if save:
455             _setattr = object.__setattr__
456             _setattr(self, '_cache_paths', (tuple(cache_option), tuple(cache_path)))
457
458     def impl_get_options_paths(self, bytype, byname, _subpath, only_first, context):
459         find_results = []
460
461         def _rebuild_dynpath(path, suffix, dynopt):
462             found = False
463             spath = path.split('.')
464             for length in xrange(1, len(spath)):
465                 subpath = '.'.join(spath[0:length])
466                 subopt = self.impl_get_opt_by_path(subpath)
467                 if dynopt == subopt:
468                     found = True
469                     break
470             if not found:  # pragma: no cover
471                 raise ConfigError(_('cannot find dynpath'))
472             subpath = subpath + suffix
473             for slength in xrange(length, len(spath)):
474                 subpath = subpath + '.' + spath[slength] + suffix
475             return subpath
476
477         def _filter_by_name(path, option):
478             name = option.impl_getname()
479             if option._is_subdyn():
480                 if byname.startswith(name):
481                     found = False
482                     for suffix in option._subdyn._impl_get_suffixes(
483                             context):
484                         if byname == name + suffix:
485                             found = True
486                             path = _rebuild_dynpath(path, suffix,
487                                                     option._subdyn)
488                             option = option._impl_to_dyn(
489                                 name + suffix, path)
490                             break
491                     if not found:
492                         return False
493             else:
494                 if not byname == name:
495                     return False
496             find_results.append((path, option))
497             return True
498
499         def _filter_by_type(path, option):
500             if isinstance(option, bytype):
501                 #if byname is not None, check option byname in _filter_by_name
502                 #not here
503                 if byname is None:
504                     if option._is_subdyn():
505                         name = option.impl_getname()
506                         for suffix in option._subdyn._impl_get_suffixes(
507                                 context):
508                             spath = _rebuild_dynpath(path, suffix,
509                                                      option._subdyn)
510                             find_results.append((spath, option._impl_to_dyn(
511                                 name + suffix, spath)))
512                     else:
513                         find_results.append((path, option))
514                 return True
515             return False
516
517         def _filter(path, option):
518             if bytype is not None:
519                 retval = _filter_by_type(path, option)
520                 if byname is None:
521                     return retval
522             if byname is not None:
523                 return _filter_by_name(path, option)
524
525         opts, paths = self._cache_paths
526         for index in xrange(0, len(paths)):
527             option = opts[index]
528             if option.impl_is_optiondescription():
529                 continue
530             path = paths[index]
531             if _subpath is not None and not path.startswith(_subpath + '.'):
532                 continue
533             if bytype == byname is None:
534                 if option._is_subdyn():
535                     name = option.impl_getname()
536                     for suffix in option._subdyn._impl_get_suffixes(
537                             context):
538                         spath = _rebuild_dynpath(path, suffix,
539                                                  option._subdyn)
540                         find_results.append((spath, option._impl_to_dyn(
541                             name + suffix, spath)))
542                 else:
543                     find_results.append((path, option))
544             else:
545                 if _filter(path, option) is False:
546                     continue
547             if only_first:
548                 return find_results[0]
549         return find_results
550
551     def _impl_st_getchildren(self, context, only_dyn=False):
552         for child in self._children[1]:
553             if only_dyn is False or child.impl_is_dynoptiondescription():
554                 yield(child)
555
556     def _getattr(self, name, suffix=undefined, context=undefined, dyn=True):
557         error = False
558         if suffix is not undefined:
559             if undefined in [suffix, context]:  # pragma: no cover
560                 raise ConfigError(_("suffix and context needed if "
561                                     "it's a dyn option"))
562             if name.endswith(suffix):
563                 oname = name[:-len(suffix)]
564                 child = self._children[1][self._children[0].index(oname)]
565                 return self._impl_get_dynchild(child, suffix)
566             else:
567                 error = True
568         else:
569             if name in self._children[0]:
570                 child = self._children[1][self._children[0].index(name)]
571                 if dyn and child.impl_is_dynoptiondescription():
572                     error = True
573                 else:
574                     return child
575             else:
576                 child = self._impl_search_dynchild(name, context=context)
577                 if child != []:
578                     return child
579                 error = True
580         if error:
581             raise AttributeError(_('unknown Option {0} '
582                                    'in OptionDescription {1}'
583                                    '').format(name, self.impl_getname()))
584
585
586 class StorageMasterSlaves(object):
587     __slots__ = ('master', 'slaves')
588
589     def __init__(self, master, slaves):
590         self.master = master
591         self.slaves = slaves
592
593     def _sm_getmaster(self):
594         return self.master
595
596     def _sm_getslaves(self):
597         return tuple(self.slaves)