7de14a228cd0003c63b4cdf67763f927ae9668ec
[tiramisu.git] / tiramisu / option.py
1 # -*- coding: utf-8 -*-
2 "option types and option description"
3 # Copyright (C) 2012-2013 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 # The original `Config` design model is unproudly borrowed from
20 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
21 # the whole pypy projet is under MIT licence
22 # ____________________________________________________________
23 import re
24 import sys
25 from copy import copy
26 from types import FunctionType
27 from IPy import IP
28 import warnings
29 #from pickle import loads, dumps
30
31 from sqlalchemy.ext.declarative import declarative_base
32 from sqlalchemy import create_engine, Column, Integer, String, Boolean, \
33     PickleType, ForeignKey, Table
34 from sqlalchemy.orm import relationship, backref
35 from sqlalchemy.orm import sessionmaker
36
37 from tiramisu.error import ConfigError, ConflictError, ValueWarning
38 from tiramisu.setting import groups, multitypes
39 from tiramisu.i18n import _
40 from tiramisu.autolib import carry_out_calculation
41
42 name_regexp = re.compile(r'^\d+')
43 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
44                    'make_dict', 'unwrap_from_path', 'read_only',
45                    'read_write', 'getowner', 'set_contexts')
46
47
48 def valid_name(name):
49     "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
50     if not isinstance(name, str):
51         return False
52     if re.match(name_regexp, name) is None and not name.startswith('_') \
53             and name not in forbidden_names \
54             and not name.startswith('impl_') \
55             and not name.startswith('cfgimpl_'):
56         return True
57     else:
58         return False
59 #____________________________________________________________
60 #
61
62 engine = create_engine('sqlite:///:memory:')
63 Base = declarative_base()
64
65
66 class _RequireExpected(Base):
67     __tablename__ = 'expected'
68     id = Column(Integer, primary_key=True)
69     expected = Column(PickleType)
70     require = Column(Integer, ForeignKey('require.id'))
71
72     def __init__(self, expected):
73         self.expected = expected
74
75
76 class _RequireOption(Base):
77     __tablename__ = 'require'
78     id = Column(Integer, primary_key=True)
79     option = Column(Integer, ForeignKey('baseoption.id'))
80     r_opt = Column(Integer)
81     expected = relationship("_RequireExpected")
82     action = Column(String, nullable=False)
83     inverse = Column(Boolean, default=False)
84     transitive = Column(Boolean, default=True)
85     same_action = Column(Boolean, default=True)
86
87     def __init__(self, option, expected, action, inverse, transitive,
88                  same_action):
89         self.r_opt = option.id
90         for expect in expected:
91             self.expected.append(_RequireExpected(expect))
92         self.action = action
93         self.inverse = inverse
94         self.transitive = transitive
95         self.same_action = same_action
96
97     def get_expected(self):
98         for expected in self.expected:
99             yield(expected.expected)
100
101     def get_option(self, config):
102         return config.cfgimpl_get_description().impl_get_opt_by_id(self.r_opt)
103
104
105 property_table = Table('property', Base.metadata,
106                        Column('left_id', Integer, ForeignKey('propertyoption.name')),
107                        Column('right_id', Integer, ForeignKey('baseoption.id'))
108                        )
109
110
111 class _PropertyOption(Base):
112     __tablename__ = 'propertyoption'
113     name = Column(String, primary_key=True)
114
115     def __init__(self, name):
116         self.name = name
117
118
119 class _Information(Base):
120     __tablename__ = 'information'
121     id = Column(Integer, primary_key=True)
122     option = Column(Integer, ForeignKey('baseoption.id'))
123     key = Column(String)
124     value = Column(PickleType)
125
126     def __init__(self, key, value):
127         self.key = key
128         self.value = value
129
130
131 class _CallbackParamOption(Base):
132     __tablename__ = 'callback_param_option'
133     id = Column(Integer, primary_key=True)
134     callback_param = Column(Integer, ForeignKey('callback_param.id'))
135     option = Column(Integer)
136     force_permissive = Column(Boolean)
137     value = Column(PickleType)
138
139     def __init__(self, option=None, force_permissive=None,  value=None):
140         if value is not None:
141             self.value = value
142         else:
143             if isinstance(option, SymLinkOption):
144                 option = option._opt
145             self.option = option.id
146             self.force_permissive = force_permissive
147
148     def get_option(self, config):
149         return config.cfgimpl_get_description().impl_get_opt_by_id(self.option)
150
151
152 class _CallbackParam(Base):
153     __tablename__ = 'callback_param'
154     id = Column(Integer, primary_key=True)
155     callback = Column(Integer, ForeignKey('baseoption.id'))
156     name = Column(String)
157     params = relationship('_CallbackParamOption')
158
159     def __init__(self, name, params):
160         self.name = name
161         for param in params:
162             if isinstance(param, tuple):
163                 self.params.append(_CallbackParamOption(option=param[0],
164                                                         force_permissive=param[1]))
165             else:
166                 self.params.append(_CallbackParamOption(value=param))
167
168
169 consistency_table = Table('consistencyopt', Base.metadata,
170                           Column('left_id', Integer, ForeignKey('consistency.id')),
171                           Column('right_id', Integer, ForeignKey('baseoption.id'))
172                           )
173
174
175 class _Consistency(Base):
176     __tablename__ = 'consistency'
177     id = Column(Integer, primary_key=True)
178     func = Column(PickleType)
179
180     def __init__(self, func, all_cons_opts):
181         self.func = func
182         for option in all_cons_opts:
183             option._consistencies.append(self)
184
185
186 class BaseOption(Base):
187     """This abstract base class stands for attribute access
188     in options that have to be set only once, it is of course done in the
189     __setattr__ method
190     """
191     __tablename__ = 'baseoption'
192     id = Column(Integer, primary_key=True)
193     _name = Column(String)
194     _informations = relationship('_Information')
195     _default = Column(PickleType)
196     _default_multi = Column(PickleType)
197     _requires = relationship('_RequireOption')
198     _multi = Column(Boolean)
199     _multitype = Column(String)
200     _callback = Column(PickleType)
201     _callback_params = relationship('_CallbackParam')
202     _validator = Column(PickleType)
203     _validator_params = relationship('_CallbackParam')
204     _parent = Column(Integer, ForeignKey('baseoption.id'))
205     _children = relationship('BaseOption', enable_typechecks=False)
206     _properties = relationship('_PropertyOption', secondary=property_table,
207                                backref=backref('options', enable_typechecks=False))
208     _warnings_only = Column(Boolean)
209     _readonly = Column(Boolean, default=False)
210     _consistencies = relationship('_Consistency', secondary=consistency_table,
211                                   backref=backref('options', enable_typechecks=False))
212     _choice_values = Column(PickleType)
213     _choice_open_values = Column(Boolean)
214     _type = Column(String(50))
215     __mapper_args__ = {
216         'polymorphic_identity': 'person',
217         'polymorphic_on': _type
218     }
219     #FIXME devrait etre une table
220     _optiondescription_group_type = Column(String)
221     #__slots__ = ('_name', '_requires', '_properties', '_readonly',
222     #             '_calc_properties', '_impl_informations',
223     #             '_state_readonly', '_state_requires', '_stated')
224
225     def __init__(self, name, doc, default=None, default_multi=None,
226                  requires=None, multi=False, callback=None,
227                  callback_params=None, validator=None, validator_params=None,
228                  properties=None, warnings_only=False, choice_values=None,
229                  choice_open_values=None):
230         if not valid_name(name):
231             raise ValueError(_("invalid name: {0} for option").format(name))
232         self._name = name
233         self.impl_set_information('doc', doc)
234         requires = validate_requires_arg(requires, self._name)
235         if requires is not None:
236             for values in requires.values():
237                 for require in values.values():
238                     self._requires.append(_RequireOption(*require))
239         if not multi and default_multi is not None:
240             raise ValueError(_("a default_multi is set whereas multi is False"
241                              " in option: {0}").format(name))
242         if default_multi is not None:
243             try:
244                 self._validate(default_multi)
245             except ValueError as err:
246                 raise ValueError(_("invalid default_multi value {0} "
247                                    "for option {1}: {2}").format(
248                                        str(default_multi), name, err))
249         self._multi = multi
250         if self._multi:
251             if default is None:
252                 default = []
253             self._multitype = multitypes.default
254             self._default_multi = default_multi
255         if callback is not None and ((not multi and (default is not None or
256                                                      default_multi is not None))
257                                      or (multi and (default != [] or
258                                                     default_multi is not None))
259                                      ):
260             raise ValueError(_("default value not allowed if option: {0} "
261                              "is calculated").format(name))
262         if properties is None:
263             properties = tuple()
264         if not isinstance(properties, tuple):
265             raise TypeError(_('invalid properties type {0} for {1},'
266                             ' must be a tuple').format(
267                                 type(properties),
268                                 self._name))
269         if validator is not None:
270             validate_callback(validator, validator_params, 'validator')
271             self._validator = validator
272             if validator_params is not None:
273                 for key, values in validator_params.items():
274                     self._validator_params.append(_CallbackParam(key, values))
275         if callback is None and callback_params is not None:
276             raise ValueError(_("params defined for a callback function but "
277                              "no callback defined"
278                              " yet for option {0}").format(name))
279         if callback is not None:
280             validate_callback(callback, callback_params, 'callback')
281             self._callback = callback
282             if callback_params is not None:
283                 for key, values in callback_params.items():
284                     self._callback_params.append(_CallbackParam(key, values))
285         if requires is not None and properties is not tuple():
286             set_forbidden_properties = set(properties) & set(requires.keys())
287             if set_forbidden_properties != frozenset():
288                 raise ValueError('conflict: properties already set in '
289                                  'requirement {0}'.format(
290                                      list(set_forbidden_properties)))
291         if choice_values is not None:
292             self._choice_values = choice_values
293         if choice_open_values is not None:
294             self._choice_open_values = choice_open_values
295         self.impl_validate(default)
296         if multi and default is None:
297             self._default = []
298         else:
299             self._default = default
300         for prop in properties:
301             prop_obj = session.query(_PropertyOption).filter(_PropertyOption.name == prop).first()
302             if prop_obj is None:
303                 prop_obj = _PropertyOption(prop)
304             self._properties.append(prop_obj)
305         self._warnings_only = warnings_only
306
307     # ____________________________________________________________
308     # information
309     def impl_set_information(self, key, value):
310         """updates the information's attribute
311         (which is a dictionary)
312
313         :param key: information's key (ex: "help", "doc"
314         :param value: information's value (ex: "the help string")
315         """
316         #FIXME pas append ! remplacer !
317         info = session.query(_Information).filter_by(option=self.id, key=key).first()
318         if info is None:
319             self._informations.append(_Information(key, value))
320         else:
321             info.value = value
322
323     def impl_get_information(self, key, default=None):
324         """retrieves one information's item
325
326         :param key: the item string (ex: "help")
327         """
328         info = session.query(_Information).filter_by(option=self.id, key=key).first()
329         if info is not None:
330             return info.value
331         elif default is not None:
332             return default
333         else:
334             raise ValueError(_("information's item not found: {0}").format(
335                 key))
336
337     # ____________________________________________________________
338     # serialize object
339     def _impl_convert_requires(self, descr, load=False):
340         """export of the requires during the serialization process
341
342         :type descr: :class:`tiramisu.option.OptionDescription`
343         :param load: `True` if we are at the init of the option description
344         :type load: bool
345         """
346         if not load and self._requires is None:
347             self._state_requires = None
348         elif load and self._state_requires is None:
349             self._requires = None
350             del(self._state_requires)
351         else:
352             if load:
353                 _requires = self._state_requires
354             else:
355                 _requires = self._requires
356             new_value = []
357             for requires in _requires:
358                 new_requires = []
359                 for require in requires:
360                     if load:
361                         new_require = [descr.impl_get_opt_by_path(require[0])]
362                     else:
363                         new_require = [descr.impl_get_path_by_opt(require[0])]
364                     new_require.extend(require[1:])
365                     new_requires.append(tuple(new_require))
366                 new_value.append(tuple(new_requires))
367             if load:
368                 del(self._state_requires)
369                 self._requires = new_value
370             else:
371                 self._state_requires = new_value
372
373     # serialize
374     def _impl_getstate(self, descr):
375         """the under the hood stuff that need to be done
376         before the serialization.
377
378         :param descr: the parent :class:`tiramisu.option.OptionDescription`
379         """
380         self._stated = True
381         for func in dir(self):
382             if func.startswith('_impl_convert_'):
383                 getattr(self, func)(descr)
384         self._state_readonly = self._readonly
385
386     def __getstate__(self, stated=True):
387         """special method to enable the serialization with pickle
388         Usualy, a `__getstate__` method does'nt need any parameter,
389         but somme under the hood stuff need to be done before this action
390
391         :parameter stated: if stated is `True`, the serialization protocol
392                            can be performed, not ready yet otherwise
393         :parameter type: bool
394         """
395         try:
396             self._stated
397         except AttributeError:
398             raise SystemError(_('cannot serialize Option, '
399                                 'only in OptionDescription'))
400         slots = set()
401         for subclass in self.__class__.__mro__:
402             if subclass is not object:
403                 slots.update(subclass.__slots__)
404         slots -= frozenset(['_cache_paths', '_cache_consistencies',
405                             '__weakref__'])
406         states = {}
407         for slot in slots:
408             # remove variable if save variable converted
409             # in _state_xxxx variable
410             if '_state' + slot not in slots:
411                 if slot.startswith('_state'):
412                     # should exists
413                     states[slot] = getattr(self, slot)
414                     # remove _state_xxx variable
415                     self.__delattr__(slot)
416                 else:
417                     try:
418                         states[slot] = getattr(self, slot)
419                     except AttributeError:
420                         pass
421         if not stated:
422             del(states['_stated'])
423         return states
424
425     # unserialize
426     def _impl_setstate(self, descr):
427         """the under the hood stuff that need to be done
428         before the serialization.
429
430         :type descr: :class:`tiramisu.option.OptionDescription`
431         """
432         for func in dir(self):
433             if func.startswith('_impl_convert_'):
434                 getattr(self, func)(descr, load=True)
435         try:
436             self._readonly = self._state_readonly
437             del(self._state_readonly)
438             del(self._stated)
439         except AttributeError:
440             pass
441
442     def __setstate__(self, state):
443         """special method that enables us to serialize (pickle)
444
445         Usualy, a `__setstate__` method does'nt need any parameter,
446         but somme under the hood stuff need to be done before this action
447
448         :parameter state: a dict is passed to the loads, it is the attributes
449                           of the options object
450         :type state: dict
451         """
452         for key, value in state.items():
453             setattr(self, key, value)
454
455     def impl_getname(self):
456         return self._name
457
458
459 class Option(BaseOption):
460     """
461     Abstract base class for configuration option's.
462
463     Reminder: an Option object is **not** a container for the value.
464     """
465 #    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
466 #                 '_state_callback', '_callback', '_multitype',
467 #                 '_consistencies', '_warnings_only', '_master_slaves',
468 #                 '_state_consistencies', '__weakref__')
469     _empty = ''
470
471     def __init__(self, name, doc, default=None, default_multi=None,
472                  requires=None, multi=False, callback=None,
473                  callback_params=None, validator=None, validator_params=None,
474                  properties=None, warnings_only=False, choice_values=None,
475                  choice_open_values=None):
476         """
477         :param name: the option's name
478         :param doc: the option's description
479         :param default: specifies the default value of the option,
480                         for a multi : ['bla', 'bla', 'bla']
481         :param default_multi: 'bla' (used in case of a reset to default only at
482                         a given index)
483         :param requires: is a list of names of options located anywhere
484                          in the configuration.
485         :param multi: if true, the option's value is a list
486         :param callback: the name of a function. If set, the function's output
487                          is responsible of the option's value
488         :param callback_params: the callback's parameter
489         :param validator: the name of a function which stands for a custom
490                           validation of the value
491         :param validator_params: the validator's parameters
492         :param properties: tuple of default properties
493         :param warnings_only: _validator and _consistencies don't raise if True
494                              Values()._warning contain message
495
496         """
497         super(Option, self).__init__(name, doc, default, default_multi,
498                                      requires, multi, callback,
499                                      callback_params, validator,
500                                      validator_params, properties,
501                                      warnings_only, choice_values,
502                                      choice_open_values)
503         session.add(self)
504         session.commit()
505
506     #def __setattr__(self, name, value):
507     #    """set once and only once some attributes in the option,
508     #    like `_name`. `_name` cannot be changed one the option and
509     #    pushed in the :class:`tiramisu.option.OptionDescription`.
510
511     #    if the attribute `_readonly` is set to `True`, the option is
512     #    "frozen" (which has noting to do with the high level "freeze"
513     #    propertie or "read_only" property)
514     #    """
515     #    #FIXME ne devrait pas pouvoir redefinir _option
516     #    if not name == '_option':
517     #        is_readonly = False
518     #        # never change _name
519     #        if name == '_name':
520     #            try:
521     #                self._name
522     #                #so _name is already set
523     #                is_readonly = True
524     #            except:
525     #                pass
526     #        elif name != '_readonly':
527     #            try:
528     #                if self._readonly is True:
529     #                    is_readonly = True
530     #            except AttributeError:
531     #                self._readonly = False
532     #        if is_readonly:
533     #            raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
534     #                                   " read-only").format(
535     #                                       self.__class__.__name__,
536     #                                       self._name,
537     #                                       name))
538     #    object.__setattr__(self, name, value)
539
540     def impl_getproperties(self):
541         for prop in self._properties:
542             yield(prop.name)
543
544     def impl_getrequires(self):
545         return self._requires
546
547     def _launch_consistency(self, func, option, value, context, index,
548                             all_cons_opts):
549         """Launch consistency now
550
551         :param func: function name, this name should start with _cons_
552         :type func: `str`
553         :param option: option that value is changing
554         :type option: `tiramisu.option.Option`
555         :param value: new value of this option
556         :param context: Config's context, if None, check default value instead
557         :type context: `tiramisu.config.Config`
558         :param index: only for multi option, consistency should be launch for
559                       specified index
560         :type index: `int`
561         :param all_cons_opts: all options concerne by this consistency
562         :type all_cons_opts: `list` of `tiramisu.option.Option`
563         """
564         if context is not None:
565             descr = context.cfgimpl_get_description()
566         #option is also in all_cons_opts
567         if option not in all_cons_opts:
568             raise ConfigError(_('option not in all_cons_opts'))
569
570         all_cons_vals = []
571         for opt in all_cons_opts:
572             #get value
573             if option == opt:
574                 opt_value = value
575             else:
576                 #if context, calculate value, otherwise get default value
577                 if context is not None:
578                     opt_value = context._getattr(
579                         descr.impl_get_path_by_opt(opt), validate=False)
580                 else:
581                     opt_value = opt.impl_getdefault()
582
583             #append value
584             if not self.impl_is_multi() or option == opt:
585                 all_cons_vals.append(opt_value)
586             else:
587                 #value is not already set, could be higher index
588                 try:
589                     all_cons_vals.append(opt_value[index])
590                 except IndexError:
591                     #so return if no value
592                     return
593         getattr(self, func)(all_cons_opts, all_cons_vals)
594
595     def impl_validate(self, value, context=None, validate=True,
596                       force_index=None):
597         """
598         :param value: the option's value
599         :param context: Config's context
600         :type context: :class:`tiramisu.config.Config`
601         :param validate: if true enables ``self._validator`` validation
602         :type validate: boolean
603         :param force_no_multi: if multi, value has to be a list
604                                not if force_no_multi is True
605         :type force_no_multi: boolean
606         """
607         if not validate:
608             return
609
610         def val_validator(val):
611             if self._validator is not None:
612                 class FakeCallbackParamOption(object):
613                     def __init__(self, option=None,
614                                  force_permissive=None,
615                                  value=None):
616                         self.option = option
617                         self.force_permissive = force_permissive
618                         self.value = value
619
620                 class FakeCallbackParam(object):
621                     def __init__(self, key, params):
622                         self.name = key
623                         self.params = params
624                 if self._validator_params != []:
625                     validator_params = []
626                     validator_params_option = [FakeCallbackParamOption(value=val)]
627                     #if '' in self._validator_params:
628                     #    for param in self._validator_params['']:
629                     #        if isinstance(param, tuple):
630                     #            validator_params_option.append(FakeCallbackParamOption(option=param[0], force_permissive=param[1]))
631                     #        else:
632                     #            validator_params_option.append(FakeCallbackParamOption(value=param))
633                     validator_params.append(FakeCallbackParam('', validator_params_option))
634                     for key in self._validator_params:
635                         if key.name != '':
636                             validator_params.append(key)
637                 else:
638                     validator_params = (FakeCallbackParam('', (FakeCallbackParamOption(value=val),)),)
639                 # Raise ValueError if not valid
640                 carry_out_calculation(self, config=context,
641                                       callback=self._validator,
642                                       callback_params=validator_params)
643
644         def do_validation(_value, _index=None):
645             if _value is None:
646                 return
647             # option validation
648             try:
649                 self._validate(_value)
650             except ValueError as err:
651                 raise ValueError(_('invalid value for option {0}: {1}'
652                                    '').format(self.impl_getname(), err))
653             try:
654                 # valid with self._validator
655                 val_validator(_value)
656                 # if not context launch consistency validation
657                 if context is not None:
658                     descr._valid_consistency(self, _value, context, _index)
659                 self._second_level_validation(_value)
660             except ValueError as err:
661                 msg = _("invalid value for option {0}: {1}").format(
662                     self.impl_getname(), err)
663                 if self._warnings_only:
664                     warnings.warn_explicit(ValueWarning(msg, self),
665                                            ValueWarning,
666                                            self.__class__.__name__, 0)
667                 else:
668                     raise ValueError(msg)
669
670         # generic calculation
671         if context is not None:
672             descr = context.cfgimpl_get_description()
673
674         if not self._multi or force_index is not None:
675             do_validation(value, force_index)
676         else:
677             if not isinstance(value, list):
678                 raise ValueError(_("invalid value {0} for option {1} which must be a list").format(value, self.impl_getname()))
679             for index, val in enumerate(value):
680                 do_validation(val, index)
681
682     def impl_getdefault(self):
683         "accessing the default value"
684         return self._default
685
686     def impl_getdefault_multi(self):
687         "accessing the default value for a multi"
688         return self._default_multi
689
690     def impl_get_multitype(self):
691         return self._multitype
692
693     def impl_get_master_slaves(self):
694         return self._master_slaves
695
696     def impl_is_empty_by_default(self):
697         "no default value has been set yet"
698         if ((not self.impl_is_multi() and self._default is None) or
699                 (self.impl_is_multi() and (self._default == []
700                                            or None in self._default))):
701             return True
702         return False
703
704     def impl_getdoc(self):
705         "accesses the Option's doc"
706         return self.impl_get_information('doc')
707
708     def impl_has_callback(self):
709         "to know if a callback has been defined or not"
710         if self._callback is None:
711             return False
712         else:
713             return True
714
715     def impl_get_callback(self):
716         return self._callback, self._callback_params
717
718     #def impl_getkey(self, value):
719     #    return value
720
721     def impl_is_multi(self):
722         return self._multi
723
724     def impl_add_consistency(self, func, *other_opts):
725         """Add consistency means that value will be validate with other_opts
726         option's values.
727
728         :param func: function's name
729         :type func: `str`
730         :param other_opts: options used to validate value
731         :type other_opts: `list` of `tiramisu.option.Option`
732         """
733         for opt in other_opts:
734             if not isinstance(opt, Option):
735                 raise ConfigError(_('consistency should be set with an option'))
736             if self is opt:
737                 raise ConfigError(_('cannot add consistency with itself'))
738             if self.impl_is_multi() != opt.impl_is_multi():
739                 raise ConfigError(_('every options in consistency should be '
740                                     'multi or none'))
741         func = '_cons_{0}'.format(func)
742         all_cons_opts = tuple([self] + list(other_opts))
743         value = self.impl_getdefault()
744         if value is not None:
745             if self.impl_is_multi():
746                 for idx, val in enumerate(value):
747                     self._launch_consistency(func, self, val, None,
748                                              idx, all_cons_opts)
749             else:
750                 self._launch_consistency(func, self, value, None,
751                                          None, all_cons_opts)
752         _Consistency(func, all_cons_opts)
753         self.impl_validate(self.impl_getdefault())
754
755     def _cons_not_equal(self, opts, vals):
756         for idx_inf, val_inf in enumerate(vals):
757             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
758                 if val_inf == val_sup is not None:
759                     raise ValueError(_("same value for {0} and {1}").format(
760                         opts[idx_inf].impl_getname(), opts[idx_inf + idx_sup + 1].impl_getname()))
761
762     def _impl_convert_callbacks(self, descr, load=False):
763         #FIXME
764         if not load and self._callback is None:
765             self._state_callback = None
766         elif load and self._state_callback is None:
767             self._callback = None
768             del(self._state_callback)
769         else:
770             if load:
771                 callback, callback_params = self._state_callback
772             else:
773                 callback, callback_params = self._callback
774             if callback_params is not None:
775                 cllbck_prms = {}
776                 for key, values in callback_params.items():
777                     vls = []
778                     for value in values:
779                         if isinstance(value, tuple):
780                             if load:
781                                 value = (descr.impl_get_opt_by_path(value[0]),
782                                          value[1])
783                             else:
784                                 value = (descr.impl_get_path_by_opt(value[0]),
785                                          value[1])
786                         vls.append(value)
787                     cllbck_prms[key] = tuple(vls)
788             else:
789                 cllbck_prms = None
790
791             if load:
792                 del(self._state_callback)
793                 self._callback = (callback, cllbck_prms)
794             else:
795                 self._state_callback = (callback, cllbck_prms)
796
797     # serialize/unserialize
798     def _impl_convert_consistencies(self, descr, load=False):
799         """during serialization process, many things have to be done.
800         one of them is the localisation of the options.
801         The paths are set once for all.
802
803         :type descr: :class:`tiramisu.option.OptionDescription`
804         :param load: `True` if we are at the init of the option description
805         :type load: bool
806         """
807         if not load and self._consistencies is None:
808             self._state_consistencies = None
809         elif load and self._state_consistencies is None:
810             self._consistencies = None
811             del(self._state_consistencies)
812         else:
813             if load:
814                 consistencies = self._state_consistencies
815             else:
816                 consistencies = self._consistencies
817             if isinstance(consistencies, list):
818                 new_value = []
819                 for consistency in consistencies:
820                     values = []
821                     for obj in consistency[1]:
822                         if load:
823                             values.append(descr.impl_get_opt_by_path(obj))
824                         else:
825                             values.append(descr.impl_get_path_by_opt(obj))
826                     new_value.append((consistency[0], tuple(values)))
827
828             else:
829                 new_value = {}
830                 for key, _consistencies in consistencies.items():
831                     new_value[key] = []
832                     for key_cons, _cons in _consistencies:
833                         _list_cons = []
834                         for _con in _cons:
835                             if load:
836                                 _list_cons.append(
837                                     descr.impl_get_opt_by_path(_con))
838                             else:
839                                 _list_cons.append(
840                                     descr.impl_get_path_by_opt(_con))
841                         new_value[key].append((key_cons, tuple(_list_cons)))
842             if load:
843                 del(self._state_consistencies)
844                 self._consistencies = new_value
845             else:
846                 self._state_consistencies = new_value
847
848     def _second_level_validation(self, value):
849         pass
850
851
852 class ChoiceOption(Option):
853     """represents a choice out of several objects.
854
855     The option can also have the value ``None``
856     """
857
858     #__slots__ = ('_values', '_open_values')
859     __mapper_args__ = {
860         'polymorphic_identity': 'choice',
861     }
862
863     def __init__(self, name, doc, values, default=None, default_multi=None,
864                  requires=None, multi=False, callback=None,
865                  callback_params=None, open_values=False, validator=None,
866                  validator_params=None, properties=None, warnings_only=False):
867         """
868         :param values: is a list of values the option can possibly take
869         """
870         if not isinstance(values, tuple):
871             raise TypeError(_('values must be a tuple for {0}').format(name))
872         if open_values not in (True, False):
873             raise TypeError(_('open_values must be a boolean for '
874                             '{0}').format(name))
875         super(ChoiceOption, self).__init__(name, doc, default=default,
876                                            default_multi=default_multi,
877                                            callback=callback,
878                                            callback_params=callback_params,
879                                            requires=requires,
880                                            multi=multi,
881                                            validator=validator,
882                                            validator_params=validator_params,
883                                            properties=properties,
884                                            warnings_only=warnings_only,
885                                            choice_values=values,
886                                            choice_open_values=open_values)
887
888     def impl_get_values(self):
889         return self._choice_values
890
891     def impl_is_openvalues(self):
892         return self._choice_open_values
893
894     def _validate(self, value):
895         if not self._choice_open_values and not value in self._choice_values:
896             raise ValueError(_('value {0} is not permitted, '
897                                'only {1} is allowed'
898                                '').format(value, self._choice_values))
899
900
901 class BoolOption(Option):
902     "represents a choice between ``True`` and ``False``"
903 #    __slots__ = tuple()
904     __mapper_args__ = {
905         'polymorphic_identity': 'bool',
906     }
907
908     def _validate(self, value):
909         if not isinstance(value, bool):
910             raise ValueError(_('invalid boolean'))
911
912
913 class IntOption(Option):
914     "represents a choice of an integer"
915 #    __slots__ = tuple()
916     __mapper_args__ = {
917         'polymorphic_identity': 'int',
918     }
919
920     def _validate(self, value):
921         if not isinstance(value, int):
922             raise ValueError(_('invalid integer'))
923
924
925 class FloatOption(Option):
926     "represents a choice of a floating point number"
927     #__slots__ = tuple()
928     __mapper_args__ = {
929         'polymorphic_identity': 'float',
930     }
931
932     def _validate(self, value):
933         if not isinstance(value, float):
934             raise ValueError(_('invalid float'))
935
936
937 class StrOption(Option):
938     "represents the choice of a string"
939     #__slots__ = tuple()
940     __mapper_args__ = {
941         'polymorphic_identity': 'string',
942     }
943
944     def _validate(self, value):
945         if not isinstance(value, str):
946             raise ValueError(_('invalid string'))
947
948
949 if sys.version_info[0] >= 3:
950     #UnicodeOption is same as StrOption in python 3+
951     class UnicodeOption(StrOption):
952         #__slots__ = tuple()
953         pass
954 else:
955     class UnicodeOption(Option):
956         "represents the choice of a unicode string"
957         #__slots__ = tuple()
958         __mapper_args__ = {
959             'polymorphic_identity': 'unicode',
960         }
961         _empty = u''
962
963         def _validate(self, value):
964             if not isinstance(value, unicode):
965                 raise ValueError(_('invalid unicode'))
966
967
968 class SymLinkOption(BaseOption):
969     #__slots__ = ('_name', '_opt', '_state_opt', '_readonly', '_parent')
970     __mapper_args__ = {
971         'polymorphic_identity': 'symlink',
972     }
973     #not return _opt consistencies
974     #_consistencies = None
975
976     def __init__(self, name, opt):
977         self._name = name
978         if not isinstance(opt, Option):
979             raise ValueError(_('malformed symlinkoption '
980                                'must be an option '
981                                'for symlink {0}').format(name))
982         self._opt = opt
983         self._readonly = True
984         self._parent = None
985         session.add(self)
986         session.commit()
987
988     def __getattr__(self, name):
989         if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'):
990             return object.__getattr__(self, name)
991         else:
992             return getattr(self._opt, name)
993
994     def _impl_getstate(self, descr):
995         super(SymLinkOption, self)._impl_getstate(descr)
996         self._state_opt = descr.impl_get_path_by_opt(self._opt)
997
998     def _impl_setstate(self, descr):
999         self._opt = descr.impl_get_opt_by_path(self._state_opt)
1000         del(self._state_opt)
1001         super(SymLinkOption, self)._impl_setstate(descr)
1002
1003     def impl_get_information(self, key, default=None):
1004         #FIXME ne devrait pas etre util si ?
1005         return self._opt.impl_get_information(key, default)
1006
1007
1008 class IPOption(Option):
1009     "represents the choice of an ip"
1010     #__slots__ = ('_private_only', '_allow_reserved')
1011     __mapper_args__ = {
1012         'polymorphic_identity': 'ip',
1013     }
1014
1015     def __init__(self, name, doc, default=None, default_multi=None,
1016                  requires=None, multi=False, callback=None,
1017                  callback_params=None, validator=None, validator_params=None,
1018                  properties=None, private_only=False, allow_reserved=False,
1019                  warnings_only=False):
1020         self._private_only = private_only
1021         self._allow_reserved = allow_reserved
1022         super(IPOption, self).__init__(name, doc, default=default,
1023                                        default_multi=default_multi,
1024                                        callback=callback,
1025                                        callback_params=callback_params,
1026                                        requires=requires,
1027                                        multi=multi,
1028                                        validator=validator,
1029                                        validator_params=validator_params,
1030                                        properties=properties,
1031                                        warnings_only=warnings_only)
1032
1033     def _validate(self, value):
1034         # sometimes an ip term starts with a zero
1035         # but this does not fit in some case, for example bind does not like it
1036         for val in value.split('.'):
1037             if val.startswith("0") and len(val) > 1:
1038                 raise ValueError(_('invalid IP'))
1039         # 'standard' validation
1040         try:
1041             IP('{0}/32'.format(value))
1042         except ValueError:
1043             raise ValueError(_('invalid IP'))
1044
1045     def _second_level_validation(self, value):
1046         ip = IP('{0}/32'.format(value))
1047         if not self._allow_reserved and ip.iptype() == 'RESERVED':
1048             raise ValueError(_("invalid IP, mustn't not be in reserved class"))
1049         if self._private_only and not ip.iptype() == 'PRIVATE':
1050             raise ValueError(_("invalid IP, must be in private class"))
1051
1052
1053 class PortOption(Option):
1054     """represents the choice of a port
1055     The port numbers are divided into three ranges:
1056     the well-known ports,
1057     the registered ports,
1058     and the dynamic or private ports.
1059     You can actived this three range.
1060     Port number 0 is reserved and can't be used.
1061     see: http://en.wikipedia.org/wiki/Port_numbers
1062     """
1063     #__slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
1064     __mapper_args__ = {
1065         'polymorphic_identity': 'port',
1066     }
1067
1068     def __init__(self, name, doc, default=None, default_multi=None,
1069                  requires=None, multi=False, callback=None,
1070                  callback_params=None, validator=None, validator_params=None,
1071                  properties=None, allow_range=False, allow_zero=False,
1072                  allow_wellknown=True, allow_registred=True,
1073                  allow_private=False, warnings_only=False):
1074         self._allow_range = allow_range
1075         self._min_value = None
1076         self._max_value = None
1077         ports_min = [0, 1, 1024, 49152]
1078         ports_max = [0, 1023, 49151, 65535]
1079         is_finally = False
1080         for index, allowed in enumerate([allow_zero,
1081                                          allow_wellknown,
1082                                          allow_registred,
1083                                          allow_private]):
1084             if self._min_value is None:
1085                 if allowed:
1086                     self._min_value = ports_min[index]
1087             elif not allowed:
1088                 is_finally = True
1089             elif allowed and is_finally:
1090                 raise ValueError(_('inconsistency in allowed range'))
1091             if allowed:
1092                 self._max_value = ports_max[index]
1093
1094         if self._max_value is None:
1095             raise ValueError(_('max value is empty'))
1096
1097         super(PortOption, self).__init__(name, doc, default=default,
1098                                          default_multi=default_multi,
1099                                          callback=callback,
1100                                          callback_params=callback_params,
1101                                          requires=requires,
1102                                          multi=multi,
1103                                          validator=validator,
1104                                          validator_params=validator_params,
1105                                          properties=properties,
1106                                          warnings_only=warnings_only)
1107
1108     def _validate(self, value):
1109         if self._allow_range and ":" in str(value):
1110             value = str(value).split(':')
1111             if len(value) != 2:
1112                 raise ValueError('invalid part, range must have two values '
1113                                  'only')
1114             if not value[0] < value[1]:
1115                 raise ValueError('invalid port, first port in range must be'
1116                                  ' smaller than the second one')
1117         else:
1118             value = [value]
1119
1120         for val in value:
1121             if not self._min_value <= int(val) <= self._max_value:
1122                 raise ValueError('invalid port, must be an between {0} and {1}'
1123                                  ''.format(self._min_value, self._max_value))
1124
1125
1126 class NetworkOption(Option):
1127     "represents the choice of a network"
1128     #__slots__ = tuple()
1129     __mapper_args__ = {
1130         'polymorphic_identity': 'network',
1131     }
1132
1133     def _validate(self, value):
1134         try:
1135             IP(value)
1136         except ValueError:
1137             raise ValueError(_('invalid network address'))
1138
1139     def _second_level_validation(self, value):
1140         ip = IP(value)
1141         if ip.iptype() == 'RESERVED':
1142             raise ValueError(_("invalid network address, must not be in reserved class"))
1143
1144
1145 class NetmaskOption(Option):
1146     "represents the choice of a netmask"
1147     #__slots__ = tuple()
1148     __mapper_args__ = {
1149         'polymorphic_identity': 'netmask',
1150     }
1151
1152     def _validate(self, value):
1153         try:
1154             IP('0.0.0.0/{0}'.format(value))
1155         except ValueError:
1156             raise ValueError(_('invalid netmask address'))
1157
1158     def _cons_network_netmask(self, opts, vals):
1159         #opts must be (netmask, network) options
1160         if None in vals:
1161             return
1162         self.__cons_netmask(opts, vals[0], vals[1], False)
1163
1164     def _cons_ip_netmask(self, opts, vals):
1165         #opts must be (netmask, ip) options
1166         if None in vals:
1167             return
1168         self.__cons_netmask(opts, vals[0], vals[1], True)
1169
1170     def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net):
1171         if len(opts) != 2:
1172             raise ConfigError(_('invalid len for opts'))
1173         msg = None
1174         try:
1175             ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
1176                     make_net=make_net)
1177             #if cidr == 32, ip same has network
1178             if ip.prefixlen() != 32:
1179                 try:
1180                     IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
1181                         make_net=not make_net)
1182                 except ValueError:
1183                     if not make_net:
1184                         msg = _("invalid network {0} ({1}) "
1185                                 "with netmask {2},"
1186                                 " this network is an IP")
1187                 else:
1188                     if make_net:
1189                         msg = _("invalid IP {0} ({1}) with netmask {2},"
1190                                 " this IP is a network")
1191
1192         except ValueError:
1193             if make_net:
1194                 msg = _('invalid IP {0} ({1}) with netmask {2}')
1195             else:
1196                 msg = _('invalid network {0} ({1}) with netmask {2}')
1197         if msg is not None:
1198             raise ValueError(msg.format(val_ipnetwork, opts[1].impl_getname(),
1199                                         val_netmask))
1200
1201
1202 class BroadcastOption(Option):
1203     #__slots__ = tuple()
1204     __mapper_args__ = {
1205         'polymorphic_identity': 'broadcast',
1206     }
1207
1208     def _validate(self, value):
1209         try:
1210             IP('{0}/32'.format(value))
1211         except ValueError:
1212             raise ValueError(_('invalid broadcast address'))
1213
1214     def _cons_broadcast(self, opts, vals):
1215         if len(vals) != 3:
1216             raise ConfigError(_('invalid len for vals'))
1217         if None in vals:
1218             return
1219         broadcast, network, netmask = vals
1220         if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast):
1221             raise ValueError(_('invalid broadcast {0} ({1}) with network {2} '
1222                                '({3}) and netmask {4} ({5})').format(
1223                                    broadcast, opts[0].impl_getname(), network,
1224                                    opts[1].impl_getname(), netmask, opts[2].impl_getname()))
1225
1226
1227 class DomainnameOption(Option):
1228     """represents the choice of a domain name
1229     netbios: for MS domain
1230     hostname: to identify the device
1231     domainname:
1232     fqdn: with tld, not supported yet
1233     """
1234     #__slots__ = ('_dom_type', '_allow_ip', '_allow_without_dot', '_domain_re')
1235     __mapper_args__ = {
1236         'polymorphic_identity': 'domainname',
1237     }
1238
1239     def __init__(self, name, doc, default=None, default_multi=None,
1240                  requires=None, multi=False, callback=None,
1241                  callback_params=None, validator=None, validator_params=None,
1242                  properties=None, allow_ip=False, type_='domainname',
1243                  warnings_only=False, allow_without_dot=False):
1244         if type_ not in ['netbios', 'hostname', 'domainname']:
1245             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
1246         self._dom_type = type_
1247         if allow_ip not in [True, False]:
1248             raise ValueError(_('allow_ip must be a boolean'))
1249         if allow_without_dot not in [True, False]:
1250             raise ValueError(_('allow_without_dot must be a boolean'))
1251         self._allow_ip = allow_ip
1252         self._allow_without_dot = allow_without_dot
1253         end = ''
1254         extrachar = ''
1255         extrachar_mandatory = ''
1256         if self._dom_type == 'netbios':
1257             length = 14
1258         elif self._dom_type == 'hostname':
1259             length = 62
1260         elif self._dom_type == 'domainname':
1261             length = 62
1262             if allow_without_dot is False:
1263                 extrachar_mandatory = '\.'
1264             else:
1265                 extrachar = '\.'
1266             end = '+[a-z]*'
1267         self._domain_re = re.compile(r'^(?:[a-z][a-z\d\-{0}]{{,{1}}}{2}){3}$'
1268                                      ''.format(extrachar, length, extrachar_mandatory, end))
1269         super(DomainnameOption, self).__init__(name, doc, default=default,
1270                                                default_multi=default_multi,
1271                                                callback=callback,
1272                                                callback_params=callback_params,
1273                                                requires=requires,
1274                                                multi=multi,
1275                                                validator=validator,
1276                                                validator_params=validator_params,
1277                                                properties=properties,
1278                                                warnings_only=warnings_only)
1279
1280     def _validate(self, value):
1281         if self._allow_ip is True:
1282             try:
1283                 IP('{0}/32'.format(value))
1284                 return
1285             except ValueError:
1286                 pass
1287         if self._dom_type == 'domainname' and not self._allow_without_dot and \
1288                 '.' not in value:
1289             raise ValueError(_("invalid domainname, must have dot"))
1290             if len(value) > 255:
1291                 raise ValueError(_("invalid domainname's length (max 255)"))
1292         if len(value) < 2:
1293             raise ValueError(_("invalid domainname's length (min 2)"))
1294         if not self._domain_re.search(value):
1295             raise ValueError(_('invalid domainname'))
1296
1297
1298 class EmailOption(DomainnameOption):
1299     #__slots__ = tuple()
1300     __mapper_args__ = {
1301         'polymorphic_identity': 'email',
1302     }
1303     username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
1304
1305     def _validate(self, value):
1306         splitted = value.split('@', 1)
1307         try:
1308             username, domain = splitted
1309         except ValueError:
1310             raise ValueError(_('invalid email address, should contains one @'
1311                                ))
1312         if not self.username_re.search(username):
1313             raise ValueError(_('invalid username in email address'))
1314         super(EmailOption, self)._validate(domain)
1315
1316
1317 class URLOption(DomainnameOption):
1318     #__slots__ = tuple()
1319     __mapper_args__ = {
1320         'polymorphic_identity': 'url',
1321     }
1322     proto_re = re.compile(r'(http|https)://')
1323     path_re = re.compile(r"^[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$")
1324
1325     def _validate(self, value):
1326         match = self.proto_re.search(value)
1327         if not match:
1328             raise ValueError(_('invalid url, should start with http:// or '
1329                                'https://'))
1330         value = value[len(match.group(0)):]
1331         # get domain/files
1332         splitted = value.split('/', 1)
1333         try:
1334             domain, files = splitted
1335         except ValueError:
1336             domain = value
1337             files = None
1338         # if port in domain
1339         splitted = domain.split(':', 1)
1340         try:
1341             domain, port = splitted
1342
1343         except ValueError:
1344             domain = splitted[0]
1345             port = 0
1346         if not 0 <= int(port) <= 65535:
1347             raise ValueError(_('invalid url, port must be an between 0 and '
1348                                '65536'))
1349         # validate domainname
1350         super(URLOption, self)._validate(domain)
1351         # validate file
1352         if files is not None and files != '' and not self.path_re.search(files):
1353             raise ValueError(_('invalid url, should ends with filename'))
1354
1355
1356 class FilenameOption(Option):
1357     #__slots__ = tuple()
1358     __mapper_args__ = {
1359         'polymorphic_identity': 'file',
1360     }
1361     path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$")
1362
1363     def _validate(self, value):
1364         match = self.path_re.search(value)
1365         if not match:
1366             raise ValueError(_('invalid filename'))
1367
1368
1369 class OptionDescription(BaseOption):
1370     """Config's schema (organisation, group) and container of Options
1371     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
1372     """
1373     #_slots = ('_name', '_requires', '_cache_paths', '_group_type',
1374     #          '_state_group_type', '_properties', '_children',
1375     #          '_cache_consistencies', '_calc_properties', '__weakref__',
1376     #          '_readonly', '_impl_informations', '_state_requires',
1377     #          '_stated', '_state_readonly')
1378     __mapper_args__ = {
1379         'polymorphic_identity': 'optiondescription',
1380     }
1381
1382     def __init__(self, name, doc, children, requires=None, properties=None):
1383         """
1384         :param children: a list of options (including optiondescriptions)
1385
1386         """
1387         super(OptionDescription, self).__init__(name, doc=doc, requires=requires, properties=properties)
1388         session.add(self)
1389         session.commit()
1390         child_names = [child.impl_getname() for child in children]
1391         #better performance like this
1392         valid_child = copy(child_names)
1393         valid_child.sort()
1394         old = None
1395         for child in valid_child:
1396             if child == old:
1397                 raise ConflictError(_('duplicate option name: '
1398                                       '{0}').format(child))
1399             old = child
1400         for child in children:
1401             if child._parent is not None:
1402                 raise ConflictError(_('duplicate option: '
1403                                       '{0}').format(child))
1404             self._children.append(child)  # = (tuple(child_names), tuple(children))
1405         self._cache_paths = None
1406         self._cache_consistencies = None
1407         # the group_type is useful for filtering OptionDescriptions in a config
1408         self._optiondescription_group_type = groups.default
1409
1410     def impl_getproperties(self):
1411         #FIXME
1412         for prop in self._properties:
1413             yield(prop.name)
1414
1415     def impl_getrequires(self):
1416         #FIXME
1417         return self._requires
1418
1419     def impl_getdoc(self):
1420         return self.impl_get_information('doc')
1421
1422     def impl_validate(self, *args):
1423         #FIXME a voir ...
1424         pass
1425
1426     def __getattr__(self, name):
1427         try:
1428             if name.startswith('_') or name.startswith('impl_'):
1429                 return object.__getattribute__(self, name)
1430             else:
1431                 #FIXME regression ... devrait etre un query !
1432                 for child in self._children:
1433                     if child.impl_getname() == name:
1434                         return child
1435                         #convert to object
1436                         #return session.query(child._type).filter_by(id=child.id).first()
1437                 #return pouet#self._children[1][self._children[0].index(name)]
1438         except ValueError:
1439             pass
1440         raise AttributeError(_('unknown Option {0} '
1441                                'in OptionDescription {1}'
1442                                '').format(name, self.impl_getname()))
1443
1444     #def impl_getkey(self, config):
1445     #    return tuple([child.impl_getkey(getattr(config, child.impl_getname()))
1446     #                  for child in self.impl_getchildren()])
1447
1448     def impl_getpaths(self, include_groups=False, _currpath=None):
1449         """returns a list of all paths in self, recursively
1450            _currpath should not be provided (helps with recursion)
1451         """
1452         if _currpath is None:
1453             _currpath = []
1454         paths = []
1455         for option in self.impl_getchildren():
1456             attr = option.impl_getname()
1457             if isinstance(option, OptionDescription):
1458                 if include_groups:
1459                     paths.append('.'.join(_currpath + [attr]))
1460                 paths += option.impl_getpaths(include_groups=include_groups,
1461                                               _currpath=_currpath + [attr])
1462             else:
1463                 paths.append('.'.join(_currpath + [attr]))
1464         return paths
1465
1466     def impl_getchildren(self):
1467         #FIXME dans la base ??
1468         return self._children
1469         #for child in self._children:
1470         #    yield(session.query(child._type).filter_by(id=child.id).first())
1471
1472     def impl_build_cache(self,
1473                          cache_path=None,
1474                          cache_option=None,
1475                          _currpath=None,
1476                          _consistencies=None,
1477                          force_no_consistencies=False):
1478         if _currpath is None and self._cache_paths is not None:
1479             # cache already set
1480             return
1481         if _currpath is None:
1482             save = True
1483             _currpath = []
1484             if not force_no_consistencies:
1485                 _consistencies = {}
1486         else:
1487             save = False
1488         if cache_path is None:
1489             cache_path = []
1490             cache_option = []
1491         for option in self.impl_getchildren():
1492             attr = option.impl_getname()
1493             if option.id is None:
1494                 raise SystemError(_("an option's id should not be None "
1495                                     "for {0}").format(option.impl_getname()))
1496             if option.id in cache_option:
1497                 raise ConflictError(_('duplicate option: {0}').format(option))
1498             cache_option.append(option.id)
1499             if not force_no_consistencies:
1500                 option._readonly = True
1501             cache_path.append(str('.'.join(_currpath + [attr])))
1502             if not isinstance(option, OptionDescription):
1503                 if not force_no_consistencies and \
1504                         option._consistencies is not []:
1505                     for consistency in option._consistencies:
1506                         func = consistency.func
1507                         all_cons_opts = consistency.options
1508                         for opt in all_cons_opts:
1509                             _consistencies.setdefault(opt,
1510                                                       []).append((func,
1511                                                                   all_cons_opts))
1512             else:
1513                 _currpath.append(attr)
1514                 option.impl_build_cache(cache_path,
1515                                         cache_option,
1516                                         _currpath,
1517                                         _consistencies,
1518                                         force_no_consistencies)
1519                 _currpath.pop()
1520         if save:
1521             self._cache_paths = (tuple(cache_option), tuple(cache_path))
1522             if not force_no_consistencies:
1523                 if _consistencies != {}:
1524                     self._cache_consistencies = {}
1525                     for opt, cons in _consistencies.items():
1526                         if opt.id not in cache_option:
1527                             raise ConfigError(_('consistency with option {0} which is not in Config').format(opt.impl_getname()))
1528                         self._cache_consistencies[opt] = tuple(cons)
1529                 self._readonly = True
1530
1531     def impl_get_opt_by_path(self, path):
1532         try:
1533             #FIXME
1534             idx = self._cache_paths[1].index(path)
1535             opt_id = self._cache_paths[0][idx]
1536             return session.query(BaseOption).filter_by(id=opt_id).first()
1537         except ValueError:
1538             raise AttributeError(_('no option for path {0}').format(path))
1539
1540     def impl_get_opt_by_id(self, opt_id):
1541         try:
1542             #FIXME
1543             #idx = self._cache_paths[0].index(opt_id)
1544             return session.query(BaseOption).filter_by(id=opt_id).first()
1545         except ValueError:
1546             raise AttributeError(_('no id {0} found').format(opt_id))
1547
1548     def impl_get_path_by_opt(self, opt):
1549         try:
1550             return self._cache_paths[1][self._cache_paths[0].index(opt.id)]
1551         except ValueError:
1552             raise AttributeError(_('no option {0} found').format(opt))
1553
1554     # ____________________________________________________________
1555     def impl_set_group_type(self, group_type):
1556         """sets a given group object to an OptionDescription
1557
1558         :param group_type: an instance of `GroupType` or `MasterGroupType`
1559                               that lives in `setting.groups`
1560         """
1561         if self._optiondescription_group_type != groups.default:
1562             raise TypeError(_('cannot change group_type if already set '
1563                             '(old {0}, new {1})').format(self._optiondescription_group_type,
1564                                                          group_type))
1565         if isinstance(group_type, groups.GroupType):
1566             self._optiondescription_group_type = group_type
1567             if isinstance(group_type, groups.MasterGroupType):
1568                 #if master (same name has group) is set
1569                 #for collect all slaves
1570                 slaves = []
1571                 master = None
1572                 for child in self.impl_getchildren():
1573                     if isinstance(child, OptionDescription):
1574                         raise ValueError(_("master group {0} shall not have "
1575                                          "a subgroup").format(self.impl_getname()))
1576                     if isinstance(child, SymLinkOption):
1577                         raise ValueError(_("master group {0} shall not have "
1578                                          "a symlinkoption").format(self.impl_getname()))
1579                     if not child.impl_is_multi():
1580                         raise ValueError(_("not allowed option {0} "
1581                                          "in group {1}"
1582                                          ": this option is not a multi"
1583                                          "").format(child.impl_getname(), self.impl_getname()))
1584                     if child._name == self.impl_getname():
1585                         child._multitype = multitypes.master
1586                         master = child
1587                     else:
1588                         slaves.append(child)
1589                 if master is None:
1590                     raise ValueError(_('master group with wrong'
1591                                        ' master name for {0}'
1592                                        ).format(self.impl_getname()))
1593                 #FIXME debut reecriture
1594                 ##master_callback, master_callback_params = master.impl_get_callback()
1595                 #if master._callback is not None and master._callback[1] is not None:
1596                 #    for key, callbacks in master._callback[1].items():
1597                 #        for callbk in callbacks:
1598                 #            if isinstance(callbk, tuple):
1599                 #                if callbk[0] in slaves:
1600                 #                    raise ValueError(_("callback of master's option shall "
1601                 #                                       "not refered a slave's ones"))
1602                 master._master_slaves = tuple(slaves)
1603                 for child in self.impl_getchildren():
1604                     if child != master:
1605                         child._master_slaves = master
1606                         child._multitype = multitypes.slave
1607         else:
1608             raise ValueError(_('group_type: {0}'
1609                                ' not allowed').format(group_type))
1610
1611     def impl_get_group_type(self):
1612         return getattr(groups, self._optiondescription_group_type)
1613
1614     def _valid_consistency(self, option, value, context, index):
1615         if self._cache_consistencies is None:
1616             return True
1617         #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
1618         consistencies = self._cache_consistencies.get(option)
1619         if consistencies is not None:
1620             for func, all_cons_opts in consistencies:
1621                 #all_cons_opts[0] is the option where func is set
1622                 ret = all_cons_opts[0]._launch_consistency(func, option,
1623                                                            value,
1624                                                            context, index,
1625                                                            all_cons_opts)
1626                 if ret is False:
1627                     return False
1628         return True
1629
1630     # ____________________________________________________________
1631     # serialize object
1632
1633     def _impl_getstate(self, descr=None):
1634         """enables us to export into a dict
1635         :param descr: parent :class:`tiramisu.option.OptionDescription`
1636         """
1637         if descr is None:
1638             self.impl_build_cache()
1639             descr = self
1640         super(OptionDescription, self)._impl_getstate(descr)
1641         self._state_group_type = str(self._optiondescription_group_type)
1642         for option in self.impl_getchildren():
1643             option._impl_getstate(descr)
1644
1645     def __getstate__(self):
1646         """special method to enable the serialization with pickle
1647         """
1648         stated = True
1649         try:
1650             # the `_state` attribute is a flag that which tells us if
1651             # the serialization can be performed
1652             self._stated
1653         except AttributeError:
1654             # if cannot delete, _impl_getstate never launch
1655             # launch it recursivement
1656             # _stated prevent __getstate__ launch more than one time
1657             # _stated is delete, if re-serialize, re-lauch _impl_getstate
1658             self._impl_getstate()
1659             stated = False
1660         return super(OptionDescription, self).__getstate__(stated)
1661
1662     def _impl_setstate(self, descr=None):
1663         """enables us to import from a dict
1664         :param descr: parent :class:`tiramisu.option.OptionDescription`
1665         """
1666         if descr is None:
1667             self._cache_paths = None
1668             self._cache_consistencies = None
1669             self.impl_build_cache(force_no_consistencies=True)
1670             descr = self
1671         self._optiondescription_group_type = getattr(groups, self._state_group_type)
1672         del(self._state_group_type)
1673         super(OptionDescription, self)._impl_setstate(descr)
1674         for option in self.impl_getchildren():
1675             option._impl_setstate(descr)
1676
1677     def __setstate__(self, state):
1678         super(OptionDescription, self).__setstate__(state)
1679         try:
1680             self._stated
1681         except AttributeError:
1682             self._impl_setstate()
1683
1684
1685 def validate_requires_arg(requires, name):
1686     """check malformed requirements
1687     and tranform dict to internal tuple
1688
1689     :param requires: have a look at the
1690                      :meth:`tiramisu.setting.Settings.apply_requires` method to
1691                      know more about
1692                      the description of the requires dictionary
1693     """
1694     if requires is None:
1695         return None
1696     ret_requires = {}
1697     config_action = {}
1698
1699     # start parsing all requires given by user (has dict)
1700     # transforme it to a tuple
1701     for require in requires:
1702         if not type(require) == dict:
1703             raise ValueError(_("malformed requirements type for option:"
1704                                " {0}, must be a dict").format(name))
1705         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1706                       'same_action')
1707         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1708         if unknown_keys != frozenset():
1709             raise ValueError('malformed requirements for option: {0}'
1710                              ' unknown keys {1}, must only '
1711                              '{2}'.format(name,
1712                                           unknown_keys,
1713                                           valid_keys))
1714         # prepare all attributes
1715         try:
1716             option = require['option']
1717             expected = require['expected']
1718             action = require['action']
1719         except KeyError:
1720             raise ValueError(_("malformed requirements for option: {0}"
1721                                " require must have option, expected and"
1722                                " action keys").format(name))
1723         inverse = require.get('inverse', False)
1724         if inverse not in [True, False]:
1725             raise ValueError(_('malformed requirements for option: {0}'
1726                                ' inverse must be boolean'))
1727         transitive = require.get('transitive', True)
1728         if transitive not in [True, False]:
1729             raise ValueError(_('malformed requirements for option: {0}'
1730                                ' transitive must be boolean'))
1731         same_action = require.get('same_action', True)
1732         if same_action not in [True, False]:
1733             raise ValueError(_('malformed requirements for option: {0}'
1734                                ' same_action must be boolean'))
1735
1736         if not isinstance(option, Option):
1737             raise ValueError(_('malformed requirements '
1738                                'must be an option in option {0}').format(name))
1739         if option.impl_is_multi():
1740             raise ValueError(_('malformed requirements option {0} '
1741                                'should not be a multi').format(name))
1742         if expected is not None:
1743             try:
1744                 option._validate(expected)
1745             except ValueError as err:
1746                 raise ValueError(_('malformed requirements second argument '
1747                                    'must be valid for option {0}'
1748                                    ': {1}').format(name, err))
1749         if action in config_action:
1750             if inverse != config_action[action]:
1751                 raise ValueError(_("inconsistency in action types"
1752                                    " for option: {0}"
1753                                    " action: {1}").format(name, action))
1754         else:
1755             config_action[action] = inverse
1756         if action not in ret_requires:
1757             ret_requires[action] = {}
1758         if option not in ret_requires[action]:
1759             ret_requires[action][option] = (option, [expected], action,
1760                                             inverse, transitive, same_action)
1761         else:
1762             ret_requires[action][option][1].append(expected)
1763     ## transform dict to tuple
1764     #ret = []
1765     #for opt_requires in ret_requires.values():
1766     #    ret_action = []
1767     #    for require in opt_requires.values():
1768     #        ret_action.append((require[0], tuple(require[1]), require[2],
1769     #                           require[3], require[4], require[5]))
1770     #    ret.append(tuple(ret_action))
1771     return ret_requires
1772
1773
1774 def validate_callback(callback, callback_params, type_):
1775     if type(callback) != FunctionType:
1776         raise ValueError(_('{0} should be a function').format(type_))
1777     if callback_params is not None:
1778         if not isinstance(callback_params, dict):
1779             raise ValueError(_('{0}_params should be a dict').format(type_))
1780         for key, callbacks in callback_params.items():
1781             if key != '' and len(callbacks) != 1:
1782                 raise ValueError(_('{0}_params with key {1} should not have '
1783                                    'length different to 1').format(type_,
1784                                                                    key))
1785             if not isinstance(callbacks, tuple):
1786                 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1787                                    ).format(type_, key))
1788             for callbk in callbacks:
1789                 if isinstance(callbk, tuple):
1790                     option, force_permissive = callbk
1791                     if type_ == 'validator' and not force_permissive:
1792                         raise ValueError(_('validator not support tuple'))
1793                     if not isinstance(option, Option) and not \
1794                             isinstance(option, SymLinkOption):
1795                         raise ValueError(_('{0}_params should have an option '
1796                                            'not a {0} for first argument'
1797                                            ).format(type_, type(option)))
1798                     if force_permissive not in [True, False]:
1799                         raise ValueError(_('{0}_params should have a boolean'
1800                                            ' not a {0} for second argument'
1801                                            ).format(type_, type(
1802                                                force_permissive)))
1803
1804 #FIXME
1805 Base.metadata.create_all(engine)
1806 Session = sessionmaker(bind=engine)
1807 session = Session()