25c5e11a080f86debd3a06c5a4b5d7e4f4403754
[tiramisu.git] / tiramisu / option / baseoption.py
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
3 #
4 # This program is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Lesser General Public License as published by the
6 # Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
12 # details.
13 #
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17 # The original `Config` design model is unproudly borrowed from
18 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
19 # the whole pypy projet is under MIT licence
20 # ____________________________________________________________
21 import re
22 from copy import copy, deepcopy
23 from types import FunctionType
24 import warnings
25
26 from tiramisu.i18n import _
27 from tiramisu.setting import log, undefined
28 from tiramisu.autolib import carry_out_calculation
29 from tiramisu.error import ConfigError, ValueWarning
30 from tiramisu.storage import get_storages_option
31
32
33 StorageBase = get_storages_option('base')
34
35 name_regexp = re.compile(r'^\d+')
36 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
37                    'make_dict', 'unwrap_from_path', 'read_only',
38                    'read_write', 'getowner', 'set_contexts')
39
40
41 def valid_name(name):
42     "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
43     if not isinstance(name, str):
44         return False
45     if re.match(name_regexp, name) is None and not name.startswith('_') \
46             and name not in forbidden_names \
47             and not name.startswith('impl_') \
48             and not name.startswith('cfgimpl_'):
49         return True
50     else:
51         return False
52
53
54 def validate_callback(callback, callback_params, type_):
55     if type(callback) != FunctionType:
56         raise ValueError(_('{0} must be a function').format(type_))
57     if callback_params is not None:
58         if not isinstance(callback_params, dict):
59             raise ValueError(_('{0}_params must be a dict').format(type_))
60         for key, callbacks in callback_params.items():
61             if key != '' and len(callbacks) != 1:
62                 raise ValueError(_("{0}_params with key {1} mustn't have "
63                                    "length different to 1").format(type_,
64                                                                    key))
65             if not isinstance(callbacks, tuple):
66                 raise ValueError(_('{0}_params must be tuple for key "{1}"'
67                                    ).format(type_, key))
68             for callbk in callbacks:
69                 if isinstance(callbk, tuple):
70                     if len(callbk) == 1:
71                         if callbk != (None,):
72                             raise ValueError(_('{0}_params with length of '
73                                                'tuple as 1 must only have '
74                                                'None as first value'))
75                     elif len(callbk) != 2:
76                         raise ValueError(_('{0}_params must only have 1 or 2 '
77                                            'as length'))
78                     else:
79                         option, force_permissive = callbk
80                         if type_ == 'validator' and not force_permissive:
81                             raise ValueError(_('validator not support tuple'))
82                         if not isinstance(option, Option) and not \
83                                 isinstance(option, SymLinkOption):
84                             raise ValueError(_('{0}_params must have an option'
85                                                ' not a {0} for first argument'
86                                                ).format(type_, type(option)))
87                         if force_permissive not in [True, False]:
88                             raise ValueError(_('{0}_params must have a boolean'
89                                                ' not a {0} for second argument'
90                                                ).format(type_, type(
91                                                    force_permissive)))
92 #____________________________________________________________
93 #
94
95
96 class Base(StorageBase):
97     __slots__ = tuple()
98
99     def __init__(self, name, doc, default=None, default_multi=None,
100                  requires=None, multi=False, callback=None,
101                  callback_params=None, validator=None, validator_params=None,
102                  properties=None, warnings_only=False):
103         if not valid_name(name):
104             raise ValueError(_("invalid name: {0} for option").format(name))
105         self._name = name
106         self._readonly = False
107         self._informations = {}
108         self.impl_set_information('doc', doc)
109         if requires is not None:
110             self._calc_properties, self._requires = validate_requires_arg(
111                 requires, self._name)
112         else:
113             self._calc_properties = frozenset()
114             self._requires = []
115         if not multi and default_multi is not None:
116             raise ValueError(_("a default_multi is set whereas multi is False"
117                              " in option: {0}").format(name))
118         if default_multi is not None:
119             try:
120                 self._validate(default_multi)
121             except ValueError as err:
122                 raise ValueError(_("invalid default_multi value {0} "
123                                    "for option {1}: {2}").format(
124                                        str(default_multi), name, err))
125         self._multi = multi
126         if self._multi:
127             if default is None:
128                 default = []
129             self._default_multi = default_multi
130         if callback is not None and ((not multi and (default is not None or
131                                                      default_multi is not None))
132                                      or (multi and (default != [] or
133                                                     default_multi is not None))
134                                      ):
135             raise ValueError(_("default value not allowed if option: {0} "
136                              "is calculated").format(name))
137         if properties is None:
138             properties = tuple()
139         if not isinstance(properties, tuple):
140             raise TypeError(_('invalid properties type {0} for {1},'
141                             ' must be a tuple').format(
142                                 type(properties),
143                                 self._name))
144         if validator is not None:
145             validate_callback(validator, validator_params, 'validator')
146             self._validator = validator
147             if validator_params is not None:
148                 self._validator_params = validator_params
149         if callback is None and callback_params is not None:
150             raise ValueError(_("params defined for a callback function but "
151                              "no callback defined"
152                              " yet for option {0}").format(name))
153         if callback is not None:
154             validate_callback(callback, callback_params, 'callback')
155             self._callback = callback
156             if callback_params is not None:
157                 self._callback_params = callback_params
158         if self._calc_properties != frozenset([]) and properties is not tuple():
159             set_forbidden_properties = self._calc_properties & set(properties)
160             if set_forbidden_properties != frozenset():
161                 raise ValueError('conflict: properties already set in '
162                                  'requirement {0}'.format(
163                                      list(set_forbidden_properties)))
164         if multi and default is None:
165             self._default = []
166         else:
167             self._default = default
168         self._properties = properties
169         self._warnings_only = warnings_only
170         ret = super(Base, self).__init__()
171         self.impl_validate(self._default)
172         return ret
173
174
175 class BaseOption(Base):
176     """This abstract base class stands for attribute access
177     in options that have to be set only once, it is of course done in the
178     __setattr__ method
179     """
180     __slots__ = tuple()
181
182     # information
183     def impl_set_information(self, key, value):
184         """updates the information's attribute
185         (which is a dictionary)
186
187         :param key: information's key (ex: "help", "doc"
188         :param value: information's value (ex: "the help string")
189         """
190         self._informations[key] = value
191
192     def impl_get_information(self, key, default=undefined):
193         """retrieves one information's item
194
195         :param key: the item string (ex: "help")
196         """
197         if key in self._informations:
198             return self._informations[key]
199         elif default is not undefined:
200             return default
201         else:
202             raise ValueError(_("information's item not found: {0}").format(
203                 key))
204
205     # ____________________________________________________________
206     # serialize object
207     def _impl_convert_requires(self, descr, load=False):
208         """export of the requires during the serialization process
209
210         :type descr: :class:`tiramisu.option.OptionDescription`
211         :param load: `True` if we are at the init of the option description
212         :type load: bool
213         """
214         if not load and self._requires is None:
215             self._state_requires = None
216         elif load and self._state_requires is None:
217             self._requires = None
218             del(self._state_requires)
219         else:
220             if load:
221                 _requires = self._state_requires
222             else:
223                 _requires = self._requires
224             new_value = []
225             for requires in _requires:
226                 new_requires = []
227                 for require in requires:
228                     if load:
229                         new_require = [descr.impl_get_opt_by_path(require[0])]
230                     else:
231                         new_require = [descr.impl_get_path_by_opt(require[0])]
232                     new_require.extend(require[1:])
233                     new_requires.append(tuple(new_require))
234                 new_value.append(tuple(new_requires))
235             if load:
236                 del(self._state_requires)
237                 self._requires = new_value
238             else:
239                 self._state_requires = new_value
240
241     # serialize
242     def _impl_getstate(self, descr):
243         """the under the hood stuff that need to be done
244         before the serialization.
245
246         :param descr: the parent :class:`tiramisu.option.OptionDescription`
247         """
248         self._stated = True
249         for func in dir(self):
250             if func.startswith('_impl_convert_'):
251                 getattr(self, func)(descr)
252         self._state_readonly = self._readonly
253
254     def __getstate__(self, stated=True):
255         """special method to enable the serialization with pickle
256         Usualy, a `__getstate__` method does'nt need any parameter,
257         but somme under the hood stuff need to be done before this action
258
259         :parameter stated: if stated is `True`, the serialization protocol
260                            can be performed, not ready yet otherwise
261         :parameter type: bool
262         """
263         try:
264             self._stated
265         except AttributeError:
266             raise SystemError(_('cannot serialize Option, '
267                                 'only in OptionDescription'))
268         slots = set()
269         for subclass in self.__class__.__mro__:
270             if subclass is not object:
271                 slots.update(subclass.__slots__)
272         slots -= frozenset(['_cache_paths', '_cache_consistencies',
273                             '__weakref__'])
274         states = {}
275         for slot in slots:
276             # remove variable if save variable converted
277             # in _state_xxxx variable
278             if '_state' + slot not in slots:
279                 if slot.startswith('_state'):
280                     # should exists
281                     states[slot] = getattr(self, slot)
282                     # remove _state_xxx variable
283                     self.__delattr__(slot)
284                 else:
285                     try:
286                         states[slot] = getattr(self, slot)
287                     except AttributeError:
288                         pass
289         if not stated:
290             del(states['_stated'])
291         return states
292
293     # unserialize
294     def _impl_setstate(self, descr):
295         """the under the hood stuff that need to be done
296         before the serialization.
297
298         :type descr: :class:`tiramisu.option.OptionDescription`
299         """
300         for func in dir(self):
301             if func.startswith('_impl_convert_'):
302                 getattr(self, func)(descr, load=True)
303         try:
304             self._readonly = self._state_readonly
305             del(self._state_readonly)
306             del(self._stated)
307         except AttributeError:
308             pass
309
310     def __setstate__(self, state):
311         """special method that enables us to serialize (pickle)
312
313         Usualy, a `__setstate__` method does'nt need any parameter,
314         but somme under the hood stuff need to be done before this action
315
316         :parameter state: a dict is passed to the loads, it is the attributes
317                           of the options object
318         :type state: dict
319         """
320         for key, value in state.items():
321             setattr(self, key, value)
322
323     def __setattr__(self, name, value):
324         """set once and only once some attributes in the option,
325         like `_name`. `_name` cannot be changed one the option and
326         pushed in the :class:`tiramisu.option.OptionDescription`.
327
328         if the attribute `_readonly` is set to `True`, the option is
329         "frozen" (which has noting to do with the high level "freeze"
330         propertie or "read_only" property)
331         """
332         if name not in ('_option', '_is_build_cache') \
333                 and not isinstance(value, tuple):
334             is_readonly = False
335             # never change _name
336             if name == '_name':
337                 try:
338                     if self._name is not None:
339                         #so _name is already set
340                         is_readonly = True
341                 except (KeyError, AttributeError):
342                     pass
343             elif name != '_readonly':
344                 is_readonly = self.impl_is_readonly()
345             if is_readonly:
346                 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
347                                        " read-only").format(
348                                            self.__class__.__name__,
349                                            self._name,
350                                            name))
351         super(BaseOption, self).__setattr__(name, value)
352
353     def impl_is_readonly(self):
354         try:
355             if self._readonly is True:
356                 return True
357         except AttributeError:
358             pass
359         return False
360
361     def impl_getname(self):
362         return self._name
363
364
365 class OnlyOption(BaseOption):
366     __slots__ = tuple()
367
368
369 class Option(OnlyOption):
370     """
371     Abstract base class for configuration option's.
372
373     Reminder: an Option object is **not** a container for the value.
374     """
375 #    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
376 #                 '_state_callback', '_callback',
377 #                 '_consistencies', '_warnings_only', '_master_slaves',
378 #                 '_state_consistencies', '__weakref__')
379     __slots__ = tuple()
380     _empty = ''
381
382     def __init__(self, name, doc, default=None, default_multi=None,
383                  requires=None, multi=False, callback=None,
384                  callback_params=None, validator=None, validator_params=None,
385                  properties=None, warnings_only=False):
386         """
387         :param name: the option's name
388         :param doc: the option's description
389         :param default: specifies the default value of the option,
390                         for a multi : ['bla', 'bla', 'bla']
391         :param default_multi: 'bla' (used in case of a reset to default only at
392                         a given index)
393         :param requires: is a list of names of options located anywhere
394                          in the configuration.
395         :param multi: if true, the option's value is a list
396         :param callback: the name of a function. If set, the function's output
397                          is responsible of the option's value
398         :param callback_params: the callback's parameter
399         :param validator: the name of a function which stands for a custom
400                           validation of the value
401         :param validator_params: the validator's parameters
402         :param properties: tuple of default properties
403         :param warnings_only: _validator and _consistencies don't raise if True
404                              Values()._warning contain message
405
406         """
407         super(Option, self).__init__(name, doc, default, default_multi,
408                                      requires, multi, callback,
409                                      callback_params, validator,
410                                      validator_params, properties,
411                                      warnings_only)
412
413     def impl_getrequires(self):
414         return self._requires
415
416     def _launch_consistency(self, func, option, value, context, index,
417                             all_cons_opts, warnings_only):
418         """Launch consistency now
419
420         :param func: function name, this name should start with _cons_
421         :type func: `str`
422         :param option: option that value is changing
423         :type option: `tiramisu.option.Option`
424         :param value: new value of this option
425         :param context: Config's context, if None, check default value instead
426         :type context: `tiramisu.config.Config`
427         :param index: only for multi option, consistency should be launch for
428                       specified index
429         :type index: `int`
430         :param all_cons_opts: all options concerne by this consistency
431         :type all_cons_opts: `list` of `tiramisu.option.Option`
432         :param warnings_only: specific raise error for warning
433         :type warnings_only: `boolean`
434         """
435         if context is not None:
436             descr = context.cfgimpl_get_description()
437
438         all_cons_vals = []
439         for opt in all_cons_opts:
440             #get value
441             if option == opt:
442                 opt_value = value
443             else:
444                 #if context, calculate value, otherwise get default value
445                 if context is not None:
446                     opt_value = context.getattr(
447                         descr.impl_get_path_by_opt(opt), validate=False,
448                         force_permissive=True)
449                 else:
450                     opt_value = opt.impl_getdefault()
451
452             #append value
453             if not self.impl_is_multi() or option == opt:
454                 all_cons_vals.append(opt_value)
455             else:
456                 #value is not already set, could be higher index
457                 try:
458                     all_cons_vals.append(opt_value[index])
459                 except IndexError:
460                     #so return if no value
461                     return
462         getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only)
463
464     def impl_validate(self, value, context=None, validate=True,
465                       force_index=None):
466         """
467         :param value: the option's value
468         :param context: Config's context
469         :type context: :class:`tiramisu.config.Config`
470         :param validate: if true enables ``self._validator`` validation
471         :type validate: boolean
472         :param force_index: if multi, value has to be a list
473                                not if force_index is not None
474         :type force_index: integer
475         """
476         if not validate:
477             return
478
479         def val_validator(val):
480             if self._validator is not None:
481                 if self._validator_params is not None:
482                     validator_params = {}
483                     for val_param, values in self._validator_params.items():
484                         validator_params[val_param] = values
485                     #FIXME ... ca sert à quoi ...
486                     if '' in validator_params:
487                         lst = list(validator_params[''])
488                         lst.insert(0, val)
489                         validator_params[''] = tuple(lst)
490                     else:
491                         validator_params[''] = (val,)
492                 else:
493                     validator_params = {'': (val,)}
494                 # Raise ValueError if not valid
495                 carry_out_calculation(self, config=context,
496                                       callback=self._validator,
497                                       callback_params=validator_params)
498
499         def do_validation(_value, _index=None):
500             if _value is None:
501                 return
502             # option validation
503             try:
504                 self._validate(_value)
505             except ValueError as err:
506                 log.debug('do_validation: value: {0} index: {1}'.format(
507                     _value, _index), exc_info=True)
508                 raise ValueError(_('invalid value for option {0}: {1}'
509                                    '').format(self.impl_getname(), err))
510             error = None
511             warning = None
512             try:
513                 # valid with self._validator
514                 val_validator(_value)
515                 # if not context launch consistency validation
516                 if context is not None:
517                     descr._valid_consistency(self, _value, context, _index)
518                 self._second_level_validation(_value, self._warnings_only)
519             except ValueError as error:
520                 log.debug(_('do_validation for {0}: error in value').format(
521                     self.impl_getname()), exc_info=True)
522                 if self._warnings_only:
523                     warning = error
524                     error = None
525             except ValueWarning as warning:
526                 log.debug(_('do_validation for {0}: warning in value').format(
527                     self.impl_getname()), exc_info=True)
528                 pass
529             if error is None and warning is None:
530                 try:
531                     # if context launch consistency validation
532                     if context is not None:
533                         descr._valid_consistency(self, _value, context, _index)
534                 except ValueError as error:
535                     log.debug(_('do_validation for {0}: error in consistency').format(
536                         self.impl_getname()), exc_info=True)
537                     pass
538                 except ValueWarning as warning:
539                     log.debug(_('do_validation for {0}: warning in consistency').format(
540                         self.impl_getname()), exc_info=True)
541                     pass
542             if warning:
543                 msg = _("warning on the value of the option {0}: {1}").format(
544                     self.impl_getname(), warning)
545                 warnings.warn_explicit(ValueWarning(msg, self),
546                                        ValueWarning,
547                                        self.__class__.__name__, 0)
548             elif error:
549                 raise ValueError(_("invalid value for option {0}: {1}").format(
550                     self._name, error))
551
552         # generic calculation
553         if context is not None:
554             descr = context.cfgimpl_get_description()
555
556         if not self._multi or force_index is not None:
557             do_validation(value, force_index)
558         else:
559             if not isinstance(value, list):
560                 raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self.impl_getname()))
561             for index, val in enumerate(value):
562                 do_validation(val, index)
563
564     def impl_getdefault(self):
565         "accessing the default value"
566         if isinstance(self._default, list):
567             return copy(self._default)
568         return self._default
569
570     def impl_getdefault_multi(self):
571         "accessing the default value for a multi"
572         return self._default_multi
573
574     def impl_is_master_slaves(self, type_='both'):
575         """FIXME
576         """
577         try:
578             self._master_slaves
579             if type_ in ('both', 'master') and \
580                     self._master_slaves.is_master(self):
581                 return True
582             if type_ in ('both', 'slave') and \
583                     not self._master_slaves.is_master(self):
584                 return True
585         except:
586             pass
587         return False
588
589     def impl_get_master_slaves(self):
590         return self._master_slaves
591
592     def impl_is_empty_by_default(self):
593         "no default value has been set yet"
594         if ((not self.impl_is_multi() and self._default is None) or
595                 (self.impl_is_multi() and (self._default == []
596                                            or None in self._default))):
597             return True
598         return False
599
600     def impl_getdoc(self):
601         "accesses the Option's doc"
602         return self.impl_get_information('doc')
603
604     def impl_has_callback(self):
605         "to know if a callback has been defined or not"
606         if self._callback is None:
607             return False
608         else:
609             return True
610
611     def impl_get_callback(self):
612         return self._callback, self._callback_params
613
614     #def impl_getkey(self, value):
615     #    return value
616
617     def impl_is_multi(self):
618         return self._multi
619
620     def impl_add_consistency(self, func, *other_opts, **params):
621         """Add consistency means that value will be validate with other_opts
622         option's values.
623
624         :param func: function's name
625         :type func: `str`
626         :param other_opts: options used to validate value
627         :type other_opts: `list` of `tiramisu.option.Option`
628         :param params: extra params (only warnings_only are allowed)
629         """
630         if self.impl_is_readonly():
631             raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is"
632                                    " read-only").format(
633                                        self.__class__.__name__,
634                                        self._name))
635         warnings_only = params.get('warnings_only', False)
636         for opt in other_opts:
637             if not isinstance(opt, Option):
638                 raise ConfigError(_('consistency must be set with an option'))
639             if self is opt:
640                 raise ConfigError(_('cannot add consistency with itself'))
641             if self.impl_is_multi() != opt.impl_is_multi():
642                 raise ConfigError(_('every options in consistency must be '
643                                     'multi or none'))
644         func = '_cons_{0}'.format(func)
645         all_cons_opts = tuple([self] + list(other_opts))
646         value = self.impl_getdefault()
647         if value is not None:
648             if self.impl_is_multi():
649                 for idx, val in enumerate(value):
650                     self._launch_consistency(func, self, val, None,
651                                              idx, all_cons_opts, warnings_only)
652             else:
653                 self._launch_consistency(func, self, value, None,
654                                          None, all_cons_opts, warnings_only)
655         self._add_consistency(func, all_cons_opts, params)
656         self.impl_validate(self.impl_getdefault())
657
658     def _cons_not_equal(self, opts, vals, warnings_only):
659         for idx_inf, val_inf in enumerate(vals):
660             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
661                 if val_inf == val_sup is not None:
662                     if warnings_only:
663                         msg = _("same value for {0} and {1}, should be different")
664                     else:
665                         msg = _("same value for {0} and {1}, must be different")
666                     raise ValueError(msg.format(opts[idx_inf].impl_getname(),
667                                                 opts[idx_inf + idx_sup + 1].impl_getname()))
668
669     def _impl_convert_callbacks(self, descr, load=False):
670         if not load and self._callback is None:
671             self._state_callback = None
672         elif load and self._state_callback is None:
673             self._callback = None
674             del(self._state_callback)
675         else:
676             if load:
677                 callback, callback_params = self._state_callback
678             else:
679                 callback, callback_params = self._callback
680             if callback_params is not None:
681                 cllbck_prms = {}
682                 for key, values in callback_params.items():
683                     vls = []
684                     for value in values:
685                         if isinstance(value, tuple):
686                             if load:
687                                 value = (descr.impl_get_opt_by_path(value[0]),
688                                          value[1])
689                             else:
690                                 value = (descr.impl_get_path_by_opt(value[0]),
691                                          value[1])
692                         vls.append(value)
693                     cllbck_prms[key] = tuple(vls)
694             else:
695                 cllbck_prms = None
696
697             if load:
698                 del(self._state_callback)
699                 self._callback = (callback, cllbck_prms)
700             else:
701                 self._state_callback = (callback, cllbck_prms)
702
703     # serialize/unserialize
704     def _impl_convert_consistencies(self, descr, load=False):
705         """during serialization process, many things have to be done.
706         one of them is the localisation of the options.
707         The paths are set once for all.
708
709         :type descr: :class:`tiramisu.option.OptionDescription`
710         :param load: `True` if we are at the init of the option description
711         :type load: bool
712         """
713         if not load and self._consistencies is None:
714             self._state_consistencies = None
715         elif load and self._state_consistencies is None:
716             self._consistencies = None
717             del(self._state_consistencies)
718         else:
719             if load:
720                 consistencies = self._state_consistencies
721             else:
722                 consistencies = self._consistencies
723             new_value = []
724             for consistency in consistencies:
725                 values = []
726                 for obj in consistency[1]:
727                     if load:
728                         values.append(descr.impl_get_opt_by_path(obj))
729                     else:
730                         values.append(descr.impl_get_path_by_opt(obj))
731                 new_value.append((consistency[0], tuple(values), consistency[2]))
732             if load:
733                 del(self._state_consistencies)
734                 self._consistencies = new_value
735             else:
736                 self._state_consistencies = new_value
737
738     def _second_level_validation(self, value, warnings_only):
739         pass
740
741
742 def validate_requires_arg(requires, name):
743     """check malformed requirements
744     and tranform dict to internal tuple
745
746     :param requires: have a look at the
747                      :meth:`tiramisu.setting.Settings.apply_requires` method to
748                      know more about
749                      the description of the requires dictionary
750     """
751     if requires is None:
752         return None, None
753     ret_requires = {}
754     config_action = {}
755
756     # start parsing all requires given by user (has dict)
757     # transforme it to a tuple
758     for require in requires:
759         if not type(require) == dict:
760             raise ValueError(_("malformed requirements type for option:"
761                                " {0}, must be a dict").format(name))
762         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
763                       'same_action')
764         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
765         if unknown_keys != frozenset():
766             raise ValueError('malformed requirements for option: {0}'
767                              ' unknown keys {1}, must only '
768                              '{2}'.format(name,
769                                           unknown_keys,
770                                           valid_keys))
771         # prepare all attributes
772         try:
773             option = require['option']
774             expected = require['expected']
775             action = require['action']
776         except KeyError:
777             raise ValueError(_("malformed requirements for option: {0}"
778                                " require must have option, expected and"
779                                " action keys").format(name))
780         if action == 'force_store_value':
781             raise ValueError(_("malformed requirements for option: {0}"
782                                " action cannot be force_store_value"
783                                ).format(name))
784         inverse = require.get('inverse', False)
785         if inverse not in [True, False]:
786             raise ValueError(_('malformed requirements for option: {0}'
787                                ' inverse must be boolean'))
788         transitive = require.get('transitive', True)
789         if transitive not in [True, False]:
790             raise ValueError(_('malformed requirements for option: {0}'
791                                ' transitive must be boolean'))
792         same_action = require.get('same_action', True)
793         if same_action not in [True, False]:
794             raise ValueError(_('malformed requirements for option: {0}'
795                                ' same_action must be boolean'))
796
797         if not isinstance(option, Option):
798             raise ValueError(_('malformed requirements '
799                                'must be an option in option {0}').format(name))
800         if option.impl_is_multi():
801             raise ValueError(_('malformed requirements option {0} '
802                                'must not be a multi').format(name))
803         if expected is not None:
804             try:
805                 option._validate(expected)
806             except ValueError as err:
807                 raise ValueError(_('malformed requirements second argument '
808                                    'must be valid for option {0}'
809                                    ': {1}').format(name, err))
810         if action in config_action:
811             if inverse != config_action[action]:
812                 raise ValueError(_("inconsistency in action types"
813                                    " for option: {0}"
814                                    " action: {1}").format(name, action))
815         else:
816             config_action[action] = inverse
817         if action not in ret_requires:
818             ret_requires[action] = {}
819         if option not in ret_requires[action]:
820             ret_requires[action][option] = (option, [expected], action,
821                                             inverse, transitive, same_action)
822         else:
823             ret_requires[action][option][1].append(expected)
824     # transform dict to tuple
825     ret = []
826     for opt_requires in ret_requires.values():
827         ret_action = []
828         for require in opt_requires.values():
829             ret_action.append((require[0], tuple(require[1]), require[2],
830                                require[3], require[4], require[5]))
831         ret.append(tuple(ret_action))
832     return frozenset(config_action.keys()), tuple(ret)
833
834
835 class SymLinkOption(OnlyOption):
836     #FIXME : et avec sqlalchemy ca marche vraiment ?
837     __slots__ = ('_opt',)
838     #not return _opt consistencies
839     #_consistencies = None
840
841     def __init__(self, name, opt):
842         self._name = name
843         if not isinstance(opt, Option):
844             raise ValueError(_('malformed symlinkoption '
845                                'must be an option '
846                                'for symlink {0}').format(name))
847         self._opt = opt
848         self._readonly = True
849         return super(Base, self).__init__()
850
851     def __getattr__(self, name):
852         if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'):
853             return object.__getattr__(self, name)
854         else:
855             return getattr(self._opt, name)
856
857     def _impl_getstate(self, descr):
858         super(SymLinkOption, self)._impl_getstate(descr)
859         self._state_opt = descr.impl_get_path_by_opt(self._opt)
860
861     def _impl_setstate(self, descr):
862         self._opt = descr.impl_get_opt_by_path(self._state_opt)
863         del(self._state_opt)
864         super(SymLinkOption, self)._impl_setstate(descr)
865
866     def impl_get_information(self, key, default=undefined):
867         return self._opt.impl_get_information(key, default)