better support for sqlalchemy storage
[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
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
36 submulti = 2
37
38
39 allowed_character = '[a-z\d\-_]'
40 name_regexp = re.compile(r'^[a-z]{0}*$'.format(allowed_character))
41 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
42                    'make_dict', 'unwrap_from_path', 'read_only',
43                    'read_write', 'getowner', 'set_contexts')
44
45
46 def valid_name(name):
47     "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
48     if not isinstance(name, str):  # pragma: optional cover
49         return False
50     if re.match(name_regexp, name) is not None and not name.startswith('_') \
51             and name not in forbidden_names \
52             and not name.startswith('impl_') \
53             and not name.startswith('cfgimpl_'):
54         return True
55     else:  # pragma: optional cover
56         return False
57
58
59 def validate_callback(callback, callback_params, type_):
60     if type(callback) != FunctionType:  # pragma: optional cover
61         raise ValueError(_('{0} must be a function').format(type_))
62     if callback_params is not None:
63         if not isinstance(callback_params, dict):  # pragma: optional cover
64             raise ValueError(_('{0}_params must be a dict').format(type_))
65         for key, callbacks in callback_params.items():
66             if key != '' and len(callbacks) != 1:  # pragma: optional cover
67                 raise ValueError(_("{0}_params with key {1} mustn't have "
68                                    "length different to 1").format(type_,
69                                                                    key))
70             if not isinstance(callbacks, tuple):  # pragma: optional cover
71                 raise ValueError(_('{0}_params must be tuple for key "{1}"'
72                                    ).format(type_, key))
73             for callbk in callbacks:
74                 if isinstance(callbk, tuple):
75                     if len(callbk) == 1:
76                         if callbk != (None,):  # pragma: optional cover
77                             raise ValueError(_('{0}_params with length of '
78                                                'tuple as 1 must only have '
79                                                'None as first value'))
80                     elif len(callbk) != 2:  # pragma: optional cover
81                         raise ValueError(_('{0}_params must only have 1 or 2 '
82                                            'as length'))
83                     else:
84                         option, force_permissive = callbk
85                         if type_ == 'validator' and not force_permissive:  # pragma: optional cover
86                             raise ValueError(_('validator not support tuple'))
87                         if not isinstance(option, Option) and not \
88                                 isinstance(option, SymLinkOption):  # pragma: optional cover
89                             raise ValueError(_('{0}_params must have an option'
90                                                ' not a {0} for first argument'
91                                                ).format(type_, type(option)))
92                         if force_permissive not in [True, False]:  # pragma: optional cover
93                             raise ValueError(_('{0}_params must have a boolean'
94                                                ' not a {0} for second argument'
95                                                ).format(type_, type(
96                                                    force_permissive)))
97 #____________________________________________________________
98 #
99
100
101 class Base(StorageBase):
102     __slots__ = tuple()
103
104     def impl_set_callback(self, callback, callback_params=None):
105         if callback is None and callback_params is not None:  # pragma: optional cover
106             raise ValueError(_("params defined for a callback function but "
107                                "no callback defined"
108                                " yet for option {0}").format(
109                                    self.impl_getname()))
110         self._validate_callback(callback, callback_params)
111         if callback is not None:
112             validate_callback(callback, callback_params, 'callback')
113             self._callback = callback
114             if callback_params is None:
115                 self._callback_params = {}
116             else:
117                 self._callback_params = callback_params
118
119     def __init__(self, name, doc, default=None, default_multi=None,
120                  requires=None, multi=False, callback=None,
121                  callback_params=None, validator=None, validator_params=None,
122                  properties=None, warnings_only=False):
123         if not valid_name(name):  # pragma: optional cover
124             raise ValueError(_("invalid name: {0} for option").format(name))
125         self._name = name
126         self._readonly = False
127         self._informations = {}
128         self.impl_set_information('doc', doc)
129         if requires is not None:
130             self._calc_properties, self._requires = validate_requires_arg(
131                 requires, self._name)
132         else:
133             self._calc_properties = frozenset()
134             self._requires = []
135         if not multi and default_multi is not None:  # pragma: optional cover
136             raise ValueError(_("a default_multi is set whereas multi is False"
137                              " in option: {0}").format(name))
138         if default_multi is not None:
139             try:
140                 self._validate(default_multi)
141             except ValueError as err:  # pragma: optional cover
142                 raise ValueError(_("invalid default_multi value {0} "
143                                    "for option {1}: {2}").format(
144                                        str(default_multi), name, err))
145         if multi is True:
146             self._multi = 0
147         elif multi is False:
148             self._multi = 1
149         elif multi is submulti:
150             self._multi = submulti
151         if self._multi != 1:
152             if default is None:
153                 default = []
154             self._default_multi = default_multi
155         if properties is None:
156             properties = tuple()
157         if not isinstance(properties, tuple):  # pragma: optional cover
158             raise TypeError(_('invalid properties type {0} for {1},'
159                             ' must be a tuple').format(
160                                 type(properties),
161                                 self._name))
162         if validator is not None:
163             validate_callback(validator, validator_params, 'validator')
164             self._validator = validator
165             if validator_params is not None:
166                 self._validator_params = validator_params
167         if self._calc_properties != frozenset([]) and properties is not tuple():  # pragma: optional cover
168             set_forbidden_properties = self._calc_properties & set(properties)
169             if set_forbidden_properties != frozenset():
170                 raise ValueError('conflict: properties already set in '
171                                  'requirement {0}'.format(
172                                      list(set_forbidden_properties)))
173         if multi and default is None:
174             self._default = []
175         else:
176             self._default = default
177         if callback is not False:
178             self.impl_set_callback(callback, callback_params)
179         self._properties = properties
180         self._warnings_only = warnings_only
181         ret = super(Base, self).__init__()
182         self.impl_validate(self._default)
183         return ret
184
185     def impl_is_optiondescription(self):
186         return self.__class__.__name__ in ['OptionDescription',
187                                            'DynOptionDescription',
188                                            'SynDynOptionDescription']
189
190     def impl_is_dynoptiondescription(self):
191         return self.__class__.__name__ in ['DynOptionDescription',
192                                            'SynDynOptionDescription']
193
194
195 class BaseOption(Base):
196     """This abstract base class stands for attribute access
197     in options that have to be set only once, it is of course done in the
198     __setattr__ method
199     """
200     __slots__ = tuple()
201
202     # information
203     def impl_set_information(self, key, value):
204         """updates the information's attribute
205         (which is a dictionary)
206
207         :param key: information's key (ex: "help", "doc"
208         :param value: information's value (ex: "the help string")
209         """
210         self._informations[key] = value
211
212     def impl_get_information(self, key, default=undefined):
213         """retrieves one information's item
214
215         :param key: the item string (ex: "help")
216         """
217         if key in self._informations:
218             return self._informations[key]
219         elif default is not undefined:  # pragma: optional cover
220             return default
221         else:  # pragma: optional cover
222             raise ValueError(_("information's item not found: {0}").format(
223                 key))
224
225     # ____________________________________________________________
226     # serialize object
227     def _impl_convert_requires(self, descr, load=False):
228         """export of the requires during the serialization process
229
230         :type descr: :class:`tiramisu.option.OptionDescription`
231         :param load: `True` if we are at the init of the option description
232         :type load: bool
233         """
234         if not load and self._requires is None:
235             self._state_requires = None
236         elif load and self._state_requires is None:
237             self._requires = None
238             del(self._state_requires)
239         else:
240             if load:
241                 _requires = self._state_requires
242             else:
243                 _requires = self._requires
244             new_value = []
245             for requires in _requires:
246                 new_requires = []
247                 for require in requires:
248                     if load:
249                         new_require = [descr.impl_get_opt_by_path(require[0])]
250                     else:
251                         new_require = [descr.impl_get_path_by_opt(require[0])]
252                     new_require.extend(require[1:])
253                     new_requires.append(tuple(new_require))
254                 new_value.append(tuple(new_requires))
255             if load:
256                 del(self._state_requires)
257                 self._requires = new_value
258             else:
259                 self._state_requires = new_value
260
261     def _impl_convert_callback(self, descr, load=False):
262         if self.__class__.__name__ == 'OptionDescription' or \
263                 isinstance(self, SymLinkOption):
264             return
265         if not load and self._callback is None:
266             self._state_callback = None
267             self._state_callback_params = {}
268         elif load and self._state_callback is None:
269             self._callback = None
270             self._callback_params = {}
271             del(self._state_callback)
272             del(self._state_callback_params)
273         else:
274             if load:
275                 callback = self._state_callback
276                 callback_params = self._state_callback_params
277             else:
278                 callback = self._callback
279                 callback_params = self._callback_params
280                 self._state_callback_params = {}
281             if callback_params is not None:
282                 cllbck_prms = {}
283                 for key, values in callback_params.items():
284                     vls = []
285                     for value in values:
286                         if isinstance(value, tuple):
287                             if load:
288                                 value = (descr.impl_get_opt_by_path(value[0]),
289                                          value[1])
290                             else:
291                                 value = (descr.impl_get_path_by_opt(value[0]),
292                                          value[1])
293                         vls.append(value)
294                     cllbck_prms[key] = tuple(vls)
295             else:
296                 cllbck_prms = None
297
298             if load:
299                 del(self._state_callback)
300                 del(self._state_callback_params)
301                 self._callback = callback
302                 self._callback_params = cllbck_prms
303             else:
304                 self._state_callback = callback
305                 self._state_callback_params = cllbck_prms
306
307     # serialize
308     def _impl_getstate(self, descr):
309         """the under the hood stuff that need to be done
310         before the serialization.
311
312         :param descr: the parent :class:`tiramisu.option.OptionDescription`
313         """
314         self._stated = True
315         for func in dir(self):
316             if func.startswith('_impl_convert_'):
317                 getattr(self, func)(descr)
318         self._state_readonly = self._readonly
319
320     def __getstate__(self, stated=True):
321         """special method to enable the serialization with pickle
322         Usualy, a `__getstate__` method does'nt need any parameter,
323         but somme under the hood stuff need to be done before this action
324
325         :parameter stated: if stated is `True`, the serialization protocol
326                            can be performed, not ready yet otherwise
327         :parameter type: bool
328         """
329         try:
330             self._stated
331         except AttributeError:  # pragma: optional cover
332             raise SystemError(_('cannot serialize Option, '
333                                 'only in OptionDescription'))
334         slots = set()
335         for subclass in self.__class__.__mro__:
336             if subclass is not object:
337                 slots.update(subclass.__slots__)
338         slots -= frozenset(['_cache_paths', '_cache_consistencies',
339                             '__weakref__'])
340         states = {}
341         for slot in slots:
342             # remove variable if save variable converted
343             # in _state_xxxx variable
344             if '_state' + slot not in slots:
345                 try:
346                     if slot.startswith('_state'):
347                         states[slot] = getattr(self, slot)
348                         # remove _state_xxx variable
349                         self.__delattr__(slot)
350                     else:
351                         states[slot] = getattr(self, slot)
352                 except AttributeError:
353                     pass
354         if not stated:
355             del(states['_stated'])
356         return states
357
358     # unserialize
359     def _impl_setstate(self, descr):
360         """the under the hood stuff that need to be done
361         before the serialization.
362
363         :type descr: :class:`tiramisu.option.OptionDescription`
364         """
365         for func in dir(self):
366             if func.startswith('_impl_convert_'):
367                 getattr(self, func)(descr, load=True)
368         try:
369             self._readonly = self._state_readonly
370             del(self._state_readonly)
371             del(self._stated)
372         except AttributeError:  # pragma: optional cover
373             pass
374
375     def __setstate__(self, state):
376         """special method that enables us to serialize (pickle)
377
378         Usualy, a `__setstate__` method does'nt need any parameter,
379         but somme under the hood stuff need to be done before this action
380
381         :parameter state: a dict is passed to the loads, it is the attributes
382                           of the options object
383         :type state: dict
384         """
385         for key, value in state.items():
386             setattr(self, key, value)
387
388     def __setattr__(self, name, value):
389         """set once and only once some attributes in the option,
390         like `_name`. `_name` cannot be changed one the option and
391         pushed in the :class:`tiramisu.option.OptionDescription`.
392
393         if the attribute `_readonly` is set to `True`, the option is
394         "frozen" (which has noting to do with the high level "freeze"
395         propertie or "read_only" property)
396         """
397         if name not in ('_option', '_is_build_cache') \
398                 and not isinstance(value, tuple) and \
399                 not name.startswith('_state'):
400             is_readonly = False
401             # never change _name
402             if name == '_name':
403                 try:
404                     if self._name is not None:
405                         #so _name is already set
406                         is_readonly = True
407                 except (KeyError, AttributeError):
408                     pass
409             elif name != '_readonly':
410                 is_readonly = self.impl_is_readonly()
411             if is_readonly:  # pragma: optional cover
412                 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
413                                        " read-only").format(
414                                            self.__class__.__name__,
415                                            self._name,
416                                            name))
417         super(BaseOption, self).__setattr__(name, value)
418
419     def impl_is_readonly(self):
420         try:
421             if self._readonly is True:
422                 return True
423         except AttributeError:
424             pass
425         return False
426
427     def impl_getname(self):
428         return self._name
429
430     def impl_getpath(self, context):
431         return context.cfgimpl_get_description().impl_get_path_by_opt(self)
432
433     def impl_get_callback(self):
434         return self._callback, self._callback_params
435
436     def impl_has_callback(self):
437         "to know if a callback has been defined or not"
438         return self._callback is not None
439
440     def _is_subdyn(self):
441         try:
442             return self._subdyn is not None
443         except AttributeError:
444             return False
445
446     def impl_getproperties(self):
447         return self._properties
448
449
450 class OnlyOption(BaseOption):
451     __slots__ = tuple()
452
453
454 class Option(OnlyOption):
455     """
456     Abstract base class for configuration option's.
457
458     Reminder: an Option object is **not** a container for the value.
459     """
460 #    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
461 #                 '_state_callback', '_callback',
462 #                 '_consistencies', '_warnings_only', '_master_slaves',
463 #                 '_state_consistencies', '__weakref__')
464     __slots__ = tuple()
465     _empty = ''
466
467     def __init__(self, name, doc, default=None, default_multi=None,
468                  requires=None, multi=False, callback=None,
469                  callback_params=None, validator=None, validator_params=None,
470                  properties=None, warnings_only=False):
471         """
472         :param name: the option's name
473         :param doc: the option's description
474         :param default: specifies the default value of the option,
475                         for a multi : ['bla', 'bla', 'bla']
476         :param default_multi: 'bla' (used in case of a reset to default only at
477                         a given index)
478         :param requires: is a list of names of options located anywhere
479                          in the configuration.
480         :param multi: if true, the option's value is a list
481         :param callback: the name of a function. If set, the function's output
482                          is responsible of the option's value
483         :param callback_params: the callback's parameter
484         :param validator: the name of a function which stands for a custom
485                           validation of the value
486         :param validator_params: the validator's parameters
487         :param properties: tuple of default properties
488         :param warnings_only: _validator and _consistencies don't raise if True
489                              Values()._warning contain message
490
491         """
492         super(Option, self).__init__(name, doc, default, default_multi,
493                                      requires, multi, callback,
494                                      callback_params, validator,
495                                      validator_params, properties,
496                                      warnings_only)
497
498     def impl_getrequires(self):
499         return self._requires
500
501     def _launch_consistency(self, func, option, value, context, index,
502                             submulti_index, all_cons_opts, warnings_only):
503         """Launch consistency now
504
505         :param func: function name, this name should start with _cons_
506         :type func: `str`
507         :param option: option that value is changing
508         :type option: `tiramisu.option.Option`
509         :param value: new value of this option
510         :param context: Config's context, if None, check default value instead
511         :type context: `tiramisu.config.Config`
512         :param index: only for multi option, consistency should be launch for
513                       specified index
514         :type index: `int`
515         :param all_cons_opts: all options concerne by this consistency
516         :type all_cons_opts: `list` of `tiramisu.option.Option`
517         :param warnings_only: specific raise error for warning
518         :type warnings_only: `boolean`
519         """
520         if context is not undefined:
521             descr = context.cfgimpl_get_description()
522
523         all_cons_vals = []
524         for opt in all_cons_opts:
525             #get value
526             if (isinstance(opt, DynSymLinkOption) and option._dyn == opt._dyn) or \
527                     option == opt:
528                 opt_value = value
529             else:
530                 #if context, calculate value, otherwise get default value
531                 if context is not undefined:
532                     if isinstance(opt, DynSymLinkOption):
533                         path = opt.impl_getpath(context)
534                     else:
535                         path = descr.impl_get_path_by_opt(opt)
536                     opt_value = context.getattr(path, validate=False,
537                                                 force_permissive=True)
538                 else:
539                     opt_value = opt.impl_getdefault()
540
541             #append value
542             if not self.impl_is_multi() or (isinstance(opt, DynSymLinkOption)
543                                             and option._dyn == opt._dyn) or \
544                     option == opt:
545                 all_cons_vals.append(opt_value)
546             elif self.impl_is_submulti():
547                 try:
548                     all_cons_vals.append(opt_value[index][submulti_index])
549                 except IndexError:
550                     #value is not already set, could be higher index
551                     #so return if no value
552                     return
553             else:
554                 try:
555                     all_cons_vals.append(opt_value[index])
556                 except IndexError:
557                     #value is not already set, could be higher index
558                     #so return if no value
559                     return
560         getattr(self, func)(all_cons_opts, all_cons_vals, warnings_only)
561
562     def impl_validate(self, value, context=undefined, validate=True,
563                       force_index=None, force_submulti_index=None,
564                       current_opt=undefined):
565         """
566         :param value: the option's value
567         :param context: Config's context
568         :type context: :class:`tiramisu.config.Config`
569         :param validate: if true enables ``self._validator`` validation
570         :type validate: boolean
571         :param force_index: if multi, value has to be a list
572                             not if force_index is not None
573         :type force_index: integer
574         :param force_submulti_index: if submulti, value has to be a list
575                                      not if force_submulti_index is not None
576         :type force_submulti_index: integer
577         """
578         if not validate:
579             return
580         if current_opt is undefined:
581             current_opt = self
582
583         def val_validator(val):
584             if self._validator is not None:
585                 if self._validator_params is not None:
586                     validator_params = {}
587                     for val_param, values in self._validator_params.items():
588                         validator_params[val_param] = values
589                     #inject value in calculation
590                     if '' in validator_params:
591                         lst = list(validator_params[''])
592                         lst.insert(0, val)
593                         validator_params[''] = tuple(lst)
594                     else:
595                         validator_params[''] = (val,)
596                 else:
597                     validator_params = {'': (val,)}
598                 # Raise ValueError if not valid
599                 carry_out_calculation(self, config=context,
600                                       callback=self._validator,
601                                       callback_params=validator_params)
602
603         def do_validation(_value, _index, submulti_index):
604             if _value is None:
605                 return
606             # option validation
607             try:
608                 self._validate(_value, context)
609             except ValueError as err:  # pragma: optional cover
610                 log.debug('do_validation: value: {0}, index: {1}, '
611                           'submulti_index: {2}'.format(_value, _index,
612                                                        submulti_index),
613                           exc_info=True)
614                 raise ValueError(_('invalid value for option {0}: {1}'
615                                    '').format(self.impl_getname(), err))
616             error = None
617             warning = None
618             try:
619                 # valid with self._validator
620                 val_validator(_value)
621                 # if not context launch consistency validation
622                 if context is not undefined:
623                     descr._valid_consistency(current_opt, _value, context,
624                                              _index, submulti_index)
625                 self._second_level_validation(_value, self._warnings_only)
626             except ValueError as error:
627                 log.debug(_('do_validation for {0}: error in value').format(
628                     self.impl_getname()), exc_info=True)
629                 if self._warnings_only:
630                     warning = error
631                     error = None
632             except ValueWarning as warning:
633                 log.debug(_('do_validation for {0}: warning in value').format(
634                     self.impl_getname()), exc_info=True)
635                 pass
636             if error is None and warning is None:
637                 try:
638                     # if context launch consistency validation
639                     if context is not undefined:
640                         descr._valid_consistency(current_opt, _value, context,
641                                                  _index, submulti_index)
642                 except ValueError as error:
643                     log.debug(_('do_validation for {0}: error in consistency').format(
644                         self.impl_getname()), exc_info=True)
645                     pass
646                 except ValueWarning as warning:
647                     log.debug(_('do_validation for {0}: warning in consistency').format(
648                         self.impl_getname()), exc_info=True)
649                     pass
650             if warning:
651                 msg = _("warning on the value of the option {0}: {1}").format(
652                     self.impl_getname(), warning)
653                 warnings.warn_explicit(ValueWarning(msg, self),
654                                        ValueWarning,
655                                        self.__class__.__name__, 0)
656             elif error:
657                 raise ValueError(_("invalid value for option {0}: {1}").format(
658                     self._name, error))
659
660         # generic calculation
661         if context is not undefined:
662             descr = context.cfgimpl_get_description()
663
664         if not self.impl_is_multi():
665             do_validation(value, None, None)
666         elif force_index is not None:
667             if self.impl_is_submulti() and force_submulti_index is None:
668                 if not isinstance(value, list):  # pragma: optional cover
669                     raise ValueError(_("invalid value {0} for option {1} which"
670                                        " must be a list").format(
671                                            value, self.impl_getname()))
672                 for idx, val in enumerate(value):
673                     do_validation(val, force_index, idx)
674             else:
675                 do_validation(value, force_index, force_submulti_index)
676         else:
677             if not isinstance(value, list):  # pragma: optional cover
678                 raise ValueError(_("invalid value {0} for option {1} which "
679                                    "must be a list").format(value,
680                                                             self.impl_getname()))
681             for idx, val in enumerate(value):
682                 if self.impl_is_submulti() and force_submulti_index is None:
683                     if not isinstance(val, list):  # pragma: optional cover
684                         raise ValueError(_("invalid value {0} for option {1} "
685                                            "which must be a list of list"
686                                            "").format(value,
687                                                       self.impl_getname()))
688                     for slave_idx, slave_val in enumerate(val):
689                         do_validation(slave_val, idx, slave_idx)
690                 else:
691                     do_validation(val, idx, force_submulti_index)
692
693     def impl_getdefault(self):
694         "accessing the default value"
695         if isinstance(self._default, list):
696             return copy(self._default)
697         return self._default
698
699     def impl_getdefault_multi(self):
700         "accessing the default value for a multi"
701         return self._default_multi
702
703     def impl_is_master_slaves(self, type_='both'):
704         """FIXME
705         """
706         try:
707             self._master_slaves
708             if type_ in ('both', 'master') and \
709                     self._master_slaves.is_master(self):
710                 return True
711             if type_ in ('both', 'slave') and \
712                     not self._master_slaves.is_master(self):
713                 return True
714         except:
715             pass
716         return False
717
718     def impl_get_master_slaves(self):
719         return self._master_slaves
720
721     def impl_is_empty_by_default(self):
722         "no default value has been set yet"
723         if ((not self.impl_is_multi() and self._default is None) or
724                 (self.impl_is_multi() and (self._default == []
725                                            or None in self._default))):
726             return True
727         return False
728
729     def impl_getdoc(self):
730         "accesses the Option's doc"
731         return self.impl_get_information('doc')
732
733     #def impl_getkey(self, value):
734     #    return value
735
736     def impl_is_multi(self):
737         return self._multi == 0 or self._multi is submulti
738
739     def impl_is_submulti(self):
740         return self._multi is submulti
741
742     def impl_add_consistency(self, func, *other_opts, **params):
743         """Add consistency means that value will be validate with other_opts
744         option's values.
745
746         :param func: function's name
747         :type func: `str`
748         :param other_opts: options used to validate value
749         :type other_opts: `list` of `tiramisu.option.Option`
750         :param params: extra params (only warnings_only are allowed)
751         """
752         if self.impl_is_readonly():  # pragma: optional cover
753             raise AttributeError(_("'{0}' ({1}) cannont add consistency, option is"
754                                    " read-only").format(
755                                        self.__class__.__name__,
756                                        self._name))
757         warnings_only = params.get('warnings_only', False)
758         if self._is_subdyn():
759             dynod = self._impl_getsubdyn()
760         else:
761             dynod = None
762         for opt in other_opts:
763             if not isinstance(opt, Option):  # pragma: optional cover
764                 raise ConfigError(_('consistency must be set with an option'))
765             if opt._is_subdyn():
766                 if dynod is None:
767                     raise ConfigError(_('almost one option in consistency is '
768                                         'in a dynoptiondescription but not all'))
769                 if dynod != opt._impl_getsubdyn():
770                     raise ConfigError(_('option in consistency must be in same'
771                                         ' dynoptiondescription'))
772                 dynod = opt._impl_getsubdyn()
773             elif dynod is not None:
774                 raise ConfigError(_('almost one option in consistency is in a '
775                                     'dynoptiondescription but not all'))
776             if self is opt:  # pragma: optional cover
777                 raise ConfigError(_('cannot add consistency with itself'))
778             if self.impl_is_multi() != opt.impl_is_multi():  # pragma: optional cover
779                 raise ConfigError(_('every options in consistency must be '
780                                     'multi or none'))
781         func = '_cons_{0}'.format(func)
782         all_cons_opts = tuple([self] + list(other_opts))
783         value = self.impl_getdefault()
784         if value is not None:
785             if self.impl_is_multi():
786                 for idx, val in enumerate(value):
787                     if not self.impl_is_submulti():
788                         self._launch_consistency(func, self, val, undefined, idx,
789                                                  None, all_cons_opts,
790                                                  warnings_only)
791                     else:
792                         for slave_idx, val in enumerate(value):
793                             self._launch_consistency(func, self, val, None,
794                                                      idx, slave_idx,
795                                                      all_cons_opts,
796                                                      warnings_only)
797             else:
798                 self._launch_consistency(func, self, value, undefined, None,
799                                          None, all_cons_opts, warnings_only)
800         self._add_consistency(func, all_cons_opts, params)
801         self.impl_validate(self.impl_getdefault())
802
803     def _cons_not_equal(self, opts, vals, warnings_only):
804         for idx_inf, val_inf in enumerate(vals):
805             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
806                 if val_inf == val_sup is not None:
807                     if warnings_only:
808                         msg = _("same value for {0} and {1}, should be different")
809                     else:
810                         msg = _("same value for {0} and {1}, must be different")
811                     raise ValueError(msg.format(opts[idx_inf].impl_getname(),
812                                                 opts[idx_inf + idx_sup + 1].impl_getname()))
813
814     # serialize/unserialize
815     def _impl_convert_consistencies(self, descr, load=False):
816         """during serialization process, many things have to be done.
817         one of them is the localisation of the options.
818         The paths are set once for all.
819
820         :type descr: :class:`tiramisu.option.OptionDescription`
821         :param load: `True` if we are at the init of the option description
822         :type load: bool
823         """
824         if not load and self._consistencies is None:
825             self._state_consistencies = None
826         elif load and self._state_consistencies is None:
827             self._consistencies = None
828             del(self._state_consistencies)
829         else:
830             if load:
831                 consistencies = self._state_consistencies
832             else:
833                 consistencies = self._consistencies
834             new_value = []
835             for consistency in consistencies:
836                 values = []
837                 for obj in consistency[1]:
838                     if load:
839                         values.append(descr.impl_get_opt_by_path(obj))
840                     else:
841                         values.append(descr.impl_get_path_by_opt(obj))
842                 new_value.append((consistency[0], tuple(values), consistency[2]))
843             if load:
844                 del(self._state_consistencies)
845                 self._consistencies = new_value
846             else:
847                 self._state_consistencies = new_value
848
849     def _second_level_validation(self, value, warnings_only):
850         pass
851
852     def _impl_to_dyn(self, name, path):
853         return DynSymLinkOption(name, self, dyn=path)
854
855     def _validate_callback(self, callback, callback_params):
856         try:
857             default_multi = self._default_multi
858         except AttributeError:
859             default_multi = None
860         if callback is not None and ((self._multi == 1 and
861                                       (self._default is not None or
862                                        default_multi is not None))
863                                      or (self._multi != 1 and
864                                          (self._default != [] or
865                                           default_multi is not None))
866                                      ):  # pragma: optional cover
867             raise ValueError(_("default value not allowed if option: {0} "
868                              "is calculated").format(self.impl_getname()))
869
870
871 def validate_requires_arg(requires, name):
872     """check malformed requirements
873     and tranform dict to internal tuple
874
875     :param requires: have a look at the
876                      :meth:`tiramisu.setting.Settings.apply_requires` method to
877                      know more about
878                      the description of the requires dictionary
879     """
880     if requires is None:
881         return None, None
882     ret_requires = {}
883     config_action = {}
884
885     # start parsing all requires given by user (has dict)
886     # transforme it to a tuple
887     for require in requires:
888         if not type(require) == dict:  # pragma: optional cover
889             raise ValueError(_("malformed requirements type for option:"
890                                " {0}, must be a dict").format(name))
891         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
892                       'same_action')
893         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
894         if unknown_keys != frozenset():  # pragma: optional cover
895             raise ValueError(_('malformed requirements for option: {0}'
896                              ' unknown keys {1}, must only '
897                              '{2}').format(name,
898                                            unknown_keys,
899                                            valid_keys))
900         # prepare all attributes
901         try:
902             option = require['option']
903             expected = require['expected']
904             action = require['action']
905         except KeyError:  # pragma: optional cover
906             raise ValueError(_("malformed requirements for option: {0}"
907                                " require must have option, expected and"
908                                " action keys").format(name))
909         if action == 'force_store_value':  # pragma: optional cover
910             raise ValueError(_("malformed requirements for option: {0}"
911                                " action cannot be force_store_value"
912                                ).format(name))
913         inverse = require.get('inverse', False)
914         if inverse not in [True, False]:  # pragma: optional cover
915             raise ValueError(_('malformed requirements for option: {0}'
916                                ' inverse must be boolean'))
917         transitive = require.get('transitive', True)
918         if transitive not in [True, False]:  # pragma: optional cover
919             raise ValueError(_('malformed requirements for option: {0}'
920                                ' transitive must be boolean'))
921         same_action = require.get('same_action', True)
922         if same_action not in [True, False]:  # pragma: optional cover
923             raise ValueError(_('malformed requirements for option: {0}'
924                                ' same_action must be boolean'))
925
926         if not isinstance(option, Option):  # pragma: optional cover
927             raise ValueError(_('malformed requirements '
928                                'must be an option in option {0}').format(name))
929         if option.impl_is_multi():  # pragma: optional cover
930             raise ValueError(_('malformed requirements option {0} '
931                                'must not be a multi for {1}').format(
932                                    option.impl_getname(), name))
933         if expected is not None:
934             try:
935                 option._validate(expected)
936             except ValueError as err:  # pragma: optional cover
937                 raise ValueError(_('malformed requirements second argument '
938                                    'must be valid for option {0}'
939                                    ': {1}').format(name, err))
940         if action in config_action:
941             if inverse != config_action[action]:  # pragma: optional cover
942                 raise ValueError(_("inconsistency in action types"
943                                    " for option: {0}"
944                                    " action: {1}").format(name, action))
945         else:
946             config_action[action] = inverse
947         if action not in ret_requires:
948             ret_requires[action] = {}
949         if option not in ret_requires[action]:
950             ret_requires[action][option] = (option, [expected], action,
951                                             inverse, transitive, same_action)
952         else:
953             ret_requires[action][option][1].append(expected)
954     # transform dict to tuple
955     ret = []
956     for opt_requires in ret_requires.values():
957         ret_action = []
958         for require in opt_requires.values():
959             ret_action.append((require[0], tuple(require[1]), require[2],
960                                require[3], require[4], require[5]))
961         ret.append(tuple(ret_action))
962     return frozenset(config_action.keys()), tuple(ret)
963
964
965 class SymLinkOption(OnlyOption):
966     __slots__ = ('_opt', '_state_opt')
967
968     def __init__(self, name, opt):
969         self._name = name
970         if not isinstance(opt, Option):  # pragma: optional cover
971             raise ValueError(_('malformed symlinkoption '
972                                'must be an option '
973                                'for symlink {0}').format(name))
974         self._opt = opt
975         self._readonly = True
976         super(Base, self).__init__()
977
978     def __getattr__(self, name, context=undefined):
979         if name in ('_opt', '_opt_type', '_readonly', 'impl_getpath'):
980             return object.__getattr__(self, name)
981         else:
982             return getattr(self._opt, name)
983
984     def _impl_getstate(self, descr):
985         super(SymLinkOption, self)._impl_getstate(descr)
986         self._state_opt = descr.impl_get_path_by_opt(self._opt)
987
988     def _impl_setstate(self, descr):
989         self._opt = descr.impl_get_opt_by_path(self._state_opt)
990         del(self._state_opt)
991         super(SymLinkOption, self)._impl_setstate(descr)
992
993     def impl_get_information(self, key, default=undefined):
994         return self._opt.impl_get_information(key, default)
995
996 #FIXME utile tout ca ? c'est un peu de la duplication ...
997     def impl_getproperties(self):
998         return self._opt._properties
999
1000     def impl_get_callback(self):
1001         return self._opt._callback, self._opt._callback_params
1002
1003     def impl_has_callback(self):
1004         "to know if a callback has been defined or not"
1005         return self._opt._callback is not None
1006
1007     def _is_subdyn(self):
1008         try:
1009             return self._opt._subdyn is not None
1010         except AttributeError:
1011             return False
1012
1013
1014 class DynSymLinkOption(SymLinkOption):
1015     __slots__ = ('_dyn',)
1016
1017     def __init__(self, name, opt, dyn):
1018         self._dyn = dyn
1019         super(DynSymLinkOption, self).__init__(name, opt)
1020
1021     def impl_getsuffix(self):
1022         return self._dyn.split('.')[-1][len(self._opt.impl_getname()):]
1023
1024     def impl_getpath(self, context):
1025         path = self._opt.impl_getpath(context)
1026         base_path = '.'.join(path.split('.')[:-2])
1027         if self.impl_is_master_slaves() and base_path is not '':
1028             base_path = base_path + self.impl_getsuffix()
1029         if base_path == '':
1030             return self._dyn
1031         else:
1032             return base_path + '.' + self._dyn
1033
1034     def impl_validate(self, value, context=undefined, validate=True,
1035                       force_index=None, force_submulti_index=None):
1036         return self._opt.impl_validate(value, context, validate, force_index,
1037                                        force_submulti_index, current_opt=self)