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