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