add _second_level_validation (second's one return only warning almost _validator...
[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, deepcopy
26 from types import FunctionType
27 from IPy import IP
28
29 from tiramisu.error import ConflictError
30 from tiramisu.setting import groups, multitypes
31 from tiramisu.i18n import _
32 from tiramisu.autolib import carry_out_calculation
33
34 name_regexp = re.compile(r'^\d+')
35 forbidden_names = ('iter_all', 'iter_group', 'find', 'find_first',
36                    'make_dict', 'unwrap_from_path', 'read_only',
37                    'read_write', 'getowner', 'set_contexts')
38
39
40 def valid_name(name):
41     "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
42     try:
43         name = str(name)
44     except:
45         return False
46     if re.match(name_regexp, name) is None and not name.startswith('_') \
47             and name not in forbidden_names \
48             and not name.startswith('impl_') \
49             and not name.startswith('cfgimpl_'):
50         return True
51     else:
52         return False
53 #____________________________________________________________
54 #
55
56
57 class BaseOption(object):
58     """This abstract base class stands for attribute access
59     in options that have to be set only once, it is of course done in the
60     __setattr__ method
61     """
62     __slots__ = ('_name', '_requires', '_properties', '_readonly',
63                  '_consistencies', '_calc_properties', '_impl_informations',
64                  '_state_consistencies', '_state_readonly', '_state_requires',
65                  '_stated')
66
67     def __init__(self, name, doc, requires, properties):
68         if not valid_name(name):
69             raise ValueError(_("invalid name: {0} for option").format(name))
70         self._name = name
71         self._impl_informations = {}
72         self.impl_set_information('doc', doc)
73         self._calc_properties, self._requires = validate_requires_arg(
74             requires, self._name)
75         self._consistencies = None
76         if properties is None:
77             properties = tuple()
78         if not isinstance(properties, tuple):
79             raise TypeError(_('invalid properties type {0} for {1},'
80                             ' must be a tuple').format(
81                                 type(properties),
82                                 self._name))
83         if self._calc_properties is not None and properties is not tuple():
84             set_forbidden_properties = set(properties) & self._calc_properties
85             if set_forbidden_properties != frozenset():
86                 raise ValueError('conflict: properties already set in '
87                                  'requirement {0}'.format(
88                                      list(set_forbidden_properties)))
89         self._properties = properties  # 'hidden', 'disabled'...
90
91     def __setattr__(self, name, value):
92         """set once and only once some attributes in the option,
93         like `_name`. `_name` cannot be changed one the option and
94         pushed in the :class:`tiramisu.option.OptionDescription`.
95
96         if the attribute `_readonly` is set to `True`, the option is
97         "frozen" (which has noting to do with the high level "freeze"
98         propertie or "read_only" property)
99         """
100         if not name.startswith('_state') and name not in ('_cache_paths',
101                                                           '_consistencies'):
102             is_readonly = False
103             # never change _name
104             if name == '_name':
105                 try:
106                     self._name
107                     #so _name is already set
108                     is_readonly = True
109                 except:
110                     pass
111             try:
112                 if self._readonly is True:
113                     if value is True:
114                         # already readonly and try to re set readonly
115                         # don't raise, just exit
116                         return
117                     is_readonly = True
118             except AttributeError:
119                 pass
120             if is_readonly:
121                 raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
122                                        " read-only").format(
123                                            self.__class__.__name__,
124                                            self._name,
125                                            name))
126         object.__setattr__(self, name, value)
127
128     # information
129     def impl_set_information(self, key, value):
130         """updates the information's attribute
131         (which is a dictionary)
132
133         :param key: information's key (ex: "help", "doc"
134         :param value: information's value (ex: "the help string")
135         """
136         self._impl_informations[key] = value
137
138     def impl_get_information(self, key, default=None):
139         """retrieves one information's item
140
141         :param key: the item string (ex: "help")
142         """
143         if key in self._impl_informations:
144             return self._impl_informations[key]
145         elif default is not None:
146             return default
147         else:
148             raise ValueError(_("information's item not found: {0}").format(
149                 key))
150
151     # serialize/unserialize
152     def _impl_convert_consistencies(self, descr, load=False):
153         """during serialization process, many things have to be done.
154         one of them is the localisation of the options.
155         The paths are set once for all.
156
157         :type descr: :class:`tiramisu.option.OptionDescription`
158         :param load: `True` if we are at the init of the option description
159         :type load: bool
160         """
161         if not load and self._consistencies is None:
162             self._state_consistencies = None
163         elif load and self._state_consistencies is None:
164             self._consistencies = None
165             del(self._state_consistencies)
166         else:
167             if load:
168                 consistencies = self._state_consistencies
169             else:
170                 consistencies = self._consistencies
171             if isinstance(consistencies, list):
172                 new_value = []
173                 for consistency in consistencies:
174                     if load:
175                         new_value.append((consistency[0],
176                                           descr.impl_get_opt_by_path(
177                                               consistency[1])))
178                     else:
179                         new_value.append((consistency[0],
180                                           descr.impl_get_path_by_opt(
181                                               consistency[1])))
182
183             else:
184                 new_value = {}
185                 for key, _consistencies in consistencies.items():
186                     new_value[key] = []
187                     for key_cons, _cons in _consistencies:
188                         _list_cons = []
189                         for _con in _cons:
190                             if load:
191                                 _list_cons.append(
192                                     descr.impl_get_opt_by_path(_con))
193                             else:
194                                 _list_cons.append(
195                                     descr.impl_get_path_by_opt(_con))
196                         new_value[key].append((key_cons, tuple(_list_cons)))
197             if load:
198                 del(self._state_consistencies)
199                 self._consistencies = new_value
200             else:
201                 self._state_consistencies = new_value
202
203     def _impl_convert_requires(self, descr, load=False):
204         """export of the requires during the serialization process
205
206         :type descr: :class:`tiramisu.option.OptionDescription`
207         :param load: `True` if we are at the init of the option description
208         :type load: bool
209         """
210         if not load and self._requires is None:
211             self._state_requires = None
212         elif load and self._state_requires is None:
213             self._requires = None
214             del(self._state_requires)
215         else:
216             if load:
217                 _requires = self._state_requires
218             else:
219                 _requires = self._requires
220             new_value = []
221             for requires in _requires:
222                 new_requires = []
223                 for require in requires:
224                     if load:
225                         new_require = [descr.impl_get_opt_by_path(require[0])]
226                     else:
227                         new_require = [descr.impl_get_path_by_opt(require[0])]
228                     new_require.extend(require[1:])
229                     new_requires.append(tuple(new_require))
230                 new_value.append(tuple(new_requires))
231             if load:
232                 del(self._state_requires)
233                 self._requires = new_value
234             else:
235                 self._state_requires = new_value
236
237     # serialize
238     def _impl_getstate(self, descr):
239         """the under the hood stuff that need to be done
240         before the serialization.
241
242         :param descr: the parent :class:`tiramisu.option.OptionDescription`
243         """
244         self._stated = True
245         for func in dir(self):
246             if func.startswith('_impl_convert_'):
247                 getattr(self, func)(descr)
248         try:
249             self._state_readonly = self._readonly
250         except AttributeError:
251             pass
252
253     def __getstate__(self, stated=True):
254         """special method to enable the serialization with pickle
255         Usualy, a `__getstate__` method does'nt need any parameter,
256         but somme under the hood stuff need to be done before this action
257
258         :parameter stated: if stated is `True`, the serialization protocol
259                            can be performed, not ready yet otherwise
260         :parameter type: bool
261         """
262         try:
263             self._stated
264         except AttributeError:
265             raise SystemError(_('cannot serialize Option, '
266                                 'only in OptionDescription'))
267         slots = set()
268         for subclass in self.__class__.__mro__:
269             if subclass is not object:
270                 slots.update(subclass.__slots__)
271         slots -= frozenset(['_cache_paths', '__weakref__'])
272         states = {}
273         for slot in slots:
274             # remove variable if save variable converted
275             # in _state_xxxx variable
276             if '_state' + slot not in slots:
277                 if slot.startswith('_state'):
278                     # should exists
279                     states[slot] = getattr(self, slot)
280                     # remove _state_xxx variable
281                     self.__delattr__(slot)
282                 else:
283                     try:
284                         states[slot] = getattr(self, slot)
285                     except AttributeError:
286                         pass
287         if not stated:
288             del(states['_stated'])
289         return states
290
291     # unserialize
292     def _impl_setstate(self, descr):
293         """the under the hood stuff that need to be done
294         before the serialization.
295
296         :type descr: :class:`tiramisu.option.OptionDescription`
297         """
298         for func in dir(self):
299             if func.startswith('_impl_convert_'):
300                 getattr(self, func)(descr, load=True)
301         try:
302             self._readonly = self._state_readonly
303             del(self._state_readonly)
304             del(self._stated)
305         except AttributeError:
306             pass
307
308     def __setstate__(self, state):
309         """special method that enables us to serialize (pickle)
310
311         Usualy, a `__setstate__` method does'nt need any parameter,
312         but somme under the hood stuff need to be done before this action
313
314         :parameter state: a dict is passed to the loads, it is the attributes
315                           of the options object
316         :type state: dict
317         """
318         for key, value in state.items():
319             setattr(self, key, value)
320
321
322 class Option(BaseOption):
323     """
324     Abstract base class for configuration option's.
325
326     Reminder: an Option object is **not** a container for the value.
327     """
328     __slots__ = ('_multi', '_validator', '_default_multi', '_default',
329                  '_state_callback', '_callback', '_multitype',
330                  '_only_warning', '_master_slaves', '__weakref__')
331     _empty = ''
332
333     def __init__(self, name, doc, default=None, default_multi=None,
334                  requires=None, multi=False, callback=None,
335                  callback_params=None, validator=None, validator_params=None,
336                  properties=None, only_warning=False):
337         """
338         :param name: the option's name
339         :param doc: the option's description
340         :param default: specifies the default value of the option,
341                         for a multi : ['bla', 'bla', 'bla']
342         :param default_multi: 'bla' (used in case of a reset to default only at
343                         a given index)
344         :param requires: is a list of names of options located anywhere
345                          in the configuration.
346         :param multi: if true, the option's value is a list
347         :param callback: the name of a function. If set, the function's output
348                          is responsible of the option's value
349         :param callback_params: the callback's parameter
350         :param validator: the name of a function which stands for a custom
351                           validation of the value
352         :param validator_params: the validator's parameters
353         :param properties: tuple of default properties
354         :param only_warning: _validator and _consistencies don't raise if True
355                              Values()._warning contain message
356
357         """
358         super(Option, self).__init__(name, doc, requires, properties)
359         self._multi = multi
360         if validator is not None:
361             validate_callback(validator, validator_params, 'validator')
362             self._validator = (validator, validator_params)
363         else:
364             self._validator = None
365         if not self._multi and default_multi is not None:
366             raise ValueError(_("a default_multi is set whereas multi is False"
367                              " in option: {0}").format(name))
368         if default_multi is not None:
369             try:
370                 self._validate(default_multi)
371             except ValueError as err:
372                 raise ValueError(_("invalid default_multi value {0} "
373                                    "for option {1}: {2}").format(
374                                        str(default_multi), name, err))
375         if callback is not None and (default is not None or
376                                      default_multi is not None):
377             raise ValueError(_("default value not allowed if option: {0} "
378                              "is calculated").format(name))
379         if callback is None and callback_params is not None:
380             raise ValueError(_("params defined for a callback function but "
381                              "no callback defined"
382                              " yet for option {0}").format(name))
383         if callback is not None:
384             validate_callback(callback, callback_params, 'callback')
385             self._callback = (callback, callback_params)
386         else:
387             self._callback = None
388         if self._multi:
389             if default is None:
390                 default = []
391             self._multitype = multitypes.default
392             self._default_multi = default_multi
393         self._only_warning = only_warning
394         self.impl_validate(default)
395         self._default = default
396
397     def _launch_consistency(self, func, opt, vals, context, index, opt_):
398         if context is not None:
399             descr = context.cfgimpl_get_description()
400         if opt is self:
401             #values are for self, search opt_ values
402             values = vals
403             if context is not None:
404                 path = descr.impl_get_path_by_opt(opt_)
405                 values_ = context._getattr(path, validate=False)
406             else:
407                 values_ = opt_.impl_getdefault()
408             if index is not None:
409                 #value is not already set, could be higher
410                 try:
411                     values_ = values_[index]
412                 except IndexError:
413                     values_ = None
414         else:
415             #values are for opt_, search self values
416             values_ = vals
417             if context is not None:
418                 path = descr.impl_get_path_by_opt(self)
419                 values = context._getattr(path, validate=False)
420             else:
421                 values = self.impl_getdefault()
422             if index is not None:
423                 #value is not already set, could be higher
424                 try:
425                     values = values[index]
426                 except IndexError:
427                     values = None
428         if index is None and self.impl_is_multi():
429             for index in range(0, len(values)):
430                 try:
431                     value = values[index]
432                     value_ = values_[index]
433                 except IndexError:
434                     value = None
435                     value_ = None
436                 if None not in (value, value_):
437                     getattr(self, func)(opt_._name, value, value_)
438         else:
439             if None not in (values, values_):
440                 getattr(self, func)(opt_._name, values, values_)
441
442     def impl_validate(self, value, context=None, validate=True,
443                       force_no_multi=False):
444         """
445         :param value: the option's value
446         :param context: Config's context
447         :type context: :class:`tiramisu.config.Config`
448         :param validate: if true enables ``self._validator`` validation
449         :type validate: boolean
450         :param force_no_multi: if multi, value has to be a list
451                                not if force_no_multi is True
452         :type force_no_multi: boolean
453         """
454         if not validate:
455             return
456
457         def val_validator(val):
458             if self._validator is not None:
459                 if self._validator[1] is not None:
460                     validator_params = deepcopy(self._validator[1])
461                     if '' in validator_params:
462                         lst = list(validator_params[''])
463                         lst.insert(0, val)
464                         validator_params[''] = tuple(lst)
465                     else:
466                         validator_params[''] = (val,)
467                 else:
468                     validator_params = {'': (val,)}
469                 # Raise ValueError if not valid
470                 carry_out_calculation(self._name, config=context,
471                                       callback=self._validator[0],
472                                       callback_params=validator_params)
473
474         def do_validation(_value, _index=None):
475             if _value is None:
476                 return
477             ret_validation = None
478             try:
479                 # valid with self._validator
480                 val_validator(_value)
481                 # if not context launch consistency validation
482                 if context is not None:
483                     descr._valid_consistency(self, _value, context, _index)
484                 self._second_level_validation(_value)
485             except ValueError as err:
486                 msg = _("invalid value {0} for option {1}: {2}").format(
487                     _value, self._name, err)
488                 if self._only_warning:
489                     ret_validation = msg
490                 else:
491                     raise ValueError(msg)
492             # option validation
493             self._validate(_value)
494             return ret_validation
495
496         # generic calculation
497         if context is not None:
498             descr = context.cfgimpl_get_description()
499
500         ret = None
501         if not self._multi or force_no_multi:
502             ret = do_validation(value)
503         else:
504             if not isinstance(value, list):
505                 raise ValueError(_("invalid value {0} for option {1} "
506                                    "which must be a list").format(value,
507                                                                   self._name))
508             for index, val in enumerate(value):
509                 ret_ = do_validation(val, index)
510                 if ret_ is not None:
511                     ret = ret_
512         return ret
513
514     def impl_getdefault(self, default_multi=False):
515         "accessing the default value"
516         if not default_multi or not self.impl_is_multi():
517             return self._default
518         else:
519             return self.getdefault_multi()
520
521     def impl_getdefault_multi(self):
522         "accessing the default value for a multi"
523         return self._default_multi
524
525     def impl_get_multitype(self):
526         return self._multitype
527
528     def impl_get_master_slaves(self):
529         return self._master_slaves
530
531     def impl_is_empty_by_default(self):
532         "no default value has been set yet"
533         if ((not self.impl_is_multi() and self._default is None) or
534                 (self.impl_is_multi() and (self._default == []
535                                            or None in self._default))):
536             return True
537         return False
538
539     def impl_getdoc(self):
540         "accesses the Option's doc"
541         return self.impl_get_information('doc')
542
543     def impl_has_callback(self):
544         "to know if a callback has been defined or not"
545         if self._callback is None:
546             return False
547         else:
548             return True
549
550     def impl_getkey(self, value):
551         return value
552
553     def impl_is_multi(self):
554         return self._multi
555
556     def impl_add_consistency(self, func, opt):
557         if self._consistencies is None:
558             self._consistencies = []
559         if not isinstance(opt, Option):
560             raise ValueError('consistency must be set with an option')
561         if self is opt:
562             raise ValueError('cannot add consistency with itself')
563         if self.impl_is_multi() != opt.impl_is_multi():
564             raise ValueError('options in consistency'
565                              ' should be multi in two sides')
566         func = '_cons_{0}'.format(func)
567         self._launch_consistency(func,
568                                  self,
569                                  self.impl_getdefault(),
570                                  None, None, opt)
571         self._consistencies.append((func, opt))
572         self.impl_validate(self.impl_getdefault())
573
574     def _cons_not_equal(self, optname, value, value_):
575         if value == value_:
576             raise ValueError(_("invalid value {0} for option {1} "
577                                "must be different as {2} option"
578                                "").format(value, self._name, optname))
579
580     def _impl_convert_callbacks(self, descr, load=False):
581         if not load and self._callback is None:
582             self._state_callback = None
583         elif load and self._state_callback is None:
584             self._callback = None
585             del(self._state_callback)
586         else:
587             if load:
588                 callback, callback_params = self._state_callback
589             else:
590                 callback, callback_params = self._callback
591             if callback_params is not None:
592                 cllbck_prms = {}
593                 for key, values in callback_params.items():
594                     vls = []
595                     for value in values:
596                         if isinstance(value, tuple):
597                             if load:
598                                 value = (descr.impl_get_opt_by_path(value[0]),
599                                          value[1])
600                             else:
601                                 value = (descr.impl_get_path_by_opt(value[0]),
602                                          value[1])
603                         vls.append(value)
604                     cllbck_prms[key] = tuple(vls)
605             else:
606                 cllbck_prms = None
607
608             if load:
609                 del(self._state_callback)
610                 self._callback = (callback, cllbck_prms)
611             else:
612                 self._state_callback = (callback, cllbck_prms)
613
614     def _second_level_validation(self, value):
615         pass
616
617
618 class ChoiceOption(Option):
619     """represents a choice out of several objects.
620
621     The option can also have the value ``None``
622     """
623
624     __slots__ = ('_values', '_open_values')
625     _opt_type = 'string'
626
627     def __init__(self, name, doc, values, default=None, default_multi=None,
628                  requires=None, multi=False, callback=None,
629                  callback_params=None, open_values=False, validator=None,
630                  validator_params=None, properties=None, only_warning=False):
631         """
632         :param values: is a list of values the option can possibly take
633         """
634         if not isinstance(values, tuple):
635             raise TypeError(_('values must be a tuple for {0}').format(name))
636         self._values = values
637         if open_values not in (True, False):
638             raise TypeError(_('open_values must be a boolean for '
639                             '{0}').format(name))
640         self._open_values = open_values
641         super(ChoiceOption, self).__init__(name, doc, default=default,
642                                            default_multi=default_multi,
643                                            callback=callback,
644                                            callback_params=callback_params,
645                                            requires=requires,
646                                            multi=multi,
647                                            validator=validator,
648                                            validator_params=validator_params,
649                                            properties=properties,
650                                            only_warning=only_warning)
651
652     def impl_get_values(self):
653         return self._values
654
655     def impl_is_openvalues(self):
656         return self._open_values
657
658     def _validate(self, value):
659         if not self._open_values and not value in self._values:
660             raise ValueError(_('value {0} is not permitted, '
661                                'only {1} is allowed'
662                                '').format(value, self._values))
663
664
665 class BoolOption(Option):
666     "represents a choice between ``True`` and ``False``"
667     __slots__ = tuple()
668     _opt_type = 'bool'
669
670     def _validate(self, value):
671         if not isinstance(value, bool):
672             raise ValueError(_('value must be a boolean'))
673
674
675 class IntOption(Option):
676     "represents a choice of an integer"
677     __slots__ = tuple()
678     _opt_type = 'int'
679
680     def _validate(self, value):
681         if not isinstance(value, int):
682             raise ValueError(_('value must be an integer'))
683
684
685 class FloatOption(Option):
686     "represents a choice of a floating point number"
687     __slots__ = tuple()
688     _opt_type = 'float'
689
690     def _validate(self, value):
691         if not isinstance(value, float):
692             raise ValueError(_('value must be a float'))
693
694
695 class StrOption(Option):
696     "represents the choice of a string"
697     __slots__ = tuple()
698     _opt_type = 'string'
699
700     def _validate(self, value):
701         if not isinstance(value, str):
702             raise ValueError(_('value must be a string, not '
703                                '{0}').format(type(value)))
704
705
706 if sys.version_info[0] >= 3:
707     #UnicodeOption is same has StrOption in python 3+
708     class UnicodeOption(StrOption):
709         __slots__ = tuple()
710         pass
711 else:
712     class UnicodeOption(Option):
713         "represents the choice of a unicode string"
714         __slots__ = tuple()
715         _opt_type = 'unicode'
716         _empty = u''
717
718         def _validate(self, value):
719             if not isinstance(value, unicode):
720                 raise ValueError(_('value must be an unicode'))
721
722
723 class SymLinkOption(BaseOption):
724     __slots__ = ('_name', '_opt', '_state_opt')
725     _opt_type = 'symlink'
726     #not return _opt consistencies
727     _consistencies = {}
728
729     def __init__(self, name, opt):
730         self._name = name
731         if not isinstance(opt, Option):
732             raise ValueError(_('malformed symlinkoption '
733                                'must be an option '
734                                'for symlink {0}').format(name))
735         self._opt = opt
736         self._readonly = True
737
738     def __getattr__(self, name):
739         if name in ('_name', '_opt', '_opt_type', '_readonly'):
740             return object.__getattr__(self, name)
741         else:
742             return getattr(self._opt, name)
743
744     def _impl_getstate(self, descr):
745         super(SymLinkOption, self)._impl_getstate(descr)
746         self._state_opt = descr.impl_get_path_by_opt(self._opt)
747
748     def _impl_setstate(self, descr):
749         self._opt = descr.impl_get_opt_by_path(self._state_opt)
750         del(self._state_opt)
751         super(SymLinkOption, self)._impl_setstate(descr)
752
753     def _impl_convert_consistencies(self, descr, load=False):
754         if load:
755             del(self._state_consistencies)
756         else:
757             self._state_consistencies = None
758
759
760 class IPOption(Option):
761     "represents the choice of an ip"
762     __slots__ = ('_only_private', '_allow_reserved')
763     _opt_type = 'ip'
764
765     def __init__(self, name, doc, default=None, default_multi=None,
766                  requires=None, multi=False, callback=None,
767                  callback_params=None, validator=None, validator_params=None,
768                  properties=None, only_private=False, allow_reserved=False,
769                  only_warning=False):
770         self._only_private = only_private
771         self._allow_reserved = allow_reserved
772         super(IPOption, self).__init__(name, doc, default=default,
773                                        default_multi=default_multi,
774                                        callback=callback,
775                                        callback_params=callback_params,
776                                        requires=requires,
777                                        multi=multi,
778                                        validator=validator,
779                                        validator_params=validator_params,
780                                        properties=properties,
781                                        only_warning=only_warning)
782
783     def _validate(self, value):
784         IP('{0}/32'.format(value))
785
786     def _second_level_validation(self, value):
787         ip = IP('{0}/32'.format(value))
788         if not self._allow_reserved and ip.iptype() == 'RESERVED':
789             raise ValueError(_("IP mustn't not be in reserved class"))
790         if self._only_private and not ip.iptype() == 'PRIVATE':
791             raise ValueError(_("IP must be in private class"))
792
793
794 class PortOption(Option):
795     """represents the choice of a port
796     The port numbers are divided into three ranges:
797     the well-known ports,
798     the registered ports,
799     and the dynamic or private ports.
800     You can actived this three range.
801     Port number 0 is reserved and can't be used.
802     see: http://en.wikipedia.org/wiki/Port_numbers
803     """
804     __slots__ = ('_allow_range', '_allow_zero', '_min_value', '_max_value')
805     _opt_type = 'port'
806
807     def __init__(self, name, doc, default=None, default_multi=None,
808                  requires=None, multi=False, callback=None,
809                  callback_params=None, validator=None, validator_params=None,
810                  properties=None, allow_range=False, allow_zero=False,
811                  allow_wellknown=True, allow_registred=True,
812                  allow_private=False, only_warning=False):
813         self._allow_range = allow_range
814         self._min_value = None
815         self._max_value = None
816         ports_min = [0, 1, 1024, 49152]
817         ports_max = [0, 1023, 49151, 65535]
818         is_finally = False
819         for index, allowed in enumerate([allow_zero,
820                                          allow_wellknown,
821                                          allow_registred,
822                                          allow_private]):
823             if self._min_value is None:
824                 if allowed:
825                     self._min_value = ports_min[index]
826             elif not allowed:
827                 is_finally = True
828             elif allowed and is_finally:
829                 raise ValueError(_('inconsistency in allowed range'))
830             if allowed:
831                 self._max_value = ports_max[index]
832
833         if self._max_value is None:
834             raise ValueError(_('max value is empty'))
835
836         super(PortOption, self).__init__(name, doc, default=default,
837                                          default_multi=default_multi,
838                                          callback=callback,
839                                          callback_params=callback_params,
840                                          requires=requires,
841                                          multi=multi,
842                                          validator=validator,
843                                          validator_params=validator_params,
844                                          properties=properties,
845                                          only_warning=only_warning)
846
847     def _validate(self, value):
848         if self._allow_range and ":" in str(value):
849             value = str(value).split(':')
850             if len(value) != 2:
851                 raise ValueError('range must have two values only')
852             if not value[0] < value[1]:
853                 raise ValueError('first port in range must be'
854                                  ' smaller than the second one')
855         else:
856             value = [value]
857
858         for val in value:
859             if not self._min_value <= int(val) <= self._max_value:
860                 raise ValueError('port must be an between {0} and {1}'
861                                  ''.format(self._min_value, self._max_value))
862
863
864 class NetworkOption(Option):
865     "represents the choice of a network"
866     __slots__ = tuple()
867     _opt_type = 'network'
868
869     def _validate(self, value):
870         IP(value)
871
872     def _second_level_validation(self, value):
873         ip = IP(value)
874         if ip.iptype() == 'RESERVED':
875             raise ValueError(_("network shall not be in reserved class"))
876
877
878 class NetmaskOption(Option):
879     "represents the choice of a netmask"
880     __slots__ = tuple()
881     _opt_type = 'netmask'
882
883     def _validate(self, value):
884         IP('0.0.0.0/{0}'.format(value))
885
886     def _cons_network_netmask(self, optname, value, value_):
887         #opts must be (netmask, network) options
888         self.__cons_netmask(optname, value, value_, False)
889
890     def _cons_ip_netmask(self, optname, value, value_):
891         #opts must be (netmask, ip) options
892         self.__cons_netmask(optname, value, value_, True)
893
894     def __cons_netmask(self, optname, val_netmask, val_ipnetwork, make_net):
895         msg = None
896         try:
897             ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
898                     make_net=make_net)
899             #if cidr == 32, ip same has network
900             if ip.prefixlen() != 32:
901                 try:
902                     IP('{0}/{1}'.format(val_ipnetwork, val_netmask),
903                         make_net=not make_net)
904                 except ValueError:
905                     if not make_net:
906                         msg = _("invalid network {0} ({1}) "
907                                 "with netmask {2} ({3}),"
908                                 " this network is an IP")
909                 else:
910                     if make_net:
911                         msg = _("invalid IP {0} ({1}) with netmask {2} ({3}),"
912                                 " this IP is a network")
913
914         except ValueError:
915             if make_net:
916                 msg = _("invalid IP {0} ({1}) with netmask {2} ({3})")
917             else:
918                 msg = _("invalid network {0} ({1}) with netmask {2} ({3})")
919         if msg is not None:
920             raise ValueError(msg.format(val_ipnetwork, optname,
921                                         val_netmask, self._name))
922
923
924 class DomainnameOption(Option):
925     "represents the choice of a domain name"
926     __slots__ = ('_type', '_allow_ip')
927     _opt_type = 'domainname'
928
929     def __init__(self, name, doc, default=None, default_multi=None,
930                  requires=None, multi=False, callback=None,
931                  callback_params=None, validator=None, validator_params=None,
932                  properties=None, allow_ip=False, type_='domainname',
933                  only_warning=False):
934         #netbios: for MS domain
935         #hostname: to identify the device
936         #domainname:
937         #fqdn: with tld, not supported yet
938         if type_ not in ['netbios', 'hostname', 'domainname']:
939             raise ValueError(_('unknown type_ {0} for hostname').format(type_))
940         self._type = type_
941         if allow_ip not in [True, False]:
942             raise ValueError(_('allow_ip must be a boolean'))
943         self._allow_ip = allow_ip
944         super(DomainnameOption, self).__init__(name, doc, default=default,
945                                                default_multi=default_multi,
946                                                callback=callback,
947                                                callback_params=callback_params,
948                                                requires=requires,
949                                                multi=multi,
950                                                validator=validator,
951                                                validator_params=validator_params,
952                                                properties=properties,
953                                                only_warning=only_warning)
954
955     def _validate(self, value):
956         if self._allow_ip is True:
957             try:
958                 IP('{0}/32'.format(value))
959                 return
960             except ValueError:
961                 pass
962         if self._type == 'netbios':
963             length = 15
964             extrachar = ''
965         elif self._type == 'hostname':
966             length = 63
967             extrachar = ''
968         elif self._type == 'domainname':
969             length = 255
970             extrachar = '\.'
971             if '.' not in value:
972                 raise ValueError(_("invalid value for {0}, must have dot"
973                                    "").format(self._name))
974         if len(value) > length:
975             raise ValueError(_("invalid domainname's length for"
976                                " {0} (max {1})").format(self._name, length))
977         if len(value) == 1:
978             raise ValueError(_("invalid domainname's length for {0} (min 2)"
979                                "").format(self._name))
980         regexp = r'^[a-z]([a-z\d{0}-])*[a-z\d]$'.format(extrachar)
981         if re.match(regexp, value) is None:
982             raise ValueError(_('invalid domainname'))
983
984
985 class OptionDescription(BaseOption):
986     """Config's schema (organisation, group) and container of Options
987     The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
988     """
989     __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
990                  '_state_group_type', '_properties', '_children',
991                  '_consistencies', '_calc_properties', '__weakref__',
992                  '_readonly', '_impl_informations', '_state_requires',
993                  '_state_consistencies', '_stated', '_state_readonly')
994     _opt_type = 'optiondescription'
995
996     def __init__(self, name, doc, children, requires=None, properties=None):
997         """
998         :param children: a list of options (including optiondescriptions)
999
1000         """
1001         super(OptionDescription, self).__init__(name, doc, requires, properties)
1002         child_names = [child._name for child in children]
1003         #better performance like this
1004         valid_child = copy(child_names)
1005         valid_child.sort()
1006         old = None
1007         for child in valid_child:
1008             if child == old:
1009                 raise ConflictError(_('duplicate option name: '
1010                                       '{0}').format(child))
1011             old = child
1012         self._children = (tuple(child_names), tuple(children))
1013         self._cache_paths = None
1014         # the group_type is useful for filtering OptionDescriptions in a config
1015         self._group_type = groups.default
1016
1017     def impl_getdoc(self):
1018         return self.impl_get_information('doc')
1019
1020     def __getattr__(self, name):
1021         if name in self.__slots__:
1022             return object.__getattribute__(self, name)
1023         try:
1024             return self._children[1][self._children[0].index(name)]
1025         except ValueError:
1026             raise AttributeError(_('unknown Option {0} '
1027                                    'in OptionDescription {1}'
1028                                    '').format(name, self._name))
1029
1030     def impl_getkey(self, config):
1031         return tuple([child.impl_getkey(getattr(config, child._name))
1032                       for child in self.impl_getchildren()])
1033
1034     def impl_getpaths(self, include_groups=False, _currpath=None):
1035         """returns a list of all paths in self, recursively
1036            _currpath should not be provided (helps with recursion)
1037         """
1038         if _currpath is None:
1039             _currpath = []
1040         paths = []
1041         for option in self.impl_getchildren():
1042             attr = option._name
1043             if isinstance(option, OptionDescription):
1044                 if include_groups:
1045                     paths.append('.'.join(_currpath + [attr]))
1046                 paths += option.impl_getpaths(include_groups=include_groups,
1047                                               _currpath=_currpath + [attr])
1048             else:
1049                 paths.append('.'.join(_currpath + [attr]))
1050         return paths
1051
1052     def impl_getchildren(self):
1053         return self._children[1]
1054
1055     def impl_build_cache(self,
1056                          cache_path=None,
1057                          cache_option=None,
1058                          _currpath=None,
1059                          _consistencies=None,
1060                          force_no_consistencies=False):
1061         if _currpath is None and self._cache_paths is not None:
1062             # cache already set
1063             return
1064         if _currpath is None:
1065             save = True
1066             _currpath = []
1067             if not force_no_consistencies:
1068                 _consistencies = {}
1069         else:
1070             save = False
1071         if cache_path is None:
1072             cache_path = []
1073             cache_option = []
1074         for option in self.impl_getchildren():
1075             attr = option._name
1076             if option in cache_option:
1077                 raise ConflictError(_('duplicate option: {0}').format(option))
1078
1079             cache_option.append(option)
1080             if not force_no_consistencies:
1081                 option._readonly = True
1082             cache_path.append(str('.'.join(_currpath + [attr])))
1083             if not isinstance(option, OptionDescription):
1084                 if not force_no_consistencies and \
1085                         option._consistencies is not None:
1086                     for consistency in option._consistencies:
1087                         func, opt = consistency
1088                         opts = (option, opt)
1089                         _consistencies.setdefault(opt,
1090                                                   []).append((func, opts))
1091                         _consistencies.setdefault(option,
1092                                                   []).append((func, opts))
1093             else:
1094                 _currpath.append(attr)
1095                 option.impl_build_cache(cache_path,
1096                                         cache_option,
1097                                         _currpath,
1098                                         _consistencies,
1099                                         force_no_consistencies)
1100                 _currpath.pop()
1101         if save:
1102             self._cache_paths = (tuple(cache_option), tuple(cache_path))
1103             if not force_no_consistencies:
1104                 self._consistencies = _consistencies
1105                 self._readonly = True
1106
1107     def impl_get_opt_by_path(self, path):
1108         try:
1109             return self._cache_paths[0][self._cache_paths[1].index(path)]
1110         except ValueError:
1111             raise AttributeError(_('no option for path {0}').format(path))
1112
1113     def impl_get_path_by_opt(self, opt):
1114         try:
1115             return self._cache_paths[1][self._cache_paths[0].index(opt)]
1116         except ValueError:
1117             raise AttributeError(_('no option {0} found').format(opt))
1118
1119     # ____________________________________________________________
1120     def impl_set_group_type(self, group_type):
1121         """sets a given group object to an OptionDescription
1122
1123         :param group_type: an instance of `GroupType` or `MasterGroupType`
1124                               that lives in `setting.groups`
1125         """
1126         if self._group_type != groups.default:
1127             raise TypeError(_('cannot change group_type if already set '
1128                             '(old {0}, new {1})').format(self._group_type,
1129                                                          group_type))
1130         if isinstance(group_type, groups.GroupType):
1131             self._group_type = group_type
1132             if isinstance(group_type, groups.MasterGroupType):
1133                 #if master (same name has group) is set
1134                 identical_master_child_name = False
1135                 #for collect all slaves
1136                 slaves = []
1137                 master = None
1138                 for child in self.impl_getchildren():
1139                     if isinstance(child, OptionDescription):
1140                         raise ValueError(_("master group {0} shall not have "
1141                                          "a subgroup").format(self._name))
1142                     if isinstance(child, SymLinkOption):
1143                         raise ValueError(_("master group {0} shall not have "
1144                                          "a symlinkoption").format(self._name))
1145                     if not child.impl_is_multi():
1146                         raise ValueError(_("not allowed option {0} "
1147                                          "in group {1}"
1148                                          ": this option is not a multi"
1149                                          "").format(child._name, self._name))
1150                     if child._name == self._name:
1151                         identical_master_child_name = True
1152                         child._multitype = multitypes.master
1153                         master = child
1154                     else:
1155                         slaves.append(child)
1156                 if master is None:
1157                     raise ValueError(_('master group with wrong'
1158                                        ' master name for {0}'
1159                                        ).format(self._name))
1160                 master._master_slaves = tuple(slaves)
1161                 for child in self.impl_getchildren():
1162                     if child != master:
1163                         child._master_slaves = master
1164                         child._multitype = multitypes.slave
1165                 if not identical_master_child_name:
1166                     raise ValueError(_("no child has same nom has master group"
1167                                        " for: {0}").format(self._name))
1168         else:
1169             raise ValueError(_('group_type: {0}'
1170                                ' not allowed').format(group_type))
1171
1172     def impl_get_group_type(self):
1173         return self._group_type
1174
1175     def _valid_consistency(self, opt, value, context=None, index=None):
1176         consistencies = self._consistencies.get(opt)
1177         if consistencies is not None:
1178             for consistency in consistencies:
1179                 opt_ = consistency[1]
1180                 ret = opt_[0]._launch_consistency(consistency[0],
1181                                                   opt,
1182                                                   value,
1183                                                   context,
1184                                                   index,
1185                                                   opt_[1])
1186                 if ret is False:
1187                     return False
1188         return True
1189
1190     def _impl_getstate(self, descr=None):
1191         """enables us to export into a dict
1192         :param descr: parent :class:`tiramisu.option.OptionDescription`
1193         """
1194         if descr is None:
1195             self.impl_build_cache()
1196             descr = self
1197         super(OptionDescription, self)._impl_getstate(descr)
1198         self._state_group_type = str(self._group_type)
1199         for option in self.impl_getchildren():
1200             option._impl_getstate(descr)
1201
1202     def __getstate__(self):
1203         """special method to enable the serialization with pickle
1204         """
1205         stated = True
1206         try:
1207             # the `_state` attribute is a flag that which tells us if
1208             # the serialization can be performed
1209             self._stated
1210         except AttributeError:
1211             # if cannot delete, _impl_getstate never launch
1212             # launch it recursivement
1213             # _stated prevent __getstate__ launch more than one time
1214             # _stated is delete, if re-serialize, re-lauch _impl_getstate
1215             self._impl_getstate()
1216             stated = False
1217         return super(OptionDescription, self).__getstate__(stated)
1218
1219     def _impl_setstate(self, descr=None):
1220         """enables us to import from a dict
1221         :param descr: parent :class:`tiramisu.option.OptionDescription`
1222         """
1223         if descr is None:
1224             self._cache_paths = None
1225             self.impl_build_cache(force_no_consistencies=True)
1226             descr = self
1227         self._group_type = getattr(groups, self._state_group_type)
1228         del(self._state_group_type)
1229         super(OptionDescription, self)._impl_setstate(descr)
1230         for option in self.impl_getchildren():
1231             option._impl_setstate(descr)
1232
1233     def __setstate__(self, state):
1234         super(OptionDescription, self).__setstate__(state)
1235         try:
1236             self._stated
1237         except AttributeError:
1238             self._impl_setstate()
1239
1240
1241 def validate_requires_arg(requires, name):
1242     """check malformed requirements
1243     and tranform dict to internal tuple
1244
1245     :param requires: have a look at the
1246                      :meth:`tiramisu.setting.Settings.apply_requires` method to
1247                      know more about
1248                      the description of the requires dictionary
1249     """
1250     if requires is None:
1251         return None, None
1252     ret_requires = {}
1253     config_action = {}
1254
1255     # start parsing all requires given by user (has dict)
1256     # transforme it to a tuple
1257     for require in requires:
1258         if not type(require) == dict:
1259             raise ValueError(_("malformed requirements type for option:"
1260                                " {0}, must be a dict").format(name))
1261         valid_keys = ('option', 'expected', 'action', 'inverse', 'transitive',
1262                       'same_action')
1263         unknown_keys = frozenset(require.keys()) - frozenset(valid_keys)
1264         if unknown_keys != frozenset():
1265             raise ValueError('malformed requirements for option: {0}'
1266                              ' unknown keys {1}, must only '
1267                              '{2}'.format(name,
1268                                           unknown_keys,
1269                                           valid_keys))
1270         # prepare all attributes
1271         try:
1272             option = require['option']
1273             expected = require['expected']
1274             action = require['action']
1275         except KeyError:
1276             raise ValueError(_("malformed requirements for option: {0}"
1277                                " require must have option, expected and"
1278                                " action keys").format(name))
1279         inverse = require.get('inverse', False)
1280         if inverse not in [True, False]:
1281             raise ValueError(_('malformed requirements for option: {0}'
1282                                ' inverse must be boolean'))
1283         transitive = require.get('transitive', True)
1284         if transitive not in [True, False]:
1285             raise ValueError(_('malformed requirements for option: {0}'
1286                                ' transitive must be boolean'))
1287         same_action = require.get('same_action', True)
1288         if same_action not in [True, False]:
1289             raise ValueError(_('malformed requirements for option: {0}'
1290                                ' same_action must be boolean'))
1291
1292         if not isinstance(option, Option):
1293             raise ValueError(_('malformed requirements '
1294                                'must be an option in option {0}').format(name))
1295         if option.impl_is_multi():
1296             raise ValueError(_('malformed requirements option {0} '
1297                                'should not be a multi').format(name))
1298         if expected is not None:
1299             try:
1300                 option._validate(expected)
1301             except ValueError as err:
1302                 raise ValueError(_('malformed requirements second argument '
1303                                    'must be valid for option {0}'
1304                                    ': {1}').format(name, err))
1305         if action in config_action:
1306             if inverse != config_action[action]:
1307                 raise ValueError(_("inconsistency in action types"
1308                                    " for option: {0}"
1309                                    " action: {1}").format(name, action))
1310         else:
1311             config_action[action] = inverse
1312         if action not in ret_requires:
1313             ret_requires[action] = {}
1314         if option not in ret_requires[action]:
1315             ret_requires[action][option] = (option, [expected], action,
1316                                             inverse, transitive, same_action)
1317         else:
1318             ret_requires[action][option][1].append(expected)
1319     # transform dict to tuple
1320     ret = []
1321     for opt_requires in ret_requires.values():
1322         ret_action = []
1323         for require in opt_requires.values():
1324             ret_action.append((require[0], tuple(require[1]), require[2],
1325                                require[3], require[4], require[5]))
1326         ret.append(tuple(ret_action))
1327     return frozenset(config_action.keys()), tuple(ret)
1328
1329
1330 def validate_callback(callback, callback_params, type_):
1331     if type(callback) != FunctionType:
1332         raise ValueError(_('{0} should be a function').format(type_))
1333     if callback_params is not None:
1334         if not isinstance(callback_params, dict):
1335             raise ValueError(_('{0}_params should be a dict').format(type_))
1336         for key, callbacks in callback_params.items():
1337             if key != '' and len(callbacks) != 1:
1338                 raise ValueError(_('{0}_params with key {1} should not have '
1339                                    'length different to 1').format(type_,
1340                                                                    key))
1341             if not isinstance(callbacks, tuple):
1342                 raise ValueError(_('{0}_params should be tuple for key "{1}"'
1343                                    ).format(type_, key))
1344             for callbk in callbacks:
1345                 if isinstance(callbk, tuple):
1346                     option, force_permissive = callbk
1347                     if type_ == 'validator' and not force_permissive:
1348                         raise ValueError(_('validator not support tuple'))
1349                     if not isinstance(option, Option) and not \
1350                             isinstance(option, SymLinkOption):
1351                         raise ValueError(_('{0}_params should have an option '
1352                                            'not a {0} for first argument'
1353                                            ).format(type_, type(option)))
1354                     if force_permissive not in [True, False]:
1355                         raise ValueError(_('{0}_params should have a boolean'
1356                                            ' not a {0} for second argument'
1357                                            ).format(type_, type(
1358                                                force_permissive)))