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