no more used and_
[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     try:
51         name = str(name)
52     except:
53         return False
54     if re.match(name_regexp, name) is None and not name.startswith('_') \
55             and name not in forbidden_names \
56             and not name.startswith('impl_') \
57             and not name.startswith('cfgimpl_'):
58         return True
59     else:
60         return False
61 #____________________________________________________________
62 #
63
64 engine = create_engine('sqlite:///:memory:')
65 Base = declarative_base()
66
67
68 class _RequireExpected(Base):
69     __tablename__ = 'expected'
70     id = Column(Integer, primary_key=True)
71     expected = Column(PickleType)
72     require = Column(Integer, ForeignKey('require.id'))
73
74     def __init__(self, expected):
75         self.expected = expected
76
77
78 class _RequireOption(Base):
79     __tablename__ = 'require'
80     id = Column(Integer, primary_key=True)
81     option = Column(Integer, ForeignKey('baseoption.id'))
82     r_opt = Column(Integer)
83     expected = relationship("_RequireExpected")
84     action = Column(String, nullable=False)
85     inverse = Column(Boolean, default=False)
86     transitive = Column(Boolean, default=True)
87     same_action = Column(Boolean, default=True)
88
89     def __init__(self, option, expected, action, inverse, transitive,
90                  same_action):
91         self.r_opt = option.id
92         for expect in expected:
93             self.expected.append(_RequireExpected(expect))
94         self.action = action
95         self.inverse = inverse
96         self.transitive = transitive
97         self.same_action = same_action
98
99     def get_expected(self):
100         for expected in self.expected:
101             yield(expected.expected)
102
103     def get_option(self, config):
104         return config.cfgimpl_get_description().impl_get_opt_by_id(self.r_opt)
105
106
107 property_table = Table('property', Base.metadata,
108                        Column('left_id', Integer, ForeignKey('propertyoption.name')),
109                        Column('right_id', Integer, ForeignKey('baseoption.id'))
110                        )
111
112
113 class _PropertyOption(Base):
114     __tablename__ = 'propertyoption'
115     name = Column(String, primary_key=True)
116
117     def __init__(self, name):
118         self.name = name
119
120
121 class _Information(Base):
122     __tablename__ = 'information'
123     id = Column(Integer, primary_key=True)
124     option = Column(Integer, ForeignKey('baseoption.id'))
125     key = Column(String)
126     value = Column(PickleType)
127
128     def __init__(self, key, value):
129         self.key = key
130         self.value = value
131
132
133 class _CallbackParamOption(Base):
134     __tablename__ = 'callback_param_option'
135     id = Column(Integer, primary_key=True)
136     callback_param = Column(Integer, ForeignKey('callback_param.id'))
137     option = Column(Integer)
138     force_permissive = Column(Boolean)
139     value = Column(PickleType)
140
141     def __init__(self, option=None, force_permissive=None,  value=None):
142         if value is not None:
143             self.value = value
144         else:
145             if isinstance(option, SymLinkOption):
146                 option = option._opt
147             self.option = option.id
148             self.force_permissive = force_permissive
149
150     def get_option(self, config):
151         return config.cfgimpl_get_description().impl_get_opt_by_id(self.option)
152
153
154 class _CallbackParam(Base):
155     __tablename__ = 'callback_param'
156     id = Column(Integer, primary_key=True)
157     callback = Column(Integer, ForeignKey('baseoption.id'))
158     name = Column(String)
159     params = relationship('_CallbackParamOption')
160
161     def __init__(self, name, params):
162         self.name = name
163         for param in params:
164             if isinstance(param, tuple):
165                 self.params.append(_CallbackParamOption(option=param[0],
166                                                         force_permissive=param[1]))
167             else:
168                 self.params.append(_CallbackParamOption(value=param))
169
170
171 consistency_table = Table('consistencyopt', Base.metadata,
172                           Column('left_id', Integer, ForeignKey('consistency.id')),
173                           Column('right_id', Integer, ForeignKey('baseoption.id'))
174                           )
175
176
177 class _Consistency(Base):
178     __tablename__ = 'consistency'
179     id = Column(Integer, primary_key=True)
180     func = Column(PickleType)
181
182     def __init__(self, func, all_cons_opts):
183         self.func = func
184         for option in all_cons_opts:
185             option._consistencies.append(self)
186
187
188 class BaseOption(Base):
189     """This abstract base class stands for attribute access
190     in options that have to be set only once, it is of course done in the
191     __setattr__ method
192     """
193     __tablename__ = 'baseoption'
194     id = Column(Integer, primary_key=True)
195     _name = Column(String)
196     _type = Column(PickleType)
197     _informations = relationship('_Information')
198     _default = Column(PickleType)
199     _default_multi = Column(PickleType)
200     _requires = relationship('_RequireOption')
201     _multi = Column(Boolean)
202     _multitype = Column(String)
203     _callback = Column(PickleType)
204     _callback_params = relationship('_CallbackParam')
205     _validator = Column(PickleType)
206     _validator_params = relationship('_CallbackParam')
207     _parent = Column(Integer, ForeignKey('baseoption.id'))
208     _children = relationship('BaseOption', enable_typechecks=False)
209     _properties = relationship('_PropertyOption', secondary=property_table,
210                                backref=backref('options', enable_typechecks=False))
211     _warnings_only = Column(Boolean)
212     _readonly = Column(Boolean, default=False)
213     _consistencies = relationship('_Consistency', secondary=consistency_table,
214                                   backref=backref('options', enable_typechecks=False))
215     _choice_values = Column(PickleType)
216     _choice_open_values = Column(Boolean)
217     #FIXME devrait etre une table
218     _optiondescription_group_type = Column(String)
219     #__slots__ = ('_name', '_requires', '_properties', '_readonly',
220     #             '_calc_properties', '_impl_informations',
221     #             '_state_readonly', '_state_requires', '_stated')
222
223     def __init__(self, name, doc, default=None, default_multi=None,
224                  requires=None, multi=False, callback=None,
225                  callback_params=None, validator=None, validator_params=None,
226                  properties=None, warnings_only=False, choice_values=None,
227                  choice_open_values=None):
228         if not valid_name(name):
229             raise ValueError(_("invalid name: {0} for option").format(name))
230         self._name = name
231         self._type = self.__class__
232         self.impl_set_information('doc', doc)
233         requires = validate_requires_arg(requires, self._name)
234         if requires is not None:
235             for values in requires.values():
236                 for require in values.values():
237                     self._requires.append(_RequireOption(*require))
238         if not multi and default_multi is not None:
239             raise ValueError(_("a default_multi is set whereas multi is False"
240                              " in option: {0}").format(name))
241         if default_multi is not None:
242             try:
243                 self._validate(default_multi)
244             except ValueError as err:
245                 raise ValueError(_("invalid default_multi value {0} "
246                                    "for option {1}: {2}").format(
247                                        str(default_multi), name, err))
248         self._multi = multi
249         if self._multi:
250             if default is None:
251                 default = []
252             self._multitype = multitypes.default
253             self._default_multi = default_multi
254         if callback is not None and ((not multi and (default is not None or
255                                                      default_multi is not None))
256                                      or (multi and (default != [] or
257                                                     default_multi is not None))
258                                      ):
259             raise ValueError(_("default value not allowed if option: {0} "
260                              "is calculated").format(name))
261         if properties is None:
262             properties = tuple()
263         if not isinstance(properties, tuple):
264             raise TypeError(_('invalid properties type {0} for {1},'
265                             ' must be a tuple').format(
266                                 type(properties),
267                                 self._name))
268         if validator is not None:
269             validate_callback(validator, validator_params, 'validator')
270             self._validator = validator
271             if validator_params is not None:
272                 for key, values in validator_params.items():
273                     self._validator_params.append(_CallbackParam(key, values))
274         if callback is None and callback_params is not None:
275             raise ValueError(_("params defined for a callback function but "
276                              "no callback defined"
277                              " yet for option {0}").format(name))
278         if callback is not None:
279             validate_callback(callback, callback_params, 'callback')
280             self._callback = callback
281             if callback_params is not None:
282                 for key, values in callback_params.items():
283                     self._callback_params.append(_CallbackParam(key, values))
284         if requires is not None and properties is not tuple():
285             set_forbidden_properties = set(properties) & set(requires.keys())
286             if set_forbidden_properties != frozenset():
287                 raise ValueError('conflict: properties already set in '
288                                  'requirement {0}'.format(
289                                      list(set_forbidden_properties)))
290         if choice_values is not None:
291             self._choice_values = choice_values
292         if choice_open_values is not None:
293             self._choice_open_values = choice_open_values
294         self.impl_validate(default)
295         if multi and default is None:
296             self._default = []
297         else:
298             self._default = default
299         for prop in properties:
300             prop_obj = session.query(_PropertyOption).filter(_PropertyOption.name == prop).first()
301             if prop_obj is None:
302                 prop_obj = _PropertyOption(prop)
303             self._properties.append(prop_obj)
304         self._warnings_only = warnings_only
305
306     # ____________________________________________________________
307     # information
308     def impl_set_information(self, key, value):
309         """updates the information's attribute
310         (which is a dictionary)
311
312         :param key: information's key (ex: "help", "doc"
313         :param value: information's value (ex: "the help string")
314         """
315         #FIXME pas append ! remplacer !
316         info = session.query(_Information).filter_by(option=self.id, key=key).first()
317         if info is None:
318             self._informations.append(_Information(key, value))
319         else:
320             info.value = value
321
322     def impl_get_information(self, key, default=None):
323         """retrieves one information's item
324
325         :param key: the item string (ex: "help")
326         """
327         info = session.query(_Information).filter_by(option=self.id, key=key).first()
328         if info is not None:
329             return info.value
330         elif default is not None:
331             return default
332         else:
333             raise ValueError(_("information's item not found: {0}").format(
334                 key))
335
336     # ____________________________________________________________
337     # serialize object
338     def _impl_convert_requires(self, descr, load=False):
339         """export of the requires during the serialization process
340
341         :type descr: :class:`tiramisu.option.OptionDescription`
342         :param load: `True` if we are at the init of the option description
343         :type load: bool
344         """
345         if not load and self._requires is None:
346             self._state_requires = None
347         elif load and self._state_requires is None:
348             self._requires = None
349             del(self._state_requires)
350         else:
351             if load:
352                 _requires = self._state_requires
353             else:
354                 _requires = self._requires
355             new_value = []
356             for requires in _requires:
357                 new_requires = []
358                 for require in requires:
359                     if load:
360                         new_require = [descr.impl_get_opt_by_path(require[0])]
361                     else:
362                         new_require = [descr.impl_get_path_by_opt(require[0])]
363                     new_require.extend(require[1:])
364                     new_requires.append(tuple(new_require))
365                 new_value.append(tuple(new_requires))
366             if load:
367                 del(self._state_requires)
368                 self._requires = new_value
369             else:
370                 self._state_requires = new_value
371
372     # serialize
373     def _impl_getstate(self, descr):
374         """the under the hood stuff that need to be done
375         before the serialization.
376
377         :param descr: the parent :class:`tiramisu.option.OptionDescription`
378         """
379         self._stated = True
380         for func in dir(self):
381             if func.startswith('_impl_convert_'):
382                 getattr(self, func)(descr)
383         self._state_readonly = self._readonly
384
385     def __getstate__(self, stated=True):
386         """special method to enable the serialization with pickle
387         Usualy, a `__getstate__` method does'nt need any parameter,
388         but somme under the hood stuff need to be done before this action
389
390         :parameter stated: if stated is `True`, the serialization protocol
391                            can be performed, not ready yet otherwise
392         :parameter type: bool
393         """
394         try:
395             self._stated
396         except AttributeError:
397             raise SystemError(_('cannot serialize Option, '
398                                 'only in OptionDescription'))
399         slots = set()
400         for subclass in self.__class__.__mro__:
401             if subclass is not object:
402                 slots.update(subclass.__slots__)
403         slots -= frozenset(['_cache_paths', '_cache_consistencies',
404                             '__weakref__'])
405         states = {}
406         for slot in slots:
407             # remove variable if save variable converted
408             # in _state_xxxx variable
409             if '_state' + slot not in slots:
410                 if slot.startswith('_state'):
411                     # should exists
412                     states[slot] = getattr(self, slot)
413                     # remove _state_xxx variable
414                     self.__delattr__(slot)
415                 else:
416                     try:
417                         states[slot] = getattr(self, slot)
418                     except AttributeError:
419                         pass
420         if not stated:
421             del(states['_stated'])
422         return states
423
424     # unserialize
425     def _impl_setstate(self, descr):
426         """the under the hood stuff that need to be done
427         before the serialization.
428
429         :type descr: :class:`tiramisu.option.OptionDescription`
430         """
431         for func in dir(self):
432             if func.startswith('_impl_convert_'):
433                 getattr(self, func)(descr, load=True)
434         try:
435             self._readonly = self._state_readonly
436             del(self._state_readonly)
437             del(self._stated)
438         except AttributeError:
439             pass
440
441     def __setstate__(self, state):
442         """special method that enables us to serialize (pickle)
443
444         Usualy, a `__setstate__` method does'nt need any parameter,
445         but somme under the hood stuff need to be done before this action
446
447         :parameter state: a dict is passed to the loads, it is the attributes
448                           of the options object
449         :type state: dict
450         """
451         for key, value in state.items():
452             setattr(self, key, value)
453
454     def impl_getname(self):
455         return self._name
456
457
458 class Option(BaseOption):
459     """
460     Abstract base class for configuration option's.
461
462     Reminder: an Option object is **not** a container for the value.
463     """
464 #    __slots__ = ('_multi', '_validator', '_default_multi', '_default',
465 #                 '_state_callback', '_callback', '_multitype',
466 #                 '_consistencies', '_warnings_only', '_master_slaves',
467 #                 '_state_consistencies', '__weakref__')
468     _empty = ''
469
470     def __init__(self, name, doc, default=None, default_multi=None,
471                  requires=None, multi=False, callback=None,
472                  callback_params=None, validator=None, validator_params=None,
473                  properties=None, warnings_only=False, choice_values=None,
474                  choice_open_values=None):
475         """
476         :param name: the option's name
477         :param doc: the option's description
478         :param default: specifies the default value of the option,
479                         for a multi : ['bla', 'bla', 'bla']
480         :param default_multi: 'bla' (used in case of a reset to default only at
481                         a given index)
482         :param requires: is a list of names of options located anywhere
483                          in the configuration.
484         :param multi: if true, the option's value is a list
485         :param callback: the name of a function. If set, the function's output
486                          is responsible of the option's value
487         :param callback_params: the callback's parameter
488         :param validator: the name of a function which stands for a custom
489                           validation of the value
490         :param validator_params: the validator's parameters
491         :param properties: tuple of default properties
492         :param warnings_only: _validator and _consistencies don't raise if True
493                              Values()._warning contain message
494
495         """
496         super(Option, self).__init__(name, doc, default, default_multi,
497                                      requires, multi, callback,
498                                      callback_params, validator,
499                                      validator_params, properties,
500                                      warnings_only, choice_values,
501                                      choice_open_values)
502         session.add(self)
503         session.commit()
504
505     #def __setattr__(self, name, value):
506     #    """set once and only once some attributes in the option,
507     #    like `_name`. `_name` cannot be changed one the option and
508     #    pushed in the :class:`tiramisu.option.OptionDescription`.
509
510     #    if the attribute `_readonly` is set to `True`, the option is
511     #    "frozen" (which has noting to do with the high level "freeze"
512     #    propertie or "read_only" property)
513     #    """
514     #    #FIXME ne devrait pas pouvoir redefinir _option
515     #    if not name == '_option':
516     #        is_readonly = False
517     #        # never change _name
518     #        if name == '_name':
519     #            try:
520     #                self._name
521     #                #so _name is already set
522     #                is_readonly = True
523     #            except:
524     #                pass
525     #        elif name != '_readonly':
526     #            try:
527     #                if self._readonly is True:
528     #                    is_readonly = True
529     #            except AttributeError:
530     #                self._readonly = False
531     #        if is_readonly:
532     #            raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
533     #                                   " read-only").format(
534     #                                       self.__class__.__name__,
535     #                                       self._name,
536     #                                       name))
537     #    object.__setattr__(self, name, value)
538
539     def impl_getproperties(self):
540         for prop in self._properties:
541             yield(prop.name)
542
543     def impl_getrequires(self):
544         return self._requires
545
546     def _launch_consistency(self, func, option, value, context, index,
547                             all_cons_opts):
548         """Launch consistency now
549
550         :param func: function name, this name should start with _cons_
551         :type func: `str`
552         :param option: option that value is changing
553         :type option: `tiramisu.option.Option`
554         :param value: new value of this option
555         :param context: Config's context, if None, check default value instead
556         :type context: `tiramisu.config.Config`
557         :param index: only for multi option, consistency should be launch for
558                       specified index
559         :type index: `int`
560         :param all_cons_opts: all options concerne by this consistency
561         :type all_cons_opts: `list` of `tiramisu.option.Option`
562         """
563         if context is not None:
564             descr = context.cfgimpl_get_description()
565         #option is also in all_cons_opts
566         if option not in all_cons_opts:
567             raise ConfigError(_('option not in all_cons_opts'))
568
569         all_cons_vals = []
570         for opt in all_cons_opts:
571             #get value
572             if option == opt:
573                 opt_value = value
574             else:
575                 #if context, calculate value, otherwise get default value
576                 if context is not None:
577                     opt_value = context._getattr(
578                         descr.impl_get_path_by_opt(opt), validate=False)
579                 else:
580                     opt_value = opt.impl_getdefault()
581
582             #append value
583             if not self.impl_is_multi() or option == opt:
584                 all_cons_vals.append(opt_value)
585             else:
586                 #value is not already set, could be higher index
587                 try:
588                     all_cons_vals.append(opt_value[index])
589                 except IndexError:
590                     #so return if no value
591                     return
592         getattr(self, func)(all_cons_opts, all_cons_vals)
593
594     def impl_validate(self, value, context=None, validate=True,
595                       force_index=None):
596         """
597         :param value: the option's value
598         :param context: Config's context
599         :type context: :class:`tiramisu.config.Config`
600         :param validate: if true enables ``self._validator`` validation
601         :type validate: boolean
602         :param force_no_multi: if multi, value has to be a list
603                                not if force_no_multi is True
604         :type force_no_multi: boolean
605         """
606         if not validate:
607             return
608
609         def val_validator(val):
610             if self._validator is not None:
611                 class FakeCallbackParamOption(object):
612                     def __init__(self, option=None,
613                                  force_permissive=None,
614                                  value=None):
615                         self.option = option
616                         self.force_permissive = force_permissive
617                         self.value = value
618
619                 class FakeCallbackParam(object):
620                     def __init__(self, key, params):
621                         self.name = key
622                         self.params = params
623                 if self._validator_params != []:
624                     validator_params = []
625                     validator_params_option = [FakeCallbackParamOption(value=val)]
626                     #if '' in self._validator_params:
627                     #    for param in self._validator_params['']:
628                     #        if isinstance(param, tuple):
629                     #            validator_params_option.append(FakeCallbackParamOption(option=param[0], force_permissive=param[1]))
630                     #        else:
631                     #            validator_params_option.append(FakeCallbackParamOption(value=param))
632                     validator_params.append(FakeCallbackParam('', validator_params_option))
633                     for key in self._validator_params:
634                         if key.name != '':
635                             validator_params.append(key)
636                 else:
637                     validator_params = (FakeCallbackParam('', (FakeCallbackParamOption(value=val),)),)
638                 # Raise ValueError if not valid
639                 carry_out_calculation(self.impl_getname(), config=context,
640                                       callback=self._validator,
641                                       callback_params=validator_params)
642
643         def do_validation(_value, _index=None):
644             if _value is None:
645                 return
646             # option validation
647             try:
648                 self._validate(_value)
649             except ValueError as err:
650                 raise ValueError(_('invalid value for option {0}: {1}'
651                                    '').format(self.impl_getname(), err))
652             try:
653                 # valid with self._validator
654                 val_validator(_value)
655                 # if not context launch consistency validation
656                 if context is not None:
657                     descr._valid_consistency(self, _value, context, _index)
658                 self._second_level_validation(_value)
659             except ValueError as err:
660                 msg = _("invalid value for option {0}: {1}").format(
661                     self.impl_getname(), err)
662                 if self._warnings_only:
663                     warnings.warn_explicit(ValueWarning(msg, self),
664                                            ValueWarning,
665                                            self.__class__.__name__, 0)
666                 else:
667                     raise ValueError(msg)
668
669         # generic calculation
670         if context is not None:
671             descr = context.cfgimpl_get_description()
672
673         if not self._multi or force_index is not None:
674             do_validation(value, force_index)
675         else:
676             if not isinstance(value, list):
677                 raise ValueError(_("which must be a list").format(value,
678                                                                   self.impl_getname()))
679             for index, val in enumerate(value):
680                 do_validation(val, index)
681
682     def impl_getdefault(self, default_multi=False):
683         "accessing the default value"
684         if not default_multi or not self.impl_is_multi():
685             return self._default
686         else:
687             return self.getdefault_multi()
688
689     def impl_getdefault_multi(self):
690         "accessing the default value for a multi"
691         return self._default_multi
692
693     def impl_get_multitype(self):
694         return self._multitype
695
696     def impl_get_master_slaves(self):
697         return self._master_slaves
698
699     def impl_is_empty_by_default(self):
700         "no default value has been set yet"
701         if ((not self.impl_is_multi() and self._default is None) or
702                 (self.impl_is_multi() and (self._default == []
703                                            or None in self._default))):
704             return True
705         return False
706
707     def impl_getdoc(self):
708         "accesses the Option's doc"
709         return self.impl_get_information('doc')
710
711     def impl_has_callback(self):
712         "to know if a callback has been defined or not"
713         if self._callback is None:
714             return False
715         else:
716             return True
717
718     def impl_get_callback(self):
719         return self._callback, self._callback_params
720
721     #def impl_getkey(self, value):
722     #    return value
723
724     def impl_is_multi(self):
725         return self._multi
726
727     def impl_add_consistency(self, func, *other_opts):
728         """Add consistency means that value will be validate with other_opts
729         option's values.
730
731         :param func: function's name
732         :type func: `str`
733         :param other_opts: options used to validate value
734         :type other_opts: `list` of `tiramisu.option.Option`
735         """
736         for opt in other_opts:
737             if not isinstance(opt, Option):
738                 raise ConfigError(_('consistency should be set with an option'))
739             if self is opt:
740                 raise ConfigError(_('cannot add consistency with itself'))
741             if self.impl_is_multi() != opt.impl_is_multi():
742                 raise ConfigError(_('every options in consistency should be '
743                                     'multi or none'))
744         func = '_cons_{0}'.format(func)
745         all_cons_opts = tuple([self] + list(other_opts))
746         value = self.impl_getdefault()
747         if value is not None:
748             if self.impl_is_multi():
749                 for idx, val in enumerate(value):
750                     self._launch_consistency(func, self, val, None,
751                                              idx, all_cons_opts)
752             else:
753                 self._launch_consistency(func, self, value, None,
754                                          None, all_cons_opts)
755         _Consistency(func, all_cons_opts)
756         self.impl_validate(self.impl_getdefault())
757
758     def _cons_not_equal(self, opts, vals):
759         for idx_inf, val_inf in enumerate(vals):
760             for idx_sup, val_sup in enumerate(vals[idx_inf + 1:]):
761                 if val_inf == val_sup is not None:
762                     raise ValueError(_("same value for {0} and {1}").format(
763                         opts[idx_inf].impl_getname(), opts[idx_inf + idx_sup + 1].impl_getname()))
764
765     def _impl_convert_callbacks(self, descr, load=False):
766         #FIXME
767         if not load and self._callback is None:
768             self._state_callback = None
769         elif load and self._state_callback is None:
770             self._callback = None
771             del(self._state_callback)
772         else:
773             if load:
774                 callback, callback_params = self._state_callback
775             else:
776                 callback, callback_params = self._callback
777             if callback_params is not None:
778                 cllbck_prms = {}
779                 for key, values in callback_params.items():
780                     vls = []
781                     for value in values:
782                         if isinstance(value, tuple):
783                             if load:
784                                 value = (descr.impl_get_opt_by_path(value[0]),
785                                          value[1])
786                             else:
787                                 value = (descr.impl_get_path_by_opt(value[0]),
788                                          value[1])
789                         vls.append(value)
790                     cllbck_prms[key] = tuple(vls)
791             else:
792                 cllbck_prms = None
793
794             if load:
795                 del(self._state_callback)
796                 self._callback = (callback, cllbck_prms)
797             else:
798                 self._state_callback = (callback, cllbck_prms)
799
800     # serialize/unserialize
801     def _impl_convert_consistencies(self, descr, load=False):
802         """during serialization process, many things have to be done.
803         one of them is the localisation of the options.
804         The paths are set once for all.
805
806         :type descr: :class:`tiramisu.option.OptionDescription`
807         :param load: `True` if we are at the init of the option description
808         :type load: bool
809         """
810         if not load and self._consistencies is None:
811             self._state_consistencies = None
812         elif load and self._state_consistencies is None:
813             self._consistencies = None
814             del(self._state_consistencies)
815         else:
816             if load:
817                 consistencies = self._state_consistencies
818             else:
819                 consistencies = self._consistencies
820             if isinstance(consistencies, list):
821                 new_value = []
822                 for consistency in consistencies:
823                     values = []
824                     for obj in consistency[1]:
825                         if load:
826                             values.append(descr.impl_get_opt_by_path(obj))
827                         else:
828                             values.append(descr.impl_get_path_by_opt(obj))
829                     new_value.append((consistency[0], tuple(values)))
830
831             else:
832                 new_value = {}
833                 for key, _consistencies in consistencies.items():
834                     new_value[key] = []
835                     for key_cons, _cons in _consistencies:
836                         _list_cons = []
837                         for _con in _cons:
838                             if load:
839                                 _list_cons.append(
840                                     descr.impl_get_opt_by_path(_con))
841                             else:
842                                 _list_cons.append(
843                                     descr.impl_get_path_by_opt(_con))
844                         new_value[key].append((key_cons, tuple(_list_cons)))
845             if load:
846                 del(self._state_consistencies)
847                 self._consistencies = new_value
848             else:
849                 self._state_consistencies = new_value
850
851     def _second_level_validation(self, value):
852         pass
853
854
855 class ChoiceOption(Option):
856     """represents a choice out of several objects.
857
858     The option can also have the value ``None``
859     """
860
861     #__slots__ = ('_values', '_open_values')
862     _opt_type = 'string'
863
864     def __init__(self, name, doc, values, default=None, default_multi=None,
865                  requires=None, multi=False, callback=None,
866                  callback_params=None, open_values=False, validator=None,
867                  validator_params=None, properties=None, warnings_only=False):
868         """
869         :param values: is a list of values the option can possibly take
870         """
871         if not isinstance(values, tuple):
872             raise TypeError(_('values must be a tuple for {0}').format(name))
873         if open_values not in (True, False):
874             raise TypeError(_('open_values must be a boolean for '
875                             '{0}').format(name))
876         super(ChoiceOption, self).__init__(name, doc, default=default,
877                                            default_multi=default_multi,
878                                            callback=callback,
879                                            callback_params=callback_params,
880                                            requires=requires,
881                                            multi=multi,
882                                            validator=validator,
883                                            validator_params=validator_params,
884                                            properties=properties,
885                                            warnings_only=warnings_only,
886                                            choice_values=values,
887                                            choice_open_values=open_values)
888
889     def impl_get_values(self):
890         return self._choice_values
891
892     def impl_is_openvalues(self):
893         return self._choice_open_values
894
895     def _validate(self, value):
896         if not self._choice_open_values and not value in self._choice_values:
897             raise ValueError(_('value {0} is not permitted, '
898                                'only {1} is allowed'
899                                '').format(value, self._choice_values))
900
901
902 class BoolOption(Option):
903     "represents a choice between ``True`` and ``False``"
904     __slots__ = tuple()
905     _opt_type = 'bool'
906
907     def _validate(self, value):
908         if not isinstance(value, bool):
909             raise ValueError(_('invalid boolean'))
910
911
912 class IntOption(Option):
913     "represents a choice of an integer"
914     __slots__ = tuple()
915     _opt_type = 'int'
916
917     def _validate(self, value):
918         if not isinstance(value, int):
919             raise ValueError(_('invalid integer'))
920
921
922 class FloatOption(Option):
923     "represents a choice of a floating point number"
924     __slots__ = tuple()
925     _opt_type = 'float'
926
927     def _validate(self, value):
928         if not isinstance(value, float):
929             raise ValueError(_('invalid float'))
930
931
932 class StrOption(Option):
933     "represents the choice of a string"
934     __slots__ = tuple()
935     _opt_type = 'string'
936
937     def _validate(self, value):
938         if not isinstance(value, str):
939             raise ValueError(_('invalid string'))
940
941
942 if sys.version_info[0] >= 3:
943     #UnicodeOption is same has StrOption in python 3+
944     class UnicodeOption(StrOption):
945         __slots__ = tuple()
946         pass
947 else:
948     class UnicodeOption(Option):
949         "represents the choice of a unicode string"
950         __slots__ = tuple()
951         _opt_type = 'unicode'
952         _empty = u''
953
954         def _validate(self, value):
955             if not isinstance(value, unicode):
956                 raise ValueError(_('invalid unicode'))
957
958
959 class SymLinkOption(BaseOption):
960     #__slots__ = ('_name', '_opt', '_state_opt', '_readonly', '_parent')
961     _opt_type = 'symlink'
962     #not return _opt consistencies
963     #_consistencies = None
964
965     def __init__(self, name, opt):
966         self._name = name
967         if not isinstance(opt, Option):
968             raise ValueError(_('malformed symlinkoption '
969                                'must be an option '
970                                'for symlink {0}').format(name))
971         self._opt = opt
972         self._readonly = True
973         self._parent = None
974         self._type = self.__class__
975         session.add(self)
976         session.commit()
977
978     def __getattr__(self, name):
979         if name in ('_opt', '_opt_type', '_readonly', 'impl_getname'):
980             return object.__getattr__(self, name)
981         else:
982             return getattr(self._opt, name)
983
984     def _impl_getstate(self, descr):
985         super(SymLinkOption, self)._impl_getstate(descr)
986         self._state_opt = descr.impl_get_path_by_opt(self._opt)
987
988     def _impl_setstate(self, descr):
989         self._opt = descr.impl_get_opt_by_path(self._state_opt)
990         del(self._state_opt)
991         super(SymLinkOption, self)._impl_setstate(descr)
992
993     def impl_getname(self):
994         return self._name
995
996     def impl_get_information(self, key, default=None):
997         #FIXME ne devrait pas etre util si ?
998         return self._opt.impl_get_information(key, default)
999
1000
1001 class IPOption(Option):
1002     "represents the choice of an ip"
1003     __slots__ = ('_private_only', '_allow_reserved')
1004     _opt_type = 'ip'
1005
1006     def __init__(self, name, doc, default=None, default_multi=None,
1007                  requires=None, multi=False, callback=None,
1008                  callback_params=None, validator=None, validator_params=None,
1009                  properties=None, private_only=False, allow_reserved=False,
1010                  warnings_only=False):
1011         self._private_only = private_only
1012         self._allow_reserved = allow_reserved
1013         super(IPOption, self).__init__(name, doc, default=default,
1014                                        default_multi=default_multi,
1015                                        callback=callback,
1016                                        callback_params=callback_params,
1017                                        requires=requires,
1018                                        multi=multi,
1019                                        validator=validator,
1020                                        validator_params=validator_params,
1021                                        properties=properties,
1022                                        warnings_only=warnings_only)
1023
1024     def _validate(self, value):
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                 identical_master_child_name = False
1537                 #for collect all slaves
1538                 slaves = []
1539                 master = None
1540                 for child in self.impl_getchildren():
1541                     if isinstance(child, OptionDescription):
1542                         raise ValueError(_("master group {0} shall not have "
1543                                          "a subgroup").format(self.impl_getname()))
1544                     if isinstance(child, SymLinkOption):
1545                         raise ValueError(_("master group {0} shall not have "
1546                                          "a symlinkoption").format(self.impl_getname()))
1547                     if not child.impl_is_multi():
1548                         raise ValueError(_("not allowed option {0} "
1549                                          "in group {1}"
1550                                          ": this option is not a multi"
1551                                          "").format(child.impl_getname(), self.impl_getname()))
1552                     if child.impl_getname() == self.impl_getname():
1553                         identical_master_child_name = True
1554                         child._multitype = multitypes.master
1555                         master = child
1556                     else:
1557                         slaves.append(child)
1558                 if master is None:
1559                     raise ValueError(_('master group with wrong'
1560                                        ' master name for {0}'
1561                                        ).format(self.impl_getname()))
1562                 master._master_slaves = tuple(slaves)
1563                 for child in self.impl_getchildren():
1564                     if child != master:
1565                         child._master_slaves = master
1566                         child._multitype = multitypes.slave
1567                 if not identical_master_child_name:
1568                     raise ValueError(_("no child has same nom has master group"
1569                                        " for: {0}").format(self.impl_getname()))
1570         else:
1571             raise ValueError(_('group_type: {0}'
1572                                ' not allowed').format(group_type))
1573
1574     def impl_get_group_type(self):
1575         return getattr(groups, self._optiondescription_group_type)
1576
1577     def _valid_consistency(self, option, value, context, index):
1578         if self._cache_consistencies is None:
1579             return True
1580         #consistencies is something like [('_cons_not_equal', (opt1, opt2))]
1581         consistencies = self._cache_consistencies.get(option)
1582         if consistencies is not None:
1583             for func, all_cons_opts in consistencies:
1584                 #all_cons_opts[0] is the option where func is set
1585                 ret = all_cons_opts[0]._launch_consistency(func, option,
1586                                                            value,
1587                                                            context, index,
1588                                                            all_cons_opts)
1589                 if ret is False:
1590                     return False
1591         return True
1592
1593     # ____________________________________________________________
1594     # serialize object
1595
1596     def _impl_getstate(self, descr=None):
1597         """enables us to export into a dict
1598         :param descr: parent :class:`tiramisu.option.OptionDescription`
1599         """
1600         if descr is None:
1601             self.impl_build_cache()
1602             descr = self
1603         super(OptionDescription, self)._impl_getstate(descr)
1604         self._state_group_type = str(self._optiondescription_group_type)
1605         for option in self.impl_getchildren():
1606             option._impl_getstate(descr)
1607
1608     def __getstate__(self):
1609         """special method to enable the serialization with pickle
1610         """
1611         stated = True
1612         try:
1613             # the `_state` attribute is a flag that which tells us if
1614             # the serialization can be performed
1615             self._stated
1616         except AttributeError:
1617             # if cannot delete, _impl_getstate never launch
1618             # launch it recursivement
1619             # _stated prevent __getstate__ launch more than one time
1620             # _stated is delete, if re-serialize, re-lauch _impl_getstate
1621             self._impl_getstate()
1622             stated = False
1623         return super(OptionDescription, self).__getstate__(stated)
1624
1625     def _impl_setstate(self, descr=None):
1626         """enables us to import from a dict
1627         :param descr: parent :class:`tiramisu.option.OptionDescription`
1628         """
1629         if descr is None:
1630             self._cache_paths = None
1631             self._cache_consistencies = None
1632             self.impl_build_cache(force_no_consistencies=True)
1633             descr = self
1634         self._optiondescription_group_type = getattr(groups, self._state_group_type)
1635         del(self._state_group_type)
1636         super(OptionDescription, self)._impl_setstate(descr)
1637         for option in self.impl_getchildren():
1638             option._impl_setstate(descr)
1639
1640     def __setstate__(self, state):
1641         super(OptionDescription, self).__setstate__(state)
1642         try:
1643             self._stated
1644         except AttributeError:
1645             self._impl_setstate()
1646
1647
1648 def validate_requires_arg(requires, name):
1649     """check malformed requirements
1650     and tranform dict to internal tuple
1651
1652     :param requires: have a look at the
1653                      :meth:`tiramisu.setting.Settings.apply_requires` method to
1654                      know more about
1655                      the description of the requires dictionary
1656     """
1657     if requires is None:
1658         return None
1659     ret_requires = {}
1660     config_action = {}
1661
1662     # start parsing all requires given by user (has dict)
1663     # transforme it to a tuple
1664     for require in requires:
1665         if not type(require) == dict:
1666             raise ValueError(_("malformed requirements type for option:"
1667                                " {0}, must be a dict").format(name))
1668         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1669                       'same_action')
1670         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1671         if unknown_keys != frozenset():
1672             raise ValueError('malformed requirements for option: {0}'
1673                              ' unknown keys {1}, must only '
1674                              '{2}'.format(name,
1675                                           unknown_keys,
1676                                           valid_keys))
1677         # prepare all attributes
1678         try:
1679             option = require['option']
1680             expected = require['expected']
1681             action = require['action']
1682         except KeyError:
1683             raise ValueError(_("malformed requirements for option: {0}"
1684                                " require must have option, expected and"
1685                                " action keys").format(name))
1686         inverse = require.get('inverse', False)
1687         if inverse not in [True, False]:
1688             raise ValueError(_('malformed requirements for option: {0}'
1689                                ' inverse must be boolean'))
1690         transitive = require.get('transitive', True)
1691         if transitive not in [True, False]:
1692             raise ValueError(_('malformed requirements for option: {0}'
1693                                ' transitive must be boolean'))
1694         same_action = require.get('same_action', True)
1695         if same_action not in [True, False]:
1696             raise ValueError(_('malformed requirements for option: {0}'
1697                                ' same_action must be boolean'))
1698
1699         if not isinstance(option, Option):
1700             raise ValueError(_('malformed requirements '
1701                                'must be an option in option {0}').format(name))
1702         if option.impl_is_multi():
1703             raise ValueError(_('malformed requirements option {0} '
1704                                'should not be a multi').format(name))
1705         if expected is not None:
1706             try:
1707                 option._validate(expected)
1708             except ValueError as err:
1709                 raise ValueError(_('malformed requirements second argument '
1710                                    'must be valid for option {0}'
1711                                    ': {1}').format(name, err))
1712         if action in config_action:
1713             if inverse != config_action[action]:
1714                 raise ValueError(_("inconsistency in action types"
1715                                    " for option: {0}"
1716                                    " action: {1}").format(name, action))
1717         else:
1718             config_action[action] = inverse
1719         if action not in ret_requires:
1720             ret_requires[action] = {}
1721         if option not in ret_requires[action]:
1722             ret_requires[action][option] = (option, [expected], action,
1723                                             inverse, transitive, same_action)
1724         else:
1725             ret_requires[action][option][1].append(expected)
1726     ## transform dict to tuple
1727     #ret = []
1728     #for opt_requires in ret_requires.values():
1729     #    ret_action = []
1730     #    for require in opt_requires.values():
1731     #        ret_action.append((require[0], tuple(require[1]), require[2],
1732     #                           require[3], require[4], require[5]))
1733     #    ret.append(tuple(ret_action))
1734     return ret_requires
1735
1736
1737 def validate_callback(callback, callback_params, type_):
1738     if type(callback) != FunctionType:
1739         raise ValueError(_('{0} should be a function').format(type_))
1740     if callback_params is not None:
1741         if not isinstance(callback_params, dict):
1742             raise ValueError(_('{0}_params should be a dict').format(type_))
1743         for key, callbacks in callback_params.items():
1744             if key != '' and len(callbacks) != 1:
1745                 raise ValueError(_('{0}_params with key {1} should not have '
1746                                    'length different to 1').format(type_,
1747                                                                    key))
1748             if not isinstance(callbacks, tuple):
1749                 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1750                                    ).format(type_, key))
1751             for callbk in callbacks:
1752                 if isinstance(callbk, tuple):
1753                     option, force_permissive = callbk
1754                     if type_ == 'validator' and not force_permissive:
1755                         raise ValueError(_('validator not support tuple'))
1756                     if not isinstance(option, Option) and not \
1757                             isinstance(option, SymLinkOption):
1758                         raise ValueError(_('{0}_params should have an option '
1759                                            'not a {0} for first argument'
1760                                            ).format(type_, type(option)))
1761                     if force_permissive not in [True, False]:
1762                         raise ValueError(_('{0}_params should have a boolean'
1763                                            ' not a {0} for second argument'
1764                                            ).format(type_, type(
1765                                                force_permissive)))
1766
1767 #FIXME
1768 Base.metadata.create_all(engine)
1769 Session = sessionmaker(bind=engine)
1770 session = Session()